Skip to content

Commit d0b814e

Browse files
fix(agent): surface real manage_tasks parse errors (#1084)
Invalid nested fields were reported as a missing action. Use the arktype summary, and coerce in_progress to doing at the boundary.
1 parent 78ee45f commit d0b814e

2 files changed

Lines changed: 53 additions & 5 deletions

File tree

src/agent/tasks.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ import type { ToolDefinition } from "@intx/types/runtime";
77
export const TaskStatusSchema = type("'todo' | 'doing' | 'done' | 'cancelled'");
88
export type TaskStatus = typeof TaskStatusSchema.infer;
99

10+
// GLM (and other Claude-shaped) payloads send in_progress; map it at the
11+
// boundary so stored tasks stay on the todo/doing/done/cancelled enum.
12+
const TaskStatusInput = type(
13+
"'todo' | 'doing' | 'done' | 'cancelled' | 'in_progress'",
14+
).pipe((s): TaskStatus => (s === "in_progress" ? "doing" : s));
15+
1016
export const TaskSchema = type({
1117
id: "string>0",
1218
title: "string>0",
@@ -27,12 +33,12 @@ const ManageTasksArgsSchema = type({
2733
"tasks?": type({
2834
id: "string>0",
2935
title: "string>0",
30-
"status?": TaskStatusSchema,
36+
"status?": TaskStatusInput,
3137
}).array(),
3238
"updates?": type({
3339
id: "string>0",
3440
"title?": "string>0",
35-
"status?": TaskStatusSchema,
41+
"status?": TaskStatusInput,
3642
}).array(),
3743
});
3844

@@ -175,10 +181,10 @@ export type ManageTasksRunner = (rawArgs: Record<string, unknown>) => Promise<{
175181
export function createManageTasksRunner(): ManageTasksRunner {
176182
let tasks: Task[] = [];
177183
return async (rawArgs) => {
178-
const parsed = parseManageTasksArgs(rawArgs);
179-
if (parsed === null) {
184+
const parsed = ManageTasksArgsSchema(rawArgs);
185+
if (parsed instanceof type.errors) {
180186
return {
181-
content: "Error: manage_tasks requires action ('create' or 'update').",
187+
content: `Error: manage_tasks arguments invalid: ${parsed.summary}`,
182188
isError: true,
183189
};
184190
}

tests/unit/agent/tasks.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
applyManageTasks,
44
createManageTasksRunner,
55
hasActiveTasks,
6+
parseManageTasksArgs,
67
type Task,
78
} from "../../../src/agent/tasks.js";
89

@@ -157,4 +158,45 @@ test("manage_tasks runner rejects invalid args", async () => {
157158
const run = createManageTasksRunner();
158159
const result = await run({});
159160
expect(result.isError).toBe(true);
161+
expect(result.content).toMatch(/action/i);
162+
});
163+
164+
test("manage_tasks runner names invalid status, not missing action", async () => {
165+
const run = createManageTasksRunner();
166+
const result = await run({
167+
action: "create",
168+
tasks: [{ id: "t1", title: "Inspect", status: "bogus" }],
169+
});
170+
expect(result.isError).toBe(true);
171+
expect(result.content).toMatch(/status/i);
172+
expect(result.content).not.toMatch(/requires action/i);
173+
});
174+
175+
test("manage_tasks runner accepts a GLM-shaped create with in_progress", async () => {
176+
const run = createManageTasksRunner();
177+
const glmPayload = {
178+
action: "create",
179+
tasks: [
180+
{ id: "t1", title: "Inspect branch", status: "in_progress" },
181+
{ id: "t2", title: "Write failing test", status: "todo" },
182+
],
183+
};
184+
const result = await run(glmPayload);
185+
expect(result.isError).toBeUndefined();
186+
expect(result.content).toBe("Tasks updated.");
187+
expect(
188+
(
189+
await run({
190+
action: "update",
191+
updates: [{ id: "t1", status: "doing" }],
192+
})
193+
).content,
194+
).toBe("No change: t1 already doing.");
195+
expect(parseManageTasksArgs(glmPayload)).toEqual({
196+
action: "create",
197+
tasks: [
198+
{ id: "t1", title: "Inspect branch", status: "doing" },
199+
{ id: "t2", title: "Write failing test", status: "todo" },
200+
],
201+
});
160202
});

0 commit comments

Comments
 (0)