minimal-agent 0.11.0 → 0.12.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 (128) hide show
  1. package/package.json +4 -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/index.js +5 -3
  7. package/plugins/workflow-runner/src/index.ts +31 -13
  8. package/plugins/workflow-runner/src/multimodal.js +2 -1
  9. package/plugins/workflow-runner/src/multimodal.ts +2 -1
  10. package/plugins/workflow-runner/src/stepExecutors/skill.js +5 -6
  11. package/plugins/workflow-runner/src/stepExecutors/skill.ts +12 -7
  12. package/plugins/workflow-runner/src/stepExecutors/tool.js +68 -57
  13. package/plugins/workflow-runner/src/stepExecutors/tool.ts +88 -66
  14. package/plugins/workflow-runner/src/toolRuntime.js +48 -0
  15. package/plugins/workflow-runner/src/toolRuntime.ts +64 -0
  16. package/plugins/workflow-runner/src/types.ts +26 -7
  17. package/plugins/workflow-runner/test/context-files.test.ts +47 -16
  18. package/plugins/workflow-runner/test/hostCapability.test.ts +9 -3
  19. package/plugins/workflow-runner/test/multimodal.test.ts +61 -10
  20. package/plugins/workflow-runner/test/toolRuntimeIntegration.test.ts +168 -0
  21. package/src/cli/print.js +19 -4
  22. package/src/cli/print.ts +17 -4
  23. package/src/cli/streamJson.js +19 -4
  24. package/src/cli/streamJson.ts +17 -4
  25. package/src/config/configDefaults.js +25 -0
  26. package/src/config/configDefaults.ts +33 -0
  27. package/src/config/configFile.js +2 -21
  28. package/src/config/configFile.ts +11 -24
  29. package/src/config/configManagement.js +1 -1
  30. package/src/config/configManagement.ts +2 -2
  31. package/src/context/compact.js +4 -0
  32. package/src/context/compact.ts +4 -0
  33. package/src/context/contextBundle.js +38 -13
  34. package/src/context/contextBundle.ts +47 -13
  35. package/src/harness/stallGuard.js +19 -4
  36. package/src/harness/stallGuard.ts +30 -7
  37. package/src/loop.js +95 -11
  38. package/src/loop.ts +125 -16
  39. package/src/main.js +8 -1
  40. package/src/main.tsx +7 -1
  41. package/src/memory/constants.js +10 -0
  42. package/src/memory/constants.ts +13 -0
  43. package/src/memory/episodicMemory.js +274 -2
  44. package/src/memory/episodicMemory.ts +306 -1
  45. package/src/memory/fileStore.js +24 -4
  46. package/src/memory/fileStore.ts +22 -2
  47. package/src/memory/memoryService.js +79 -12
  48. package/src/memory/memoryService.ts +108 -9
  49. package/src/memory/repository.js +415 -85
  50. package/src/memory/repository.ts +437 -86
  51. package/src/memory/turnJournal.js +95 -0
  52. package/src/memory/turnJournal.ts +120 -0
  53. package/src/memory/types.ts +16 -0
  54. package/src/plugins/pluginApi.ts +30 -6
  55. package/src/plugins/pluginRunner.js +6 -2
  56. package/src/plugins/pluginRunner.ts +12 -7
  57. package/src/prompts/policyRegistry.js +27 -0
  58. package/src/prompts/policyRegistry.ts +27 -0
  59. package/src/prompts/promptAssembly.js +39 -7
  60. package/src/prompts/promptAssembly.ts +145 -59
  61. package/src/session/runAgentTurn.js +43 -21
  62. package/src/session/runAgentTurn.ts +68 -26
  63. package/src/session/runTurn.js +11 -46
  64. package/src/session/runTurn.ts +12 -46
  65. package/src/session/runtimeStore.js +149 -0
  66. package/src/session/runtimeStore.ts +197 -0
  67. package/src/session/sessionState.js +5 -0
  68. package/src/session/sessionState.ts +14 -0
  69. package/src/tools/agent/agent.js +143 -14
  70. package/src/tools/agent/agent.ts +149 -14
  71. package/src/tools/agent/agentTaskService.js +60 -0
  72. package/src/tools/agent/agentTaskService.ts +113 -0
  73. package/src/tools/agent/agentTypes.js +5 -0
  74. package/src/tools/agent/agentTypes.ts +5 -0
  75. package/src/tools/bash/bash.js +31 -11
  76. package/src/tools/bash/bash.ts +29 -10
  77. package/src/tools/capabilities.js +5 -0
  78. package/src/tools/capabilities.ts +11 -1
  79. package/src/tools/edit/edit.js +7 -4
  80. package/src/tools/edit/edit.ts +7 -4
  81. package/src/tools/edit/multi-edit.js +4 -2
  82. package/src/tools/edit/multi-edit.ts +4 -2
  83. package/src/tools/glob/glob.js +156 -40
  84. package/src/tools/glob/glob.ts +185 -46
  85. package/src/tools/index.js +35 -17
  86. package/src/tools/index.ts +81 -20
  87. package/src/tools/lsp/lsp.js +165 -22
  88. package/src/tools/lsp/lsp.ts +206 -36
  89. package/src/tools/powershell/powershell.js +116 -26
  90. package/src/tools/powershell/powershell.ts +155 -30
  91. package/src/tools/presentresult/presentresult.js +10 -0
  92. package/src/tools/presentresult/presentresult.ts +10 -0
  93. package/src/tools/read/read.js +244 -29
  94. package/src/tools/read/read.ts +391 -59
  95. package/src/tools/shared/diffSummary.js +44 -0
  96. package/src/tools/shared/diffSummary.ts +63 -0
  97. package/src/tools/shared/fileActivity.js +2 -1
  98. package/src/tools/shared/fileActivity.ts +3 -0
  99. package/src/tools/shared/permanentDeleteGuard.js +26 -0
  100. package/src/tools/shared/permanentDeleteGuard.ts +34 -0
  101. package/src/tools/shared/toolOutputStore.js +97 -0
  102. package/src/tools/shared/toolOutputStore.ts +110 -0
  103. package/src/tools/skill/skill.js +86 -0
  104. package/src/tools/skill/skill.ts +94 -0
  105. package/src/tools/snip/snip.js +67 -0
  106. package/src/tools/snip/snip.ts +80 -0
  107. package/src/tools/tasks/backgroundTaskService.js +79 -28
  108. package/src/tools/tasks/backgroundTaskService.ts +165 -49
  109. package/src/tools/tasks/taskManagement.js +163 -0
  110. package/src/tools/tasks/taskManagement.ts +180 -0
  111. package/src/tools/tasks/taskOutput.js +64 -14
  112. package/src/tools/tasks/taskOutput.ts +102 -23
  113. package/src/tools/tasks/taskStop.js +48 -8
  114. package/src/tools/tasks/taskStop.ts +69 -9
  115. package/src/tools/tasks/taskStore.js +46 -0
  116. package/src/tools/tasks/taskStore.ts +77 -0
  117. package/src/tools/terminal/terminalCapture.js +167 -0
  118. package/src/tools/terminal/terminalCapture.ts +179 -0
  119. package/src/tools/trash/trash.js +233 -0
  120. package/src/tools/trash/trash.ts +283 -0
  121. package/src/tools/worktree/sessionWorktree.js +128 -0
  122. package/src/tools/worktree/sessionWorktree.ts +158 -0
  123. package/src/tools/write/write.js +6 -2
  124. package/src/tools/write/write.ts +6 -2
  125. package/src/types.ts +12 -0
  126. package/src/ui/hooks/useChat.js +31 -11
  127. package/src/ui/hooks/useChat.ts +31 -13
  128. package/src/web/protocol.ts +78 -3
@@ -13,6 +13,8 @@ import { killTree } from '../shared/killTree.js';
13
13
  import { startBackgroundTask } from '../tasks/backgroundTaskService.js';
14
14
  import { processExecutionMetadata } from '../tasks/processEvidence.js';
15
15
  import { DEFAULT_MAX_RESULT_SIZE_CHARS } from '../types.js';
16
+ import { permanentDeleteReason } from '../shared/permanentDeleteGuard.js';
17
+ import { ToolOutputStore } from '../shared/toolOutputStore.js';
16
18
 
17
19
  const MAX_TIMEOUT_MS = 600_000;
18
20
  function findPowerShell(): string | undefined {
@@ -25,7 +27,13 @@ function findPowerShell(): string | undefined {
25
27
  if (existsSync(path)) return path;
26
28
  }
27
29
  }
28
- const system = join(process.env.SystemRoot ?? 'C:\\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
30
+ const system = join(
31
+ process.env.SystemRoot ?? 'C:\\Windows',
32
+ 'System32',
33
+ 'WindowsPowerShell',
34
+ 'v1.0',
35
+ 'powershell.exe',
36
+ );
29
37
  return existsSync(system) ? system : undefined;
30
38
  }
31
39
 
@@ -38,27 +46,57 @@ const inputSchema = z.object({
38
46
  type PowerShellInput = z.infer<typeof inputSchema>;
39
47
 
40
48
  function invocation(command: string): string[] {
41
- return ['-NoLogo', '-NoProfile', '-NonInteractive', '-Command', `$OutputEncoding=[Console]::OutputEncoding=[Text.UTF8Encoding]::new(); ${command}`];
49
+ return [
50
+ '-NoLogo',
51
+ '-NoProfile',
52
+ '-NonInteractive',
53
+ '-Command',
54
+ `$OutputEncoding=[Console]::OutputEncoding=[Text.UTF8Encoding]::new(); ${command}`,
55
+ ];
42
56
  }
43
57
 
44
- async function approved(command: string, signal?: AbortSignal): Promise<ToolResult | undefined> {
58
+ async function approved(
59
+ command: string,
60
+ signal?: AbortSignal,
61
+ ): Promise<ToolResult | undefined> {
62
+ const permanentDelete = permanentDeleteReason(command, 'powershell');
63
+ if (permanentDelete)
64
+ return {
65
+ ok: false,
66
+ error: `永久删除检查拒绝执行:${permanentDelete}\n命令:${command}`,
67
+ };
45
68
  const forbidden = checkForbidden(command);
46
- if (forbidden) return { ok: false, error: `安全检查拒绝执行:${forbidden}\n命令:${command}` };
69
+ if (forbidden)
70
+ return {
71
+ ok: false,
72
+ error: `安全检查拒绝执行:${forbidden}\n命令:${command}`,
73
+ };
47
74
  const irreversible = classifyCommand(command);
48
75
  if (!irreversible) return undefined;
49
- const [answer] = await requestUserChoice([{
50
- question: `这条命令不可逆:${irreversible.why}。\n\n命令:${command}\n\n确认执行吗?`, header: '不可逆操作',
51
- options: [
52
- { label: '取消,不执行 (推荐)', description: '放弃并改用可回滚做法' },
53
- { label: '我已确认后果,执行', description: '继续执行不可逆操作' },
76
+ const [answer] = await requestUserChoice(
77
+ [
78
+ {
79
+ question: `这条命令不可逆:${irreversible.why}。\n\n命令:${command}\n\n确认执行吗?`,
80
+ header: '不可逆操作',
81
+ options: [
82
+ { label: '取消,不执行 (推荐)', description: '放弃并改用可回滚做法' },
83
+ { label: '我已确认后果,执行', description: '继续执行不可逆操作' },
84
+ ],
85
+ },
54
86
  ],
55
- }], signal);
56
- return answer?.selected?.[0]?.includes('执行') && !answer.selected[0]?.includes('取消')
87
+ signal,
88
+ );
89
+ return answer?.selected?.[0]?.includes('执行') &&
90
+ !answer.selected[0]?.includes('取消')
57
91
  ? undefined
58
92
  : { ok: false, error: `已拒绝执行不可逆 PowerShell 命令:${command}` };
59
93
  }
60
94
 
61
- async function call(input: PowerShellInput, signal?: AbortSignal, context?: ToolCallContext): Promise<ToolResult> {
95
+ async function call(
96
+ input: PowerShellInput,
97
+ signal?: AbortSignal,
98
+ context?: ToolCallContext,
99
+ ): Promise<ToolResult> {
62
100
  const executable = findPowerShell();
63
101
  if (!executable) return { ok: false, error: '当前宿主没有可用的 PowerShell' };
64
102
  const refusal = await approved(input.command, signal);
@@ -67,38 +105,122 @@ async function call(input: PowerShellInput, signal?: AbortSignal, context?: Tool
67
105
  const cwd = context?.cwd ?? getWorkingDir();
68
106
  if (input.run_in_background) {
69
107
  try {
70
- const task = await startBackgroundTask({ command: input.command, executable, args, cwd, env: sanitizedChildEnv(), sessionId: context?.sessionId, shellKind: 'powershell' }, context?.sessionState?.backgroundTasks);
108
+ const task = await startBackgroundTask(
109
+ {
110
+ command: input.command,
111
+ executable,
112
+ args,
113
+ cwd,
114
+ env: sanitizedChildEnv(),
115
+ sessionId: context?.sessionId,
116
+ workspaceId: context?.workspaceId,
117
+ shellKind: 'powershell',
118
+ },
119
+ context?.sessionState?.backgroundTasks,
120
+ );
71
121
  return {
72
122
  ok: true,
73
- content: `PowerShell 后台任务已启动。task_id=${task.id}\nlog=${task.logPath}`,
74
- metadata: processExecutionMetadata({ shell: 'powershell', mode: 'background', command: input.command, status: 'started', taskId: task.id }),
123
+ content: `PowerShell 后台任务已启动。task_id=${task.id.replace(/^proc_/, '')}\nprocess_id=${task.id}\nlog=${task.logPath}`,
124
+ metadata: processExecutionMetadata({
125
+ shell: 'powershell',
126
+ mode: 'background',
127
+ command: input.command,
128
+ status: 'started',
129
+ taskId: task.id,
130
+ }),
75
131
  };
76
- } catch (error) { return { ok: false, error: `PowerShell 后台任务启动失败:${(error as Error).message}` }; }
132
+ } catch (error) {
133
+ return {
134
+ ok: false,
135
+ error: `PowerShell 后台任务启动失败:${(error as Error).message}`,
136
+ };
137
+ }
77
138
  }
78
139
 
79
140
  return await new Promise<ToolResult>((resolvePromise) => {
80
- const child = spawn(executable, args, { cwd, env: sanitizedChildEnv(), windowsHide: true, detached: false });
81
- let stdout = ''; let stderr = ''; let finished = false;
82
- const finish = (result: ToolResult) => { if (finished) return; finished = true; clearTimeout(timer); signal?.removeEventListener('abort', abort); resolvePromise(result); };
83
- const abort = () => { killTree(child); finish({ ok: false, error: `PowerShell 命令被中断\n${stdout}\n${stderr}`.slice(0, DEFAULT_MAX_RESULT_SIZE_CHARS), metadata: processExecutionMetadata({ shell: 'powershell', mode: 'foreground', command: input.command, status: 'stopped', exitCode: null }) }); };
141
+ const child = spawn(executable, args, {
142
+ cwd,
143
+ env: sanitizedChildEnv(),
144
+ windowsHide: true,
145
+ detached: false,
146
+ });
147
+ let finished = false;
148
+ const store = new ToolOutputStore({
149
+ workspaceId: context?.workspaceId,
150
+ sessionId: context?.sessionId,
151
+ });
152
+ const finish = async (
153
+ status: 'completed' | 'failed' | 'stopped',
154
+ code: number | null,
155
+ prefix = '',
156
+ ) => {
157
+ if (finished) return;
158
+ finished = true;
159
+ clearTimeout(timer);
160
+ signal?.removeEventListener('abort', abort);
161
+ const output = await store.finish();
162
+ const reference = output.path
163
+ ? `\n[完整输出: ${output.path}; bytes=${output.bytes}; sha256=${output.sha256}; complete=${output.complete}]`
164
+ : '';
165
+ let text = `${prefix}${output.stdout ? `<stdout>\n${output.stdout.trimEnd()}\n</stdout>\n` : ''}${output.stderr ? `<stderr>\n${output.stderr.trimEnd()}\n</stderr>\n` : ''}[exit code: ${code ?? 'killed'}]`;
166
+ if (text.length + reference.length > DEFAULT_MAX_RESULT_SIZE_CHARS) {
167
+ const suffix = `\n... (输出预览已截断)${reference}`;
168
+ text =
169
+ text.slice(
170
+ 0,
171
+ Math.max(0, DEFAULT_MAX_RESULT_SIZE_CHARS - suffix.length),
172
+ ) + suffix;
173
+ } else text += reference;
174
+ const metadata = processExecutionMetadata({
175
+ shell: 'powershell',
176
+ mode: 'foreground',
177
+ command: input.command,
178
+ status,
179
+ exitCode: code,
180
+ });
181
+ resolvePromise(
182
+ status === 'completed'
183
+ ? { ok: true, content: text, metadata }
184
+ : { ok: false, error: text, metadata },
185
+ );
186
+ };
187
+ const abort = () => {
188
+ killTree(child);
189
+ void finish('stopped', null, 'PowerShell 命令被中断\n');
190
+ };
84
191
  const timeout = input.timeout ?? 120_000;
85
- const timer = setTimeout(() => { killTree(child); finish({ ok: false, error: `PowerShell 命令超时(${timeout}ms)\n${stdout}\n${stderr}`.slice(0, DEFAULT_MAX_RESULT_SIZE_CHARS), metadata: processExecutionMetadata({ shell: 'powershell', mode: 'foreground', command: input.command, status: 'failed', exitCode: null }) }); }, timeout);
192
+ const timer = setTimeout(() => {
193
+ killTree(child);
194
+ void finish('failed', null, `PowerShell 命令超时(${timeout}ms)\n`);
195
+ }, timeout);
86
196
  signal?.addEventListener('abort', abort, { once: true });
87
- child.stdout?.setEncoding('utf8'); child.stderr?.setEncoding('utf8');
88
- child.stdout?.on('data', (chunk) => { if (stdout.length < DEFAULT_MAX_RESULT_SIZE_CHARS * 2) stdout += chunk; });
89
- child.stderr?.on('data', (chunk) => { if (stderr.length < DEFAULT_MAX_RESULT_SIZE_CHARS * 2) stderr += chunk; });
90
- child.once('error', (error) => finish({ ok: false, error: `PowerShell 启动失败:${error.message}`, metadata: processExecutionMetadata({ shell: 'powershell', mode: 'foreground', command: input.command, status: 'failed', exitCode: null }) }));
197
+ child.stdout?.setEncoding('utf8');
198
+ child.stderr?.setEncoding('utf8');
199
+ child.stdout?.on('data', (chunk) => {
200
+ if (!store.append('stdout', chunk)) {
201
+ child.stdout?.pause();
202
+ store.onDrain(() => child.stdout?.resume());
203
+ }
204
+ });
205
+ child.stderr?.on('data', (chunk) => {
206
+ if (!store.append('stderr', chunk)) {
207
+ child.stderr?.pause();
208
+ store.onDrain(() => child.stderr?.resume());
209
+ }
210
+ });
211
+ child.once('error', (error) => {
212
+ void finish('failed', null, `PowerShell 启动失败:${error.message}\n`);
213
+ });
91
214
  child.once('close', (code) => {
92
- const text = `${stdout ? `<stdout>\n${stdout.trimEnd()}\n</stdout>\n` : ''}${stderr ? `<stderr>\n${stderr.trimEnd()}\n</stderr>\n` : ''}[exit code: ${code}]`.slice(0, DEFAULT_MAX_RESULT_SIZE_CHARS);
93
- const metadata = processExecutionMetadata({ shell: 'powershell', mode: 'foreground', command: input.command, status: code === 0 ? 'completed' : 'failed', exitCode: code });
94
- finish(code === 0 ? { ok: true, content: text, metadata } : { ok: false, error: text, metadata });
215
+ void finish(code === 0 ? 'completed' : 'failed', code);
95
216
  });
96
217
  });
97
218
  }
98
219
 
99
220
  export const powerShellTool: Tool<PowerShellInput> = {
100
221
  name: 'PowerShell',
101
- description: 'Executes native PowerShell on Windows. Use for Windows-specific administration and scripts; use run_in_background for long builds, tests, or servers, then TaskOutput/TaskStop.',
222
+ description:
223
+ 'Executes native PowerShell on Windows. Use for Windows-specific administration and scripts; use run_in_background for long builds, tests, or servers, then TaskOutput/TaskStop.',
102
224
  inputSchema,
103
225
  parameters: toToolParameters(inputSchema),
104
226
  isReadOnly: false,
@@ -107,7 +229,10 @@ export const powerShellTool: Tool<PowerShellInput> = {
107
229
  interruptBehavior: () => 'block',
108
230
  resolveAvailability: () => {
109
231
  const executable = findPowerShell();
110
- return { available: Boolean(executable), signature: executable ?? 'unavailable' };
232
+ return {
233
+ available: Boolean(executable),
234
+ signature: executable ?? 'unavailable',
235
+ };
111
236
  },
112
237
  call: (input, signal) => call(input, signal),
113
238
  callWithContext: (input, signal, context) => call(input, signal, context),
@@ -7,17 +7,27 @@ import { getWorkingDir } from '../../bootstrap/workingDir.js';
7
7
  import { toToolParameters } from '../../utils/zodToJson.js';
8
8
  const MIME_BY_EXT = {
9
9
  '.csv': 'text/csv',
10
+ '.css': 'text/css',
10
11
  '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
12
+ '.gif': 'image/gif',
11
13
  '.html': 'text/html',
14
+ '.js': 'text/javascript',
12
15
  '.jpeg': 'image/jpeg',
13
16
  '.jpg': 'image/jpeg',
14
17
  '.json': 'application/json',
15
18
  '.md': 'text/markdown',
19
+ '.mp4': 'video/mp4',
16
20
  '.pdf': 'application/pdf',
17
21
  '.png': 'image/png',
18
22
  '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
23
+ '.py': 'text/x-python',
24
+ '.svg': 'image/svg+xml',
25
+ '.ts': 'text/typescript',
26
+ '.tsx': 'text/typescript',
19
27
  '.txt': 'text/plain',
28
+ '.webp': 'image/webp',
20
29
  '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
30
+ '.zip': 'application/zip',
21
31
  };
22
32
  const verificationSchema = z.enum(['passed', 'failed', 'not_run']).default('not_run');
23
33
  const inputSchema = z.object({
@@ -10,17 +10,27 @@ import { toToolParameters } from '../../utils/zodToJson.js';
10
10
 
11
11
  const MIME_BY_EXT: Record<string, string> = {
12
12
  '.csv': 'text/csv',
13
+ '.css': 'text/css',
13
14
  '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
15
+ '.gif': 'image/gif',
14
16
  '.html': 'text/html',
17
+ '.js': 'text/javascript',
15
18
  '.jpeg': 'image/jpeg',
16
19
  '.jpg': 'image/jpeg',
17
20
  '.json': 'application/json',
18
21
  '.md': 'text/markdown',
22
+ '.mp4': 'video/mp4',
19
23
  '.pdf': 'application/pdf',
20
24
  '.png': 'image/png',
21
25
  '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
26
+ '.py': 'text/x-python',
27
+ '.svg': 'image/svg+xml',
28
+ '.ts': 'text/typescript',
29
+ '.tsx': 'text/typescript',
22
30
  '.txt': 'text/plain',
31
+ '.webp': 'image/webp',
23
32
  '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
33
+ '.zip': 'application/zip',
24
34
  };
25
35
 
26
36
  const verificationSchema = z.enum(['passed', 'failed', 'not_run']).default('not_run');