Skip to content

Commit cb11dc0

Browse files
Enhance task reconciler logging and migrate shared mocks
• Add tool ID to interpreter args and result logging for tool call context tracking • Refactor interpreter result logging to output structured JSON with tool ID and content fields • Migrate CommandRunner and UserInfo mocks from frontend/cli/cmd/mocks to shared/mocks for cross-package reusability • Update test imports in daemon_install_test, root_test, and task_list_test • Enhance task_list_test request matching with CmpEqual helper and protocol buffer comparison • Update agent names in model_provider_test (coder → edit, architect → plan) • Add quick agent to default agent list This improves tool call debuggability through granular logging, enables mock reuse across packages, and strengthens test reliability with better request matching. Co-authored-by: construct-agent <noreply@construct.sh>
1 parent 87bda24 commit cb11dc0

10 files changed

Lines changed: 90 additions & 36 deletions

File tree

backend/agent/task_reconciler.go

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -417,9 +417,6 @@ func (r *TaskReconciler) reconcileInvokeModel(ctx context.Context, taskID uuid.U
417417
LogError(logger, "failed to assemble system prompt", err)
418418
return Result{}, fmt.Errorf("failed to assemble system prompt: %w", err)
419419
}
420-
logger.DebugContext(ctx, "system prompt assembled",
421-
"prompt_length", len(systemPrompt),
422-
)
423420

424421
LogOperationStart(logger, "invoke model")
425422
invokeStart := time.Now()
@@ -717,7 +714,7 @@ func (r *TaskReconciler) callTools(ctx context.Context, task *memory.Task, messa
717714
logger.ErrorContext(ctx, "failed to unmarshal tool call", "error", err)
718715
return nil, nil, fmt.Errorf("failed to unmarshal tool call: %w", err)
719716
}
720-
logInterpreterArgs(ctx, task.ID, toolCall.Args)
717+
logInterpreterArgs(ctx, task.ID, toolCall.ID, toolCall.Args)
721718

722719
toolStart := time.Now()
723720
result, err := r.interpreter.Interpret(ctx, afero.NewOsFs(), toolCall.Args, &codeact.Task{
@@ -739,12 +736,13 @@ func (r *TaskReconciler) callTools(ctx context.Context, task *memory.Task, messa
739736
"success", true,
740737
)
741738
}
742-
toolResults = append(toolResults, &codeact.InterpreterToolResult{
739+
interpreterResult := &codeact.InterpreterToolResult{
743740
ID: toolCall.ID,
744741
Output: result.ConsoleOutput,
745742
FunctionCalls: result.FunctionCalls,
746743
Error: conv.ErrorToString(err),
747-
})
744+
}
745+
toolResults = append(toolResults, interpreterResult)
748746

749747
for tool, count := range result.ToolStats {
750748
toolStats[tool] += count
@@ -753,7 +751,7 @@ func (r *TaskReconciler) callTools(ctx context.Context, task *memory.Task, messa
753751
"count", count,
754752
)
755753
}
756-
logInterpreterResult(ctx, task.ID, result)
754+
logInterpreterResult(ctx, task.ID, toolCall.ID, interpreterResult)
757755
}
758756
}
759757

@@ -803,28 +801,28 @@ func calculateCost(usage model.Usage, model *memory.Model) float64 {
803801
(float64(usage.CacheReadTokens) * model.CacheReadCost / 1000000)
804802
}
805803

806-
func logInterpreterArgs(ctx context.Context, taskID uuid.UUID, args json.RawMessage) {
804+
func logInterpreterArgs(ctx context.Context, taskID uuid.UUID, toolID string, args json.RawMessage) {
807805
var a codeact.InterpreterInput
808806
err := json.Unmarshal(args, &a)
809807
if err != nil {
810808
slog.ErrorContext(ctx, "failed to unmarshal interpreter args", "error", err)
811809
return
812810
}
813811

814-
logInterpreter(ctx, taskID, a.Script, "args_interpreter")
812+
logInterpreter(ctx, taskID, toolID, a.Script, "args_interpreter")
815813
}
816814

817-
func logInterpreterResult(ctx context.Context, taskID uuid.UUID, result *codeact.InterpreterOutput) {
815+
func logInterpreterResult(ctx context.Context, taskID uuid.UUID, toolID string, result *codeact.InterpreterToolResult) {
818816
jsonResult, err := json.MarshalIndent(result, "", " ")
819817
if err != nil {
820818
slog.ErrorContext(ctx, "failed to marshal interpreter result", "error", err)
821819
return
822820
}
823821

824-
logInterpreter(ctx, taskID, string(jsonResult), "result_interpreter")
822+
logInterpreter(ctx, taskID, toolID, string(jsonResult), "result_interpreter")
825823
}
826824

827-
func logInterpreter(ctx context.Context, taskID uuid.UUID, content string, operation string) {
825+
func logInterpreter(ctx context.Context, taskID uuid.UUID, toolID string, content string, operation string) {
828826
taskDir := fmt.Sprintf("/tmp/tool_call/%s", taskID.String())
829827
if _, err := os.Stat(taskDir); os.IsNotExist(err) {
830828
err = os.MkdirAll(taskDir, 0755)
@@ -841,7 +839,23 @@ func logInterpreter(ctx context.Context, taskID uuid.UUID, content string, opera
841839
}
842840
defer fp.Close()
843841

844-
_, err = fp.WriteString(content + "\n\n" + strings.Repeat("-", 100) + "\n\n")
842+
type ToolOperation struct {
843+
ToolID string `json:"tool_id"`
844+
Content string `json:"content"`
845+
}
846+
847+
toolOperation := ToolOperation{
848+
ToolID: toolID,
849+
Content: content,
850+
}
851+
852+
jsonOperation, err := json.MarshalIndent(toolOperation, "", " ")
853+
if err != nil {
854+
slog.ErrorContext(ctx, "failed to marshal tool operation", "error", err)
855+
return
856+
}
857+
858+
_, err = fp.WriteString(string(jsonOperation) + "\n\n" + strings.Repeat("-", 100) + "\n\n")
845859
if err != nil {
846860
slog.ErrorContext(ctx, "failed to write interpreter args", "error", err)
847861
}

backend/api/model_provider_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,15 @@ func TestCreateModelProvider(t *testing.T) {
8080
},
8181
Agents: []*memory.Agent{
8282
{
83-
Name: "coder",
83+
Name: "edit",
8484
Builtin: true,
8585
},
8686
{
87-
Name: "architect",
87+
Name: "quick",
88+
Builtin: true,
89+
},
90+
{
91+
Name: "plan",
8892
Builtin: true,
8993
},
9094
},

backend/tool/codeact/read_file.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ Returns an object containing the file content as a string:
2626
}
2727
%[1]s
2828
29-
If the file doesn't exist or cannot be read, it will throw an exception describing the issue. The content will be returned with line numbers prefixed to each line.
30-
The line numbers are not part of the actual file content, they are just for you to understand the file structure.
29+
If the file doesn't exist or cannot be read, it will throw an exception describing the issue.
3130
3231
When reading a specific line range, the content will include context comments:
3332
- %[2]s// skipped X lines%[2]s at the beginning if start_line > 1

backend/tool/filesystem/create_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ import (
1111
)
1212

1313
func TestCreateFile(t *testing.T) {
14-
t.Parallel()
15-
1614
setup := &base.ToolTestSetup[*CreateFileInput, *CreateFileResult]{
1715
Call: func(ctx context.Context, services *base.ToolTestServices, input *CreateFileInput) (*CreateFileResult, error) {
1816
return CreateFile(services.FS, input)

backend/tool/filesystem/edit_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ import (
1111
)
1212

1313
func TestEditFile(t *testing.T) {
14-
t.Parallel()
15-
1614
setup := &base.ToolTestSetup[*EditFileInput, *EditFileResult]{
1715
Call: func(ctx context.Context, services *base.ToolTestServices, input *EditFileInput) (*EditFileResult, error) {
1816
return EditFile(services.FS, input)

frontend/cli/cmd/daemon_install_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"connectrpc.com/connect"
88
api_client "github.com/furisto/construct/api/go/client"
99
v1 "github.com/furisto/construct/api/go/v1"
10-
"github.com/furisto/construct/frontend/cli/cmd/mocks"
10+
"github.com/furisto/construct/shared/mocks"
1111
"github.com/furisto/construct/shared/conv"
1212
"github.com/spf13/afero"
1313
"go.uber.org/mock/gomock"

frontend/cli/cmd/root_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"connectrpc.com/connect"
1010
api_client "github.com/furisto/construct/api/go/client"
1111
v1 "github.com/furisto/construct/api/go/v1"
12-
"github.com/furisto/construct/frontend/cli/cmd/mocks"
12+
"github.com/furisto/construct/shared/mocks"
1313
"github.com/furisto/construct/shared/conv"
1414
"github.com/google/go-cmp/cmp"
1515
"github.com/spf13/afero"

frontend/cli/cmd/task_list_test.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ import (
77
"connectrpc.com/connect"
88
api_client "github.com/furisto/construct/api/go/client"
99
v1 "github.com/furisto/construct/api/go/v1"
10+
"github.com/furisto/construct/shared/conv"
11+
"github.com/google/go-cmp/cmp/cmpopts"
1012
"github.com/google/uuid"
1113
"go.uber.org/mock/gomock"
14+
"google.golang.org/protobuf/testing/protocmp"
1215
"google.golang.org/protobuf/types/known/timestamppb"
1316
)
1417

@@ -187,6 +190,7 @@ func TestTaskList(t *testing.T) {
187190
&connect.Request[v1.ListTasksRequest]{
188191
Msg: &v1.ListTasksRequest{
189192
Filter: &v1.ListTasksRequest_Filter{},
193+
PageSize: conv.Ptr(int32(0)),
190194
},
191195
},
192196
).Return(nil, connect.NewError(connect.CodeInternal, nil))
@@ -206,11 +210,15 @@ func setupTaskListMock(mockClient *api_client.MockClient, agentID *string, tasks
206210

207211
mockClient.Task.EXPECT().ListTasks(
208212
gomock.Any(),
209-
&connect.Request[v1.ListTasksRequest]{
213+
CmpEqual(&connect.Request[v1.ListTasksRequest]{
210214
Msg: &v1.ListTasksRequest{
211-
Filter: filter,
215+
Filter: filter,
216+
PageSize: conv.Ptr(int32(0)),
212217
},
213-
},
218+
}, protocmp.Transform(),
219+
cmpopts.IgnoreUnexported(connect.Request[v1.ListTasksRequest]{}),
220+
cmpopts.IgnoreFields(v1.ListTasksRequest{}, "state"),
221+
),
214222
).Return(&connect.Response[v1.ListTasksResponse]{
215223
Msg: &v1.ListTasksResponse{
216224
Tasks: tasks,
@@ -221,13 +229,16 @@ func setupTaskListMock(mockClient *api_client.MockClient, agentID *string, tasks
221229
func setupAgentLookupForTaskListMock(mockClient *api_client.MockClient, agentName, agentID string) {
222230
mockClient.Agent.EXPECT().ListAgents(
223231
gomock.Any(),
224-
&connect.Request[v1.ListAgentsRequest]{
232+
CmpEqual(&connect.Request[v1.ListAgentsRequest]{
225233
Msg: &v1.ListAgentsRequest{
226234
Filter: &v1.ListAgentsRequest_Filter{
227235
Names: []string{agentName},
228236
},
229237
},
230-
},
238+
}, protocmp.Transform(),
239+
cmpopts.IgnoreUnexported(connect.Request[v1.ListAgentsRequest]{}),
240+
cmpopts.IgnoreFields(v1.ListAgentsRequest{}, "state"),
241+
),
231242
).Return(&connect.Response[v1.ListAgentsResponse]{
232243
Msg: &v1.ListAgentsResponse{
233244
Agents: []*v1.Agent{

frontend/cli/cmd/mocks/command_runner_mock.go renamed to shared/mocks/command_runner_mock.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 37 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)