minimal-agent 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -1
- package/plugins/workflow-runner/plugin.js +27 -15
- package/plugins/workflow-runner/plugin.ts +34 -20
- package/plugins/workflow-runner/src/agentCommon.js +13 -12
- package/plugins/workflow-runner/src/agentCommon.ts +22 -13
- package/plugins/workflow-runner/src/expressions.js +29 -8
- package/plugins/workflow-runner/src/expressions.ts +28 -10
- package/plugins/workflow-runner/src/index.js +50 -20
- package/plugins/workflow-runner/src/index.ts +75 -31
- package/plugins/workflow-runner/src/multimodal.js +2 -1
- package/plugins/workflow-runner/src/multimodal.ts +2 -1
- package/plugins/workflow-runner/src/recovery.js +9 -2
- package/plugins/workflow-runner/src/recovery.ts +9 -2
- package/plugins/workflow-runner/src/runner.js +31 -5
- package/plugins/workflow-runner/src/runner.ts +33 -5
- package/plugins/workflow-runner/src/stepExecutors/assert.js +2 -1
- package/plugins/workflow-runner/src/stepExecutors/assert.ts +3 -1
- package/plugins/workflow-runner/src/stepExecutors/skill.js +5 -6
- package/plugins/workflow-runner/src/stepExecutors/skill.ts +12 -7
- package/plugins/workflow-runner/src/stepExecutors/tool.js +91 -61
- package/plugins/workflow-runner/src/stepExecutors/tool.ts +111 -71
- package/plugins/workflow-runner/src/stepExecutors/verify.js +6 -3
- package/plugins/workflow-runner/src/stepExecutors/verify.ts +7 -3
- package/plugins/workflow-runner/src/taskIdentity.js +197 -21
- package/plugins/workflow-runner/src/taskIdentity.ts +201 -20
- package/plugins/workflow-runner/src/toolRuntime.js +48 -0
- package/plugins/workflow-runner/src/toolRuntime.ts +64 -0
- package/plugins/workflow-runner/src/types.js +113 -7
- package/plugins/workflow-runner/src/types.ts +137 -14
- package/plugins/workflow-runner/src/workflowState.js +455 -54
- package/plugins/workflow-runner/src/workflowState.ts +508 -63
- package/plugins/workflow-runner/test/cli.e2e.test.ts +20 -0
- package/plugins/workflow-runner/test/context-files.test.ts +47 -16
- package/plugins/workflow-runner/test/e2eNewLayout.test.ts +314 -0
- package/plugins/workflow-runner/test/engineRobustness.test.ts +9 -1
- package/plugins/workflow-runner/test/expressions.test.ts +42 -0
- package/plugins/workflow-runner/test/hostCapability.test.ts +9 -3
- package/plugins/workflow-runner/test/illustrate.load.test.ts +2 -2
- package/plugins/workflow-runner/test/multimodal.test.ts +61 -10
- package/plugins/workflow-runner/test/parseWorkflowArgs.test.ts +19 -0
- package/plugins/workflow-runner/test/ppt.load.test.ts +15 -15
- package/plugins/workflow-runner/test/ralphLoop.e2e.test.ts +3 -2
- package/plugins/workflow-runner/test/research.load.test.ts +328 -0
- package/plugins/workflow-runner/test/runnerPreflight.test.ts +68 -11
- package/plugins/workflow-runner/test/stateLifecycle.test.ts +60 -12
- package/plugins/workflow-runner/test/taskArbitration.test.ts +19 -3
- package/plugins/workflow-runner/test/taskLifecycle.test.ts +61 -7
- package/plugins/workflow-runner/test/toolRuntimeIntegration.test.ts +168 -0
- package/plugins/workflow-runner/test/webppt.load.test.ts +4 -4
- package/plugins/workflow-runner/test/word.load.test.ts +9 -9
- package/skills/skill-creator/scripts/run_eval.py +3 -2
- package/src/cli/print.js +36 -4
- package/src/cli/print.ts +42 -4
- package/src/cli/streamJson.js +19 -4
- package/src/cli/streamJson.ts +17 -4
- package/src/config/configDefaults.js +25 -0
- package/src/config/configDefaults.ts +33 -0
- package/src/config/configFile.js +2 -21
- package/src/config/configFile.ts +11 -24
- package/src/config/configManagement.js +1 -1
- package/src/config/configManagement.ts +2 -2
- package/src/config/configService.js +24 -0
- package/src/config/configService.ts +23 -0
- package/src/context/compact.js +4 -0
- package/src/context/compact.ts +4 -0
- package/src/context/contextBundle.js +38 -13
- package/src/context/contextBundle.ts +47 -13
- package/src/harness/stallGuard.js +19 -4
- package/src/harness/stallGuard.ts +30 -7
- package/src/infra/verification.js +25 -18
- package/src/infra/verification.ts +50 -18
- package/src/loop.js +95 -11
- package/src/loop.ts +125 -16
- package/src/main.js +8 -1
- package/src/main.tsx +7 -1
- package/src/memory/constants.js +10 -0
- package/src/memory/constants.ts +13 -0
- package/src/memory/episodicMemory.js +274 -2
- package/src/memory/episodicMemory.ts +306 -1
- package/src/memory/fileStore.js +24 -4
- package/src/memory/fileStore.ts +22 -2
- package/src/memory/memoryService.js +79 -12
- package/src/memory/memoryService.ts +108 -9
- package/src/memory/repository.js +415 -85
- package/src/memory/repository.ts +437 -86
- package/src/memory/turnJournal.js +95 -0
- package/src/memory/turnJournal.ts +120 -0
- package/src/memory/types.ts +16 -0
- package/src/plugins/pluginApi.ts +30 -6
- package/src/plugins/pluginRunner.js +23 -8
- package/src/plugins/pluginRunner.ts +32 -13
- package/src/prompts/policyRegistry.js +55 -1
- package/src/prompts/policyRegistry.ts +55 -1
- package/src/prompts/promptAssembly.js +39 -7
- package/src/prompts/promptAssembly.ts +145 -59
- package/src/session/runAgentTurn.js +43 -21
- package/src/session/runAgentTurn.ts +68 -26
- package/src/session/runTurn.js +11 -46
- package/src/session/runTurn.ts +12 -46
- package/src/session/runtimeStore.js +149 -0
- package/src/session/runtimeStore.ts +197 -0
- package/src/session/sessionState.js +5 -0
- package/src/session/sessionState.ts +14 -0
- package/src/tools/advisor/advisor.js +76 -9
- package/src/tools/advisor/advisor.ts +79 -9
- package/src/tools/agent/agent.js +143 -14
- package/src/tools/agent/agent.ts +149 -14
- package/src/tools/agent/agentTaskService.js +60 -0
- package/src/tools/agent/agentTaskService.ts +113 -0
- package/src/tools/agent/agentTypes.js +5 -0
- package/src/tools/agent/agentTypes.ts +5 -0
- package/src/tools/bash/bash.js +80 -15
- package/src/tools/bash/bash.ts +83 -15
- package/src/tools/capabilities.js +5 -0
- package/src/tools/capabilities.ts +11 -1
- package/src/tools/edit/edit.js +7 -4
- package/src/tools/edit/edit.ts +7 -4
- package/src/tools/edit/multi-edit.js +4 -2
- package/src/tools/edit/multi-edit.ts +4 -2
- package/src/tools/glob/glob.js +156 -40
- package/src/tools/glob/glob.ts +185 -46
- package/src/tools/index.js +35 -17
- package/src/tools/index.ts +81 -20
- package/src/tools/lsp/lsp.js +165 -22
- package/src/tools/lsp/lsp.ts +206 -36
- package/src/tools/powershell/powershell.js +134 -27
- package/src/tools/powershell/powershell.ts +171 -30
- package/src/tools/presentresult/presentresult.js +10 -0
- package/src/tools/presentresult/presentresult.ts +10 -0
- package/src/tools/read/read.js +249 -29
- package/src/tools/read/read.ts +396 -59
- package/src/tools/shared/diffSummary.js +44 -0
- package/src/tools/shared/diffSummary.ts +63 -0
- package/src/tools/shared/fileActivity.js +2 -1
- package/src/tools/shared/fileActivity.ts +3 -0
- package/src/tools/shared/permanentDeleteGuard.js +57 -0
- package/src/tools/shared/permanentDeleteGuard.ts +71 -0
- package/src/tools/shared/toolOutputStore.js +97 -0
- package/src/tools/shared/toolOutputStore.ts +110 -0
- package/src/tools/skill/skill.js +86 -0
- package/src/tools/skill/skill.ts +94 -0
- package/src/tools/snip/snip.js +67 -0
- package/src/tools/snip/snip.ts +80 -0
- package/src/tools/tasks/backgroundTaskService.js +79 -28
- package/src/tools/tasks/backgroundTaskService.ts +165 -49
- package/src/tools/tasks/taskManagement.js +163 -0
- package/src/tools/tasks/taskManagement.ts +180 -0
- package/src/tools/tasks/taskOutput.js +64 -14
- package/src/tools/tasks/taskOutput.ts +102 -23
- package/src/tools/tasks/taskStop.js +48 -8
- package/src/tools/tasks/taskStop.ts +69 -9
- package/src/tools/tasks/taskStore.js +46 -0
- package/src/tools/tasks/taskStore.ts +77 -0
- package/src/tools/terminal/terminalCapture.js +167 -0
- package/src/tools/terminal/terminalCapture.ts +179 -0
- package/src/tools/trash/trash.js +233 -0
- package/src/tools/trash/trash.ts +283 -0
- package/src/tools/webbrowser/browserControl.js +5 -1
- package/src/tools/webbrowser/browserControl.ts +7 -1
- package/src/tools/webbrowser/webbrowser.js +8 -4
- package/src/tools/webbrowser/webbrowser.ts +8 -4
- package/src/tools/worktree/sessionWorktree.js +128 -0
- package/src/tools/worktree/sessionWorktree.ts +158 -0
- package/src/tools/write/write.js +6 -2
- package/src/tools/write/write.ts +6 -2
- package/src/types.ts +22 -4
- package/src/ui/hooks/useChat.js +31 -11
- package/src/ui/hooks/useChat.ts +31 -13
- package/src/web/protocol.ts +78 -3
- package/workflows/adaptive-research.yaml +5 -5
- package/workflows/book-review-short.yaml +5 -5
- package/workflows/e2e-write-greet.yaml +2 -2
- package/workflows/illustrate.yaml +12 -12
- package/workflows/plan.yaml +18 -18
- package/workflows/ppt.yaml +201 -201
- package/workflows/ralph-loop.yaml +3 -1
- package/workflows/research.md +449 -0
- package/workflows/research.yaml +873 -0
- package/workflows/webppt.yaml +47 -47
- package/workflows/word.yaml +80 -80
- package/workflows/youtube-shorts.yaml +25 -25
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { toToolParameters } from '../../utils/zodToJson.js';
|
|
3
|
+
import { assertNoDependencyCycle, effectiveTaskStatus, listManagedTasks, mutateManagedTasks, newTaskId, } from './taskStore.js';
|
|
4
|
+
const id = z.string().regex(/^task_[0-9a-f-]{36}$/i);
|
|
5
|
+
const createSchema = z.object({
|
|
6
|
+
title: z.string().min(1).max(200),
|
|
7
|
+
description: z.string().max(5000).optional(),
|
|
8
|
+
activeForm: z.string().max(200).optional(),
|
|
9
|
+
owner: z.string().max(120).optional(),
|
|
10
|
+
blockedBy: z.array(id).default([]).optional(),
|
|
11
|
+
metadata: z.record(z.unknown()).optional(),
|
|
12
|
+
});
|
|
13
|
+
const getSchema = z.object({ task_id: id });
|
|
14
|
+
const updateSchema = z.object({
|
|
15
|
+
task_id: id,
|
|
16
|
+
title: z.string().min(1).max(200).optional(),
|
|
17
|
+
description: z.string().max(5000).optional(),
|
|
18
|
+
activeForm: z.string().max(200).optional(),
|
|
19
|
+
owner: z.string().max(120).optional(),
|
|
20
|
+
blockedBy: z.array(id).optional(),
|
|
21
|
+
status: z.enum(['pending', 'in_progress', 'completed', 'deleted']).optional(),
|
|
22
|
+
metadata: z.record(z.unknown()).optional(),
|
|
23
|
+
});
|
|
24
|
+
const listSchema = z.object({
|
|
25
|
+
status: z.enum(['pending', 'in_progress', 'completed', 'blocked']).optional(),
|
|
26
|
+
include_completed: z.boolean().default(false).optional(),
|
|
27
|
+
});
|
|
28
|
+
function render(task, all) {
|
|
29
|
+
return `${task.id} [${effectiveTaskStatus(task, all)}] ${task.title}${task.blockedBy.length ? ` (blockedBy: ${task.blockedBy.join(', ')})` : ''}`;
|
|
30
|
+
}
|
|
31
|
+
export const taskCreateTool = {
|
|
32
|
+
name: 'TaskCreate',
|
|
33
|
+
description: 'Creates a durable structured task in the current workspace/session. Use for multi-step work that must survive context compression or another user turn.',
|
|
34
|
+
inputSchema: createSchema,
|
|
35
|
+
parameters: toToolParameters(createSchema),
|
|
36
|
+
isReadOnly: false,
|
|
37
|
+
isConcurrencySafe: false,
|
|
38
|
+
call: async () => ({
|
|
39
|
+
ok: false,
|
|
40
|
+
error: 'TaskCreate requires session context',
|
|
41
|
+
}),
|
|
42
|
+
callWithContext: async (input, _s, ctx) => {
|
|
43
|
+
const now = Date.now();
|
|
44
|
+
const task = {
|
|
45
|
+
id: newTaskId(),
|
|
46
|
+
title: input.title,
|
|
47
|
+
description: input.description,
|
|
48
|
+
activeForm: input.activeForm,
|
|
49
|
+
owner: input.owner,
|
|
50
|
+
status: 'pending',
|
|
51
|
+
blockedBy: input.blockedBy ?? [],
|
|
52
|
+
metadata: input.metadata,
|
|
53
|
+
createdAt: now,
|
|
54
|
+
updatedAt: now,
|
|
55
|
+
};
|
|
56
|
+
try {
|
|
57
|
+
const tasks = await mutateManagedTasks(ctx, (items) => {
|
|
58
|
+
const next = [...items, task];
|
|
59
|
+
assertNoDependencyCycle(next);
|
|
60
|
+
return next;
|
|
61
|
+
});
|
|
62
|
+
return { ok: true, content: `已创建 ${render(task, tasks)}` };
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
return { ok: false, error: e.message };
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
export const taskGetTool = {
|
|
70
|
+
name: 'TaskGet',
|
|
71
|
+
description: 'Reads one durable task and its dependency state.',
|
|
72
|
+
inputSchema: getSchema,
|
|
73
|
+
parameters: toToolParameters(getSchema),
|
|
74
|
+
isReadOnly: true,
|
|
75
|
+
isConcurrencySafe: true,
|
|
76
|
+
call: async () => ({ ok: false, error: 'TaskGet requires session context' }),
|
|
77
|
+
callWithContext: async (input, _s, ctx) => {
|
|
78
|
+
const tasks = await listManagedTasks(ctx);
|
|
79
|
+
const task = tasks.find((t) => t.id === input.task_id);
|
|
80
|
+
return task
|
|
81
|
+
? {
|
|
82
|
+
ok: true,
|
|
83
|
+
content: JSON.stringify({ ...task, effectiveStatus: effectiveTaskStatus(task, tasks) }, null, 2),
|
|
84
|
+
}
|
|
85
|
+
: { ok: false, error: `任务不存在:${input.task_id}` };
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
export const taskUpdateTool = {
|
|
89
|
+
name: 'TaskUpdate',
|
|
90
|
+
description: 'Updates task status, dependencies or details. status=deleted removes only the task record and never project files.',
|
|
91
|
+
inputSchema: updateSchema,
|
|
92
|
+
parameters: toToolParameters(updateSchema),
|
|
93
|
+
isReadOnly: false,
|
|
94
|
+
isConcurrencySafe: false,
|
|
95
|
+
call: async () => ({
|
|
96
|
+
ok: false,
|
|
97
|
+
error: 'TaskUpdate requires session context',
|
|
98
|
+
}),
|
|
99
|
+
callWithContext: async (input, _s, ctx) => {
|
|
100
|
+
try {
|
|
101
|
+
let found = false;
|
|
102
|
+
const tasks = await mutateManagedTasks(ctx, (items) => {
|
|
103
|
+
const target = items.find((t) => t.id === input.task_id);
|
|
104
|
+
if (!target)
|
|
105
|
+
return items;
|
|
106
|
+
found = true;
|
|
107
|
+
if (input.status === 'deleted') {
|
|
108
|
+
if (items.some((t) => t.blockedBy.includes(target.id)))
|
|
109
|
+
throw new Error('仍有任务依赖它,不能删除');
|
|
110
|
+
return items.filter((t) => t.id !== target.id);
|
|
111
|
+
}
|
|
112
|
+
Object.assign(target, {
|
|
113
|
+
...(input.title !== undefined && { title: input.title }),
|
|
114
|
+
...(input.description !== undefined && {
|
|
115
|
+
description: input.description,
|
|
116
|
+
}),
|
|
117
|
+
...(input.activeForm !== undefined && {
|
|
118
|
+
activeForm: input.activeForm,
|
|
119
|
+
}),
|
|
120
|
+
...(input.owner !== undefined && { owner: input.owner }),
|
|
121
|
+
...(input.blockedBy !== undefined && { blockedBy: input.blockedBy }),
|
|
122
|
+
...(input.status !== undefined && { status: input.status }),
|
|
123
|
+
...(input.metadata !== undefined && { metadata: input.metadata }),
|
|
124
|
+
updatedAt: Date.now(),
|
|
125
|
+
});
|
|
126
|
+
assertNoDependencyCycle(items);
|
|
127
|
+
return items;
|
|
128
|
+
});
|
|
129
|
+
if (!found)
|
|
130
|
+
return { ok: false, error: `任务不存在:${input.task_id}` };
|
|
131
|
+
const task = tasks.find((t) => t.id === input.task_id);
|
|
132
|
+
return {
|
|
133
|
+
ok: true,
|
|
134
|
+
content: task
|
|
135
|
+
? `已更新 ${render(task, tasks)}`
|
|
136
|
+
: `已删除任务记录 ${input.task_id}`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
catch (e) {
|
|
140
|
+
return { ok: false, error: e.message };
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
export const taskListTool = {
|
|
145
|
+
name: 'TaskList',
|
|
146
|
+
description: 'Lists active durable tasks and dependency blockers. Completed tasks are hidden by default.',
|
|
147
|
+
inputSchema: listSchema,
|
|
148
|
+
parameters: toToolParameters(listSchema),
|
|
149
|
+
isReadOnly: true,
|
|
150
|
+
isConcurrencySafe: true,
|
|
151
|
+
call: async () => ({ ok: false, error: 'TaskList requires session context' }),
|
|
152
|
+
callWithContext: async (input, _s, ctx) => {
|
|
153
|
+
const tasks = await listManagedTasks(ctx);
|
|
154
|
+
const selected = tasks.filter((t) => (input.include_completed || t.status !== 'completed') &&
|
|
155
|
+
(!input.status || effectiveTaskStatus(t, tasks) === input.status));
|
|
156
|
+
return {
|
|
157
|
+
ok: true,
|
|
158
|
+
content: selected.length
|
|
159
|
+
? selected.map((t) => render(t, tasks)).join('\n')
|
|
160
|
+
: '(no matching tasks)',
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { Tool, ToolCallContext, ToolResult } from '../../types.js';
|
|
3
|
+
import { toToolParameters } from '../../utils/zodToJson.js';
|
|
4
|
+
import {
|
|
5
|
+
assertNoDependencyCycle,
|
|
6
|
+
effectiveTaskStatus,
|
|
7
|
+
listManagedTasks,
|
|
8
|
+
mutateManagedTasks,
|
|
9
|
+
newTaskId,
|
|
10
|
+
type ManagedTask,
|
|
11
|
+
type ManagedTaskStatus,
|
|
12
|
+
} from './taskStore.js';
|
|
13
|
+
|
|
14
|
+
const id = z.string().regex(/^task_[0-9a-f-]{36}$/i);
|
|
15
|
+
const createSchema = z.object({
|
|
16
|
+
title: z.string().min(1).max(200),
|
|
17
|
+
description: z.string().max(5000).optional(),
|
|
18
|
+
activeForm: z.string().max(200).optional(),
|
|
19
|
+
owner: z.string().max(120).optional(),
|
|
20
|
+
blockedBy: z.array(id).default([]).optional(),
|
|
21
|
+
metadata: z.record(z.unknown()).optional(),
|
|
22
|
+
});
|
|
23
|
+
const getSchema = z.object({ task_id: id });
|
|
24
|
+
const updateSchema = z.object({
|
|
25
|
+
task_id: id,
|
|
26
|
+
title: z.string().min(1).max(200).optional(),
|
|
27
|
+
description: z.string().max(5000).optional(),
|
|
28
|
+
activeForm: z.string().max(200).optional(),
|
|
29
|
+
owner: z.string().max(120).optional(),
|
|
30
|
+
blockedBy: z.array(id).optional(),
|
|
31
|
+
status: z.enum(['pending', 'in_progress', 'completed', 'deleted']).optional(),
|
|
32
|
+
metadata: z.record(z.unknown()).optional(),
|
|
33
|
+
});
|
|
34
|
+
const listSchema = z.object({
|
|
35
|
+
status: z.enum(['pending', 'in_progress', 'completed', 'blocked']).optional(),
|
|
36
|
+
include_completed: z.boolean().default(false).optional(),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
function render(task: ManagedTask, all: ManagedTask[]): string {
|
|
40
|
+
return `${task.id} [${effectiveTaskStatus(task, all)}] ${task.title}${task.blockedBy.length ? ` (blockedBy: ${task.blockedBy.join(', ')})` : ''}`;
|
|
41
|
+
}
|
|
42
|
+
export const taskCreateTool: Tool<z.infer<typeof createSchema>> = {
|
|
43
|
+
name: 'TaskCreate',
|
|
44
|
+
description:
|
|
45
|
+
'Creates a durable structured task in the current workspace/session. Use for multi-step work that must survive context compression or another user turn.',
|
|
46
|
+
inputSchema: createSchema,
|
|
47
|
+
parameters: toToolParameters(createSchema),
|
|
48
|
+
isReadOnly: false,
|
|
49
|
+
isConcurrencySafe: false,
|
|
50
|
+
call: async () => ({
|
|
51
|
+
ok: false,
|
|
52
|
+
error: 'TaskCreate requires session context',
|
|
53
|
+
}),
|
|
54
|
+
callWithContext: async (input, _s, ctx) => {
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
const task = {
|
|
57
|
+
id: newTaskId(),
|
|
58
|
+
title: input.title,
|
|
59
|
+
description: input.description,
|
|
60
|
+
activeForm: input.activeForm,
|
|
61
|
+
owner: input.owner,
|
|
62
|
+
status: 'pending' as ManagedTaskStatus,
|
|
63
|
+
blockedBy: input.blockedBy ?? [],
|
|
64
|
+
metadata: input.metadata,
|
|
65
|
+
createdAt: now,
|
|
66
|
+
updatedAt: now,
|
|
67
|
+
};
|
|
68
|
+
try {
|
|
69
|
+
const tasks = await mutateManagedTasks(ctx, (items) => {
|
|
70
|
+
const next = [...items, task];
|
|
71
|
+
assertNoDependencyCycle(next);
|
|
72
|
+
return next;
|
|
73
|
+
});
|
|
74
|
+
return { ok: true, content: `已创建 ${render(task, tasks)}` };
|
|
75
|
+
} catch (e) {
|
|
76
|
+
return { ok: false, error: (e as Error).message };
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
export const taskGetTool: Tool<z.infer<typeof getSchema>> = {
|
|
81
|
+
name: 'TaskGet',
|
|
82
|
+
description: 'Reads one durable task and its dependency state.',
|
|
83
|
+
inputSchema: getSchema,
|
|
84
|
+
parameters: toToolParameters(getSchema),
|
|
85
|
+
isReadOnly: true,
|
|
86
|
+
isConcurrencySafe: true,
|
|
87
|
+
call: async () => ({ ok: false, error: 'TaskGet requires session context' }),
|
|
88
|
+
callWithContext: async (input, _s, ctx) => {
|
|
89
|
+
const tasks = await listManagedTasks(ctx);
|
|
90
|
+
const task = tasks.find((t) => t.id === input.task_id);
|
|
91
|
+
return task
|
|
92
|
+
? {
|
|
93
|
+
ok: true,
|
|
94
|
+
content: JSON.stringify(
|
|
95
|
+
{ ...task, effectiveStatus: effectiveTaskStatus(task, tasks) },
|
|
96
|
+
null,
|
|
97
|
+
2,
|
|
98
|
+
),
|
|
99
|
+
}
|
|
100
|
+
: { ok: false, error: `任务不存在:${input.task_id}` };
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
export const taskUpdateTool: Tool<z.infer<typeof updateSchema>> = {
|
|
104
|
+
name: 'TaskUpdate',
|
|
105
|
+
description:
|
|
106
|
+
'Updates task status, dependencies or details. status=deleted removes only the task record and never project files.',
|
|
107
|
+
inputSchema: updateSchema,
|
|
108
|
+
parameters: toToolParameters(updateSchema),
|
|
109
|
+
isReadOnly: false,
|
|
110
|
+
isConcurrencySafe: false,
|
|
111
|
+
call: async () => ({
|
|
112
|
+
ok: false,
|
|
113
|
+
error: 'TaskUpdate requires session context',
|
|
114
|
+
}),
|
|
115
|
+
callWithContext: async (input, _s, ctx) => {
|
|
116
|
+
try {
|
|
117
|
+
let found = false;
|
|
118
|
+
const tasks = await mutateManagedTasks(ctx, (items) => {
|
|
119
|
+
const target = items.find((t) => t.id === input.task_id);
|
|
120
|
+
if (!target) return items;
|
|
121
|
+
found = true;
|
|
122
|
+
if (input.status === 'deleted') {
|
|
123
|
+
if (items.some((t) => t.blockedBy.includes(target.id)))
|
|
124
|
+
throw new Error('仍有任务依赖它,不能删除');
|
|
125
|
+
return items.filter((t) => t.id !== target.id);
|
|
126
|
+
}
|
|
127
|
+
Object.assign(target, {
|
|
128
|
+
...(input.title !== undefined && { title: input.title }),
|
|
129
|
+
...(input.description !== undefined && {
|
|
130
|
+
description: input.description,
|
|
131
|
+
}),
|
|
132
|
+
...(input.activeForm !== undefined && {
|
|
133
|
+
activeForm: input.activeForm,
|
|
134
|
+
}),
|
|
135
|
+
...(input.owner !== undefined && { owner: input.owner }),
|
|
136
|
+
...(input.blockedBy !== undefined && { blockedBy: input.blockedBy }),
|
|
137
|
+
...(input.status !== undefined && { status: input.status }),
|
|
138
|
+
...(input.metadata !== undefined && { metadata: input.metadata }),
|
|
139
|
+
updatedAt: Date.now(),
|
|
140
|
+
});
|
|
141
|
+
assertNoDependencyCycle(items);
|
|
142
|
+
return items;
|
|
143
|
+
});
|
|
144
|
+
if (!found) return { ok: false, error: `任务不存在:${input.task_id}` };
|
|
145
|
+
const task = tasks.find((t) => t.id === input.task_id);
|
|
146
|
+
return {
|
|
147
|
+
ok: true,
|
|
148
|
+
content: task
|
|
149
|
+
? `已更新 ${render(task, tasks)}`
|
|
150
|
+
: `已删除任务记录 ${input.task_id}`,
|
|
151
|
+
};
|
|
152
|
+
} catch (e) {
|
|
153
|
+
return { ok: false, error: (e as Error).message };
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
export const taskListTool: Tool<z.infer<typeof listSchema>> = {
|
|
158
|
+
name: 'TaskList',
|
|
159
|
+
description:
|
|
160
|
+
'Lists active durable tasks and dependency blockers. Completed tasks are hidden by default.',
|
|
161
|
+
inputSchema: listSchema,
|
|
162
|
+
parameters: toToolParameters(listSchema),
|
|
163
|
+
isReadOnly: true,
|
|
164
|
+
isConcurrencySafe: true,
|
|
165
|
+
call: async () => ({ ok: false, error: 'TaskList requires session context' }),
|
|
166
|
+
callWithContext: async (input, _s, ctx) => {
|
|
167
|
+
const tasks = await listManagedTasks(ctx);
|
|
168
|
+
const selected = tasks.filter(
|
|
169
|
+
(t) =>
|
|
170
|
+
(input.include_completed || t.status !== 'completed') &&
|
|
171
|
+
(!input.status || effectiveTaskStatus(t, tasks) === input.status),
|
|
172
|
+
);
|
|
173
|
+
return {
|
|
174
|
+
ok: true,
|
|
175
|
+
content: selected.length
|
|
176
|
+
? selected.map((t) => render(t, tasks)).join('\n')
|
|
177
|
+
: '(no matching tasks)',
|
|
178
|
+
};
|
|
179
|
+
},
|
|
180
|
+
};
|
|
@@ -1,28 +1,78 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { toToolParameters } from '../../utils/zodToJson.js';
|
|
3
3
|
import { DEFAULT_MAX_RESULT_SIZE_CHARS } from '../types.js';
|
|
4
|
-
import { getBackgroundTask, readTaskLog } from './backgroundTaskService.js';
|
|
4
|
+
import { getBackgroundTask, readTaskLog, } from './backgroundTaskService.js';
|
|
5
5
|
import { processExecutionMetadata } from './processEvidence.js';
|
|
6
|
+
import { getLiveAgentTask, loadAgentRecord, } from '../agent/agentTaskService.js';
|
|
6
7
|
const inputSchema = z.object({
|
|
7
|
-
task_id: z.string().
|
|
8
|
-
offset: z
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
task_id: z.string().regex(/^(?:proc_|agent_)?[0-9a-f-]{36}$/i),
|
|
9
|
+
offset: z
|
|
10
|
+
.number()
|
|
11
|
+
.int()
|
|
12
|
+
.nonnegative()
|
|
13
|
+
.optional()
|
|
14
|
+
.describe('从该字节偏移读取;使用上次返回的 next_offset 增量读取'),
|
|
15
|
+
tail_chars: z
|
|
16
|
+
.number()
|
|
17
|
+
.int()
|
|
18
|
+
.min(1)
|
|
19
|
+
.max(DEFAULT_MAX_RESULT_SIZE_CHARS)
|
|
20
|
+
.optional(),
|
|
21
|
+
block: z
|
|
22
|
+
.boolean()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe('任务仍运行时,是否等待新输出或任务结束'),
|
|
25
|
+
timeout: z
|
|
26
|
+
.number()
|
|
27
|
+
.int()
|
|
28
|
+
.min(1)
|
|
29
|
+
.max(120_000)
|
|
30
|
+
.optional()
|
|
31
|
+
.describe('block=true 时的最长等待毫秒数'),
|
|
12
32
|
});
|
|
13
33
|
async function waitForChange(task, offset, timeout) {
|
|
14
|
-
if (task.status !== 'running' ||
|
|
34
|
+
if (task.status !== 'running' ||
|
|
35
|
+
readTaskLog(task, offset, 1).totalBytes > offset)
|
|
15
36
|
return;
|
|
16
37
|
await new Promise((resolvePromise) => {
|
|
17
|
-
const done = () => {
|
|
38
|
+
const done = () => {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
task.events.off('change', done);
|
|
41
|
+
resolvePromise();
|
|
42
|
+
};
|
|
18
43
|
const timer = setTimeout(done, timeout);
|
|
19
44
|
timer.unref?.();
|
|
20
45
|
task.events.once('change', done);
|
|
21
|
-
if (task.status !== 'running' ||
|
|
46
|
+
if (task.status !== 'running' ||
|
|
47
|
+
readTaskLog(task, offset, 1).totalBytes > offset)
|
|
22
48
|
done();
|
|
23
49
|
});
|
|
24
50
|
}
|
|
25
|
-
async function call(input, state) {
|
|
51
|
+
async function call(input, state, context) {
|
|
52
|
+
if (input.task_id.startsWith('agent_') && context) {
|
|
53
|
+
const live = getLiveAgentTask(context.sessionState?.agentTasks, input.task_id);
|
|
54
|
+
const record = live ?? (await loadAgentRecord(context, input.task_id));
|
|
55
|
+
if (!record)
|
|
56
|
+
return { ok: false, error: `当前会话中不存在 Agent ${input.task_id}` };
|
|
57
|
+
if (input.block && live?.status === 'running')
|
|
58
|
+
await Promise.race([
|
|
59
|
+
live.completion,
|
|
60
|
+
new Promise((r) => setTimeout(r, input.timeout ?? 30_000)),
|
|
61
|
+
]);
|
|
62
|
+
const current = getLiveAgentTask(context.sessionState?.agentTasks, input.task_id) ??
|
|
63
|
+
(await loadAgentRecord(context, input.task_id)) ??
|
|
64
|
+
record;
|
|
65
|
+
const body = current.result
|
|
66
|
+
? current.result.ok
|
|
67
|
+
? current.result.content
|
|
68
|
+
: current.result.error
|
|
69
|
+
: (current.progress ?? '<agent is still running>');
|
|
70
|
+
return {
|
|
71
|
+
ok: true,
|
|
72
|
+
content: `${body}\n\n[agent ${current.id}: ${current.status}]`,
|
|
73
|
+
metadata: current.result?.metadata,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
26
76
|
const task = getBackgroundTask(input.task_id, state);
|
|
27
77
|
if (!task)
|
|
28
78
|
return { ok: false, error: `当前会话中不存在后台任务 ${input.task_id}` };
|
|
@@ -33,7 +83,7 @@ async function call(input, state) {
|
|
|
33
83
|
const text = output.text || '<no new output>';
|
|
34
84
|
return {
|
|
35
85
|
ok: true,
|
|
36
|
-
content: `${text}\n\n[task ${task.id}: ${task.status}; exit=${task.exitCode ?? 'pending'}; next_offset=${output.nextOffset}; total_bytes=${output.totalBytes}]`,
|
|
86
|
+
content: `${text}\n\n[task ${task.id}: ${task.status}; exit=${task.exitCode ?? 'pending'}; next_offset=${output.nextOffset}; total_bytes=${output.totalBytes}${task.outputSha256 ? `; sha256=${task.outputSha256}; complete=${task.complete}` : ''}; log=${task.logPath}]`,
|
|
37
87
|
metadata: processExecutionMetadata({
|
|
38
88
|
shell: task.shell,
|
|
39
89
|
mode: 'background',
|
|
@@ -52,12 +102,12 @@ async function call(input, state) {
|
|
|
52
102
|
}
|
|
53
103
|
export const taskOutputTool = {
|
|
54
104
|
name: 'TaskOutput',
|
|
55
|
-
description: 'Reads incremental output from a Bash or
|
|
105
|
+
description: 'Reads incremental output from a Bash/PowerShell process or progress/results from an Agent task in the current session. Use block=true to wait efficiently instead of polling repeatedly.',
|
|
56
106
|
inputSchema,
|
|
57
107
|
parameters: toToolParameters(inputSchema),
|
|
58
108
|
isReadOnly: true,
|
|
59
109
|
isConcurrencySafe: true,
|
|
60
110
|
maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
|
|
61
|
-
call: (input) => call(input),
|
|
62
|
-
callWithContext: (input, _signal, context) => call(input, context.sessionState?.backgroundTasks),
|
|
111
|
+
call: (input) => call(input, undefined),
|
|
112
|
+
callWithContext: (input, _signal, context) => call(input, context.sessionState?.backgroundTasks, context),
|
|
63
113
|
};
|
|
@@ -3,51 +3,128 @@ import { z } from 'zod';
|
|
|
3
3
|
import type { Tool, ToolCallContext, ToolResult } from '../../types.js';
|
|
4
4
|
import { toToolParameters } from '../../utils/zodToJson.js';
|
|
5
5
|
import { DEFAULT_MAX_RESULT_SIZE_CHARS } from '../types.js';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
getBackgroundTask,
|
|
8
|
+
readTaskLog,
|
|
9
|
+
type BackgroundTaskState,
|
|
10
|
+
} from './backgroundTaskService.js';
|
|
7
11
|
import { processExecutionMetadata } from './processEvidence.js';
|
|
12
|
+
import {
|
|
13
|
+
getLiveAgentTask,
|
|
14
|
+
loadAgentRecord,
|
|
15
|
+
} from '../agent/agentTaskService.js';
|
|
8
16
|
|
|
9
17
|
const inputSchema = z.object({
|
|
10
|
-
task_id: z.string().
|
|
11
|
-
offset: z
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
task_id: z.string().regex(/^(?:proc_|agent_)?[0-9a-f-]{36}$/i),
|
|
19
|
+
offset: z
|
|
20
|
+
.number()
|
|
21
|
+
.int()
|
|
22
|
+
.nonnegative()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe('从该字节偏移读取;使用上次返回的 next_offset 增量读取'),
|
|
25
|
+
tail_chars: z
|
|
26
|
+
.number()
|
|
27
|
+
.int()
|
|
28
|
+
.min(1)
|
|
29
|
+
.max(DEFAULT_MAX_RESULT_SIZE_CHARS)
|
|
30
|
+
.optional(),
|
|
31
|
+
block: z
|
|
32
|
+
.boolean()
|
|
33
|
+
.optional()
|
|
34
|
+
.describe('任务仍运行时,是否等待新输出或任务结束'),
|
|
35
|
+
timeout: z
|
|
36
|
+
.number()
|
|
37
|
+
.int()
|
|
38
|
+
.min(1)
|
|
39
|
+
.max(120_000)
|
|
40
|
+
.optional()
|
|
41
|
+
.describe('block=true 时的最长等待毫秒数'),
|
|
15
42
|
});
|
|
16
43
|
type TaskOutputInput = z.infer<typeof inputSchema>;
|
|
17
44
|
|
|
18
|
-
async function waitForChange(
|
|
19
|
-
|
|
45
|
+
async function waitForChange(
|
|
46
|
+
task: NonNullable<ReturnType<typeof getBackgroundTask>>,
|
|
47
|
+
offset: number,
|
|
48
|
+
timeout: number,
|
|
49
|
+
): Promise<void> {
|
|
50
|
+
if (
|
|
51
|
+
task.status !== 'running' ||
|
|
52
|
+
readTaskLog(task, offset, 1).totalBytes > offset
|
|
53
|
+
)
|
|
54
|
+
return;
|
|
20
55
|
await new Promise<void>((resolvePromise) => {
|
|
21
|
-
const done = () => {
|
|
56
|
+
const done = () => {
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
task.events.off('change', done);
|
|
59
|
+
resolvePromise();
|
|
60
|
+
};
|
|
22
61
|
const timer = setTimeout(done, timeout);
|
|
23
62
|
timer.unref?.();
|
|
24
63
|
task.events.once('change', done);
|
|
25
|
-
if (
|
|
64
|
+
if (
|
|
65
|
+
task.status !== 'running' ||
|
|
66
|
+
readTaskLog(task, offset, 1).totalBytes > offset
|
|
67
|
+
)
|
|
68
|
+
done();
|
|
26
69
|
});
|
|
27
70
|
}
|
|
28
71
|
|
|
29
|
-
async function call(
|
|
72
|
+
async function call(
|
|
73
|
+
input: TaskOutputInput,
|
|
74
|
+
state: BackgroundTaskState | undefined,
|
|
75
|
+
context?: ToolCallContext,
|
|
76
|
+
): Promise<ToolResult> {
|
|
77
|
+
if (input.task_id.startsWith('agent_') && context) {
|
|
78
|
+
const live = getLiveAgentTask(
|
|
79
|
+
context.sessionState?.agentTasks,
|
|
80
|
+
input.task_id,
|
|
81
|
+
);
|
|
82
|
+
const record = live ?? (await loadAgentRecord(context, input.task_id));
|
|
83
|
+
if (!record)
|
|
84
|
+
return { ok: false, error: `当前会话中不存在 Agent ${input.task_id}` };
|
|
85
|
+
if (input.block && live?.status === 'running')
|
|
86
|
+
await Promise.race([
|
|
87
|
+
live.completion,
|
|
88
|
+
new Promise<void>((r) => setTimeout(r, input.timeout ?? 30_000)),
|
|
89
|
+
]);
|
|
90
|
+
const current =
|
|
91
|
+
getLiveAgentTask(context.sessionState?.agentTasks, input.task_id) ??
|
|
92
|
+
(await loadAgentRecord(context, input.task_id)) ??
|
|
93
|
+
record;
|
|
94
|
+
const body = current.result
|
|
95
|
+
? current.result.ok
|
|
96
|
+
? current.result.content
|
|
97
|
+
: current.result.error
|
|
98
|
+
: (current.progress ?? '<agent is still running>');
|
|
99
|
+
return {
|
|
100
|
+
ok: true,
|
|
101
|
+
content: `${body}\n\n[agent ${current.id}: ${current.status}]`,
|
|
102
|
+
metadata: current.result?.metadata,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
30
105
|
const task = getBackgroundTask(input.task_id, state);
|
|
31
|
-
if (!task)
|
|
106
|
+
if (!task)
|
|
107
|
+
return { ok: false, error: `当前会话中不存在后台任务 ${input.task_id}` };
|
|
32
108
|
const offset = input.offset ?? 0;
|
|
33
109
|
if (input.block) await waitForChange(task, offset, input.timeout ?? 30_000);
|
|
34
110
|
const output = readTaskLog(task, offset, input.tail_chars ?? 20_000);
|
|
35
111
|
const text = output.text || '<no new output>';
|
|
36
112
|
return {
|
|
37
113
|
ok: true,
|
|
38
|
-
content: `${text}\n\n[task ${task.id}: ${task.status}; exit=${task.exitCode ?? 'pending'}; next_offset=${output.nextOffset}; total_bytes=${output.totalBytes}]`,
|
|
114
|
+
content: `${text}\n\n[task ${task.id}: ${task.status}; exit=${task.exitCode ?? 'pending'}; next_offset=${output.nextOffset}; total_bytes=${output.totalBytes}${task.outputSha256 ? `; sha256=${task.outputSha256}; complete=${task.complete}` : ''}; log=${task.logPath}]`,
|
|
39
115
|
metadata: processExecutionMetadata({
|
|
40
116
|
shell: task.shell,
|
|
41
117
|
mode: 'background',
|
|
42
118
|
command: task.command,
|
|
43
119
|
taskId: task.id,
|
|
44
|
-
status:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
120
|
+
status:
|
|
121
|
+
task.status === 'completed'
|
|
122
|
+
? 'completed'
|
|
123
|
+
: task.status === 'failed'
|
|
124
|
+
? 'failed'
|
|
125
|
+
: task.status === 'stopped'
|
|
126
|
+
? 'stopped'
|
|
127
|
+
: 'running',
|
|
51
128
|
exitCode: task.exitCode,
|
|
52
129
|
}),
|
|
53
130
|
};
|
|
@@ -55,12 +132,14 @@ async function call(input: TaskOutputInput, state?: BackgroundTaskState): Promis
|
|
|
55
132
|
|
|
56
133
|
export const taskOutputTool: Tool<TaskOutputInput> = {
|
|
57
134
|
name: 'TaskOutput',
|
|
58
|
-
description:
|
|
135
|
+
description:
|
|
136
|
+
'Reads incremental output from a Bash/PowerShell process or progress/results from an Agent task in the current session. Use block=true to wait efficiently instead of polling repeatedly.',
|
|
59
137
|
inputSchema,
|
|
60
138
|
parameters: toToolParameters(inputSchema),
|
|
61
139
|
isReadOnly: true,
|
|
62
140
|
isConcurrencySafe: true,
|
|
63
141
|
maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
|
|
64
|
-
call: (input) => call(input),
|
|
65
|
-
callWithContext: (input, _signal, context: ToolCallContext) =>
|
|
142
|
+
call: (input) => call(input, undefined),
|
|
143
|
+
callWithContext: (input, _signal, context: ToolCallContext) =>
|
|
144
|
+
call(input, context.sessionState?.backgroundTasks, context),
|
|
66
145
|
};
|