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.
Files changed (181) hide show
  1. package/package.json +5 -1
  2. package/plugins/workflow-runner/plugin.js +27 -15
  3. package/plugins/workflow-runner/plugin.ts +34 -20
  4. package/plugins/workflow-runner/src/agentCommon.js +13 -12
  5. package/plugins/workflow-runner/src/agentCommon.ts +22 -13
  6. package/plugins/workflow-runner/src/expressions.js +29 -8
  7. package/plugins/workflow-runner/src/expressions.ts +28 -10
  8. package/plugins/workflow-runner/src/index.js +50 -20
  9. package/plugins/workflow-runner/src/index.ts +75 -31
  10. package/plugins/workflow-runner/src/multimodal.js +2 -1
  11. package/plugins/workflow-runner/src/multimodal.ts +2 -1
  12. package/plugins/workflow-runner/src/recovery.js +9 -2
  13. package/plugins/workflow-runner/src/recovery.ts +9 -2
  14. package/plugins/workflow-runner/src/runner.js +31 -5
  15. package/plugins/workflow-runner/src/runner.ts +33 -5
  16. package/plugins/workflow-runner/src/stepExecutors/assert.js +2 -1
  17. package/plugins/workflow-runner/src/stepExecutors/assert.ts +3 -1
  18. package/plugins/workflow-runner/src/stepExecutors/skill.js +5 -6
  19. package/plugins/workflow-runner/src/stepExecutors/skill.ts +12 -7
  20. package/plugins/workflow-runner/src/stepExecutors/tool.js +91 -61
  21. package/plugins/workflow-runner/src/stepExecutors/tool.ts +111 -71
  22. package/plugins/workflow-runner/src/stepExecutors/verify.js +6 -3
  23. package/plugins/workflow-runner/src/stepExecutors/verify.ts +7 -3
  24. package/plugins/workflow-runner/src/taskIdentity.js +197 -21
  25. package/plugins/workflow-runner/src/taskIdentity.ts +201 -20
  26. package/plugins/workflow-runner/src/toolRuntime.js +48 -0
  27. package/plugins/workflow-runner/src/toolRuntime.ts +64 -0
  28. package/plugins/workflow-runner/src/types.js +113 -7
  29. package/plugins/workflow-runner/src/types.ts +137 -14
  30. package/plugins/workflow-runner/src/workflowState.js +455 -54
  31. package/plugins/workflow-runner/src/workflowState.ts +508 -63
  32. package/plugins/workflow-runner/test/cli.e2e.test.ts +20 -0
  33. package/plugins/workflow-runner/test/context-files.test.ts +47 -16
  34. package/plugins/workflow-runner/test/e2eNewLayout.test.ts +314 -0
  35. package/plugins/workflow-runner/test/engineRobustness.test.ts +9 -1
  36. package/plugins/workflow-runner/test/expressions.test.ts +42 -0
  37. package/plugins/workflow-runner/test/hostCapability.test.ts +9 -3
  38. package/plugins/workflow-runner/test/illustrate.load.test.ts +2 -2
  39. package/plugins/workflow-runner/test/multimodal.test.ts +61 -10
  40. package/plugins/workflow-runner/test/parseWorkflowArgs.test.ts +19 -0
  41. package/plugins/workflow-runner/test/ppt.load.test.ts +15 -15
  42. package/plugins/workflow-runner/test/ralphLoop.e2e.test.ts +3 -2
  43. package/plugins/workflow-runner/test/research.load.test.ts +328 -0
  44. package/plugins/workflow-runner/test/runnerPreflight.test.ts +68 -11
  45. package/plugins/workflow-runner/test/stateLifecycle.test.ts +60 -12
  46. package/plugins/workflow-runner/test/taskArbitration.test.ts +19 -3
  47. package/plugins/workflow-runner/test/taskLifecycle.test.ts +61 -7
  48. package/plugins/workflow-runner/test/toolRuntimeIntegration.test.ts +168 -0
  49. package/plugins/workflow-runner/test/webppt.load.test.ts +4 -4
  50. package/plugins/workflow-runner/test/word.load.test.ts +9 -9
  51. package/skills/skill-creator/scripts/run_eval.py +3 -2
  52. package/src/cli/print.js +36 -4
  53. package/src/cli/print.ts +42 -4
  54. package/src/cli/streamJson.js +19 -4
  55. package/src/cli/streamJson.ts +17 -4
  56. package/src/config/configDefaults.js +25 -0
  57. package/src/config/configDefaults.ts +33 -0
  58. package/src/config/configFile.js +2 -21
  59. package/src/config/configFile.ts +11 -24
  60. package/src/config/configManagement.js +1 -1
  61. package/src/config/configManagement.ts +2 -2
  62. package/src/config/configService.js +24 -0
  63. package/src/config/configService.ts +23 -0
  64. package/src/context/compact.js +4 -0
  65. package/src/context/compact.ts +4 -0
  66. package/src/context/contextBundle.js +38 -13
  67. package/src/context/contextBundle.ts +47 -13
  68. package/src/harness/stallGuard.js +19 -4
  69. package/src/harness/stallGuard.ts +30 -7
  70. package/src/infra/verification.js +25 -18
  71. package/src/infra/verification.ts +50 -18
  72. package/src/loop.js +95 -11
  73. package/src/loop.ts +125 -16
  74. package/src/main.js +8 -1
  75. package/src/main.tsx +7 -1
  76. package/src/memory/constants.js +10 -0
  77. package/src/memory/constants.ts +13 -0
  78. package/src/memory/episodicMemory.js +274 -2
  79. package/src/memory/episodicMemory.ts +306 -1
  80. package/src/memory/fileStore.js +24 -4
  81. package/src/memory/fileStore.ts +22 -2
  82. package/src/memory/memoryService.js +79 -12
  83. package/src/memory/memoryService.ts +108 -9
  84. package/src/memory/repository.js +415 -85
  85. package/src/memory/repository.ts +437 -86
  86. package/src/memory/turnJournal.js +95 -0
  87. package/src/memory/turnJournal.ts +120 -0
  88. package/src/memory/types.ts +16 -0
  89. package/src/plugins/pluginApi.ts +30 -6
  90. package/src/plugins/pluginRunner.js +23 -8
  91. package/src/plugins/pluginRunner.ts +32 -13
  92. package/src/prompts/policyRegistry.js +55 -1
  93. package/src/prompts/policyRegistry.ts +55 -1
  94. package/src/prompts/promptAssembly.js +39 -7
  95. package/src/prompts/promptAssembly.ts +145 -59
  96. package/src/session/runAgentTurn.js +43 -21
  97. package/src/session/runAgentTurn.ts +68 -26
  98. package/src/session/runTurn.js +11 -46
  99. package/src/session/runTurn.ts +12 -46
  100. package/src/session/runtimeStore.js +149 -0
  101. package/src/session/runtimeStore.ts +197 -0
  102. package/src/session/sessionState.js +5 -0
  103. package/src/session/sessionState.ts +14 -0
  104. package/src/tools/advisor/advisor.js +76 -9
  105. package/src/tools/advisor/advisor.ts +79 -9
  106. package/src/tools/agent/agent.js +143 -14
  107. package/src/tools/agent/agent.ts +149 -14
  108. package/src/tools/agent/agentTaskService.js +60 -0
  109. package/src/tools/agent/agentTaskService.ts +113 -0
  110. package/src/tools/agent/agentTypes.js +5 -0
  111. package/src/tools/agent/agentTypes.ts +5 -0
  112. package/src/tools/bash/bash.js +80 -15
  113. package/src/tools/bash/bash.ts +83 -15
  114. package/src/tools/capabilities.js +5 -0
  115. package/src/tools/capabilities.ts +11 -1
  116. package/src/tools/edit/edit.js +7 -4
  117. package/src/tools/edit/edit.ts +7 -4
  118. package/src/tools/edit/multi-edit.js +4 -2
  119. package/src/tools/edit/multi-edit.ts +4 -2
  120. package/src/tools/glob/glob.js +156 -40
  121. package/src/tools/glob/glob.ts +185 -46
  122. package/src/tools/index.js +35 -17
  123. package/src/tools/index.ts +81 -20
  124. package/src/tools/lsp/lsp.js +165 -22
  125. package/src/tools/lsp/lsp.ts +206 -36
  126. package/src/tools/powershell/powershell.js +134 -27
  127. package/src/tools/powershell/powershell.ts +171 -30
  128. package/src/tools/presentresult/presentresult.js +10 -0
  129. package/src/tools/presentresult/presentresult.ts +10 -0
  130. package/src/tools/read/read.js +249 -29
  131. package/src/tools/read/read.ts +396 -59
  132. package/src/tools/shared/diffSummary.js +44 -0
  133. package/src/tools/shared/diffSummary.ts +63 -0
  134. package/src/tools/shared/fileActivity.js +2 -1
  135. package/src/tools/shared/fileActivity.ts +3 -0
  136. package/src/tools/shared/permanentDeleteGuard.js +57 -0
  137. package/src/tools/shared/permanentDeleteGuard.ts +71 -0
  138. package/src/tools/shared/toolOutputStore.js +97 -0
  139. package/src/tools/shared/toolOutputStore.ts +110 -0
  140. package/src/tools/skill/skill.js +86 -0
  141. package/src/tools/skill/skill.ts +94 -0
  142. package/src/tools/snip/snip.js +67 -0
  143. package/src/tools/snip/snip.ts +80 -0
  144. package/src/tools/tasks/backgroundTaskService.js +79 -28
  145. package/src/tools/tasks/backgroundTaskService.ts +165 -49
  146. package/src/tools/tasks/taskManagement.js +163 -0
  147. package/src/tools/tasks/taskManagement.ts +180 -0
  148. package/src/tools/tasks/taskOutput.js +64 -14
  149. package/src/tools/tasks/taskOutput.ts +102 -23
  150. package/src/tools/tasks/taskStop.js +48 -8
  151. package/src/tools/tasks/taskStop.ts +69 -9
  152. package/src/tools/tasks/taskStore.js +46 -0
  153. package/src/tools/tasks/taskStore.ts +77 -0
  154. package/src/tools/terminal/terminalCapture.js +167 -0
  155. package/src/tools/terminal/terminalCapture.ts +179 -0
  156. package/src/tools/trash/trash.js +233 -0
  157. package/src/tools/trash/trash.ts +283 -0
  158. package/src/tools/webbrowser/browserControl.js +5 -1
  159. package/src/tools/webbrowser/browserControl.ts +7 -1
  160. package/src/tools/webbrowser/webbrowser.js +8 -4
  161. package/src/tools/webbrowser/webbrowser.ts +8 -4
  162. package/src/tools/worktree/sessionWorktree.js +128 -0
  163. package/src/tools/worktree/sessionWorktree.ts +158 -0
  164. package/src/tools/write/write.js +6 -2
  165. package/src/tools/write/write.ts +6 -2
  166. package/src/types.ts +22 -4
  167. package/src/ui/hooks/useChat.js +31 -11
  168. package/src/ui/hooks/useChat.ts +31 -13
  169. package/src/web/protocol.ts +78 -3
  170. package/workflows/adaptive-research.yaml +5 -5
  171. package/workflows/book-review-short.yaml +5 -5
  172. package/workflows/e2e-write-greet.yaml +2 -2
  173. package/workflows/illustrate.yaml +12 -12
  174. package/workflows/plan.yaml +18 -18
  175. package/workflows/ppt.yaml +201 -201
  176. package/workflows/ralph-loop.yaml +3 -1
  177. package/workflows/research.md +449 -0
  178. package/workflows/research.yaml +873 -0
  179. package/workflows/webppt.yaml +47 -47
  180. package/workflows/word.yaml +80 -80
  181. 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().uuid(),
8
- offset: z.number().int().nonnegative().optional().describe('从该字节偏移读取;使用上次返回的 next_offset 增量读取'),
9
- tail_chars: z.number().int().min(1).max(DEFAULT_MAX_RESULT_SIZE_CHARS).optional(),
10
- block: z.boolean().optional().describe('任务仍运行时,是否等待新输出或任务结束'),
11
- timeout: z.number().int().min(1).max(120_000).optional().describe('block=true 时的最长等待毫秒数'),
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' || readTaskLog(task, offset, 1).totalBytes > offset)
34
+ if (task.status !== 'running' ||
35
+ readTaskLog(task, offset, 1).totalBytes > offset)
15
36
  return;
16
37
  await new Promise((resolvePromise) => {
17
- const done = () => { clearTimeout(timer); task.events.off('change', done); resolvePromise(); };
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' || readTaskLog(task, offset, 1).totalBytes > offset)
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 PowerShell background task in the current session. Use block=true to wait efficiently for new output or completion instead of polling repeatedly.',
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 { getBackgroundTask, readTaskLog, type BackgroundTaskState } from './backgroundTaskService.js';
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().uuid(),
11
- offset: z.number().int().nonnegative().optional().describe('从该字节偏移读取;使用上次返回的 next_offset 增量读取'),
12
- tail_chars: z.number().int().min(1).max(DEFAULT_MAX_RESULT_SIZE_CHARS).optional(),
13
- block: z.boolean().optional().describe('任务仍运行时,是否等待新输出或任务结束'),
14
- timeout: z.number().int().min(1).max(120_000).optional().describe('block=true 时的最长等待毫秒数'),
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(task: NonNullable<ReturnType<typeof getBackgroundTask>>, offset: number, timeout: number): Promise<void> {
19
- if (task.status !== 'running' || readTaskLog(task, offset, 1).totalBytes > offset) return;
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 = () => { clearTimeout(timer); task.events.off('change', done); resolvePromise(); };
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 (task.status !== 'running' || readTaskLog(task, offset, 1).totalBytes > offset) done();
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(input: TaskOutputInput, state?: BackgroundTaskState): Promise<ToolResult> {
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) return { ok: false, error: `当前会话中不存在后台任务 ${input.task_id}` };
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: task.status === 'completed'
45
- ? 'completed'
46
- : task.status === 'failed'
47
- ? 'failed'
48
- : task.status === 'stopped'
49
- ? 'stopped'
50
- : 'running',
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: 'Reads incremental output from a Bash or PowerShell background task in the current session. Use block=true to wait efficiently for new output or completion instead of polling repeatedly.',
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) => call(input, context.sessionState?.backgroundTasks),
142
+ call: (input) => call(input, undefined),
143
+ callWithContext: (input, _signal, context: ToolCallContext) =>
144
+ call(input, context.sessionState?.backgroundTasks, context),
66
145
  };