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
@@ -20,14 +20,15 @@ from scripts.utils import parse_skill_md
20
20
 
21
21
 
22
22
  def find_project_root() -> Path:
23
- """Find the project root by walking up from cwd looking for .claude/.
23
+ """Find the project root by walking up from cwd looking for .kakadeai/
24
+ (this repo's project-config dir) or .claude/ (upstream convention).
24
25
 
25
26
  Mimics how Claude Code discovers its project root, so the command file
26
27
  we create ends up where claude -p will look for it.
27
28
  """
28
29
  current = Path.cwd()
29
30
  for parent in [current, *current.parents]:
30
- if (parent / ".claude").is_dir():
31
+ if (parent / ".kakadeai").is_dir() or (parent / ".claude").is_dir():
31
32
  return parent
32
33
  return current
33
34
 
package/src/cli/print.js CHANGED
@@ -27,7 +27,9 @@ import { updateSessionMetadata } from '../context/sessionRegistry.js';
27
27
  import { workspaceIndexService } from '../context/workspaceIndex.js';
28
28
  import { normalizeImageDetail } from '../multimodal.js';
29
29
  import { runWithPlugins } from '../plugins/pluginRunner.js';
30
- import { buildTransientPromptBundle } from '../prompts/system.js';
30
+ import { buildBaseSystemPrompt, buildTransientPromptBundle } from '../prompts/system.js';
31
+ import { getOrCreateSessionState, sessionStateKey } from '../session/sessionState.js';
32
+ import { hydrateSessionWorktree } from '../tools/worktree/sessionWorktree.js';
31
33
  import { ALL_TOOLS } from '../tools/index.js';
32
34
  import { createPromptCacheContext } from '../llm/promptCache.js';
33
35
  import { createAgentRuntimeContext, filterToolsForRuntime, memoryContextFromRuntime, } from '../runtime/agentRuntimeContext.js';
@@ -106,8 +108,16 @@ export function extractImageOptions(args) {
106
108
  * stop_reason = end_turn | max_turns | stalled | error | interrupted | config_error
107
109
  * num_turns = assistant_message 事件计数
108
110
  * error = 失败文案(成功时 null)
111
+ * warnings = 本轮所有非致命警告(与 stderr 同源;宿主要结构化的那一份)
109
112
  * session_file(可选)= 当前对话落盘路径;拿得到才填,拿不到省略该键
110
113
  *
114
+ * completionStatus = 本层从 stop_reason + deliverables 重算:跑完了没、产物验没验
115
+ * delivery_status(可选)= 内核 turn_done 的交付判定,**回答的是另一个问题**:
116
+ * completed / delivery_incomplete / delivery_unverified / claims_unverified。
117
+ * 两者并列而非替代 —— `ok:true` 配 `delivery_status:'claims_unverified'` 是
118
+ * 完全合法的一行:Agent 确实正常收尾了,但它宣称的「测试已通过」没有证据支撑。
119
+ * 判断「任务是否真做成」的宿主应当读它,而不是只看 ok。
120
+ *
111
121
  * 导出供单测断言。
112
122
  */
113
123
  export function emitJsonResult(result, sessionFile) {
@@ -134,6 +144,7 @@ export function emitJsonResult(result, sessionFile) {
134
144
  num_turns: result.numTurns,
135
145
  error: result.error,
136
146
  completionStatus,
147
+ ...(result.deliveryStatus ? { delivery_status: result.deliveryStatus } : {}),
137
148
  deliverables,
138
149
  verification: allVerified ? 'passed' : deliverables.length > 0 ? 'not_run' : 'none',
139
150
  warnings,
@@ -232,10 +243,16 @@ export async function runPrintMode(provider, args, initialHistory, options) {
232
243
  void finalizeOnce().finally(() => process.exit(130));
233
244
  });
234
245
  try {
235
- const cwd = options.workspace?.cwd ?? process.cwd();
236
- const workspace = await workspaceIndexService.findByPath(cwd).catch(() => null);
246
+ const workspaceRoot = options.workspace?.cwd ?? process.cwd();
247
+ const workspace = await workspaceIndexService.findByPath(workspaceRoot).catch(() => null);
237
248
  const sessionId = options.workspace?.sessionId ?? 'default';
238
- const conversationKey = `workspace:${workspace?.id ?? cwd}:session:${sessionId}`;
249
+ const sessionState = getOrCreateSessionState(sessionStateKey({
250
+ surface: 'cli', workspaceId: workspace?.id,
251
+ cwd: workspace?.id ? undefined : workspaceRoot, sessionId,
252
+ }));
253
+ await hydrateSessionWorktree({ sessionState, workspaceId: workspace?.id, sessionId, cwd: workspaceRoot });
254
+ const cwd = sessionState.activeWorktree?.path ?? workspaceRoot;
255
+ const conversationKey = `workspace:${workspace?.id ?? workspaceRoot}:session:${sessionId}`;
239
256
  const computerRun = options.computerUseMode && options.computerUseMode !== 'disabled'
240
257
  ? createCliComputerRun({
241
258
  mode: options.computerUseMode,
@@ -259,6 +276,11 @@ export async function runPrintMode(provider, args, initialHistory, options) {
259
276
  computer: computerRun,
260
277
  });
261
278
  const turnTools = filterToolsForRuntime(ALL_TOOLS, runtimeContext);
279
+ const stablePrompt = await buildBaseSystemPrompt(cwd, turnTools);
280
+ if (history[0]?.role === 'system')
281
+ history[0] = { ...history[0], content: stablePrompt };
282
+ else
283
+ history.unshift({ role: 'system', content: stablePrompt });
262
284
  const transientBundle = await buildTransientPromptBundle({
263
285
  cwd,
264
286
  tools: turnTools,
@@ -266,6 +288,7 @@ export async function runPrintMode(provider, args, initialHistory, options) {
266
288
  surface: 'cli',
267
289
  cwd,
268
290
  workspaceId: workspace?.id,
291
+ sessionId,
269
292
  interactiveAsk: false,
270
293
  memoryAccess: runtimeContext.memoryAccess,
271
294
  currentUserInput: prompt,
@@ -282,6 +305,7 @@ export async function runPrintMode(provider, args, initialHistory, options) {
282
305
  imageMaxLongSidePixel: imageOptions.imageMaxLongSidePixel,
283
306
  allowedTools: turnTools.map((tool) => tool.name),
284
307
  runtimeContext,
308
+ sessionState,
285
309
  ...(transientBundle ? { transientBundle } : {}),
286
310
  promptCache: createPromptCacheContext(`workspace:${workspace?.id ?? cwd}:session:${sessionId}`),
287
311
  memoryContext: memoryContextFromRuntime(runtimeContext),
@@ -384,11 +408,19 @@ export function handleEvent(event, result, verbose, outputFormat = 'text') {
384
408
  // 通用非致命警告(如 workflow 多余参数、pause 被自动跳过等)始终走 stderr,
385
409
  // 不受 verbose 控制 —— 这是用户必须看到的信号,避免静默丢弃语义被忽略。
386
410
  process.stderr.write(`${event.message}\n`);
411
+ // 同时收进结局契约:stderr 是给人看的,spawn 它的宿主要的是结构化的一条。
412
+ // (此前 PrintResult.warnings 声明了、读了、也输出了,就是没有任何地方写它 ——
413
+ // json 那行的 warnings 恒为空数组。)
414
+ (result.warnings ??= []).push(event.message);
387
415
  return {};
388
416
  case 'turn_done':
389
417
  // text 模式:非 verbose 时这里一次性把累积答案打到 stdout(旧行为,逐字节不变)。
390
418
  // json 模式:只记 stopReason='end_turn',stdout 保持静默。
391
419
  result.stopReason = 'end_turn';
420
+ // 内核的交付判定必须过进程边界:模型谎称「已验证」时,若不记这一笔,
421
+ // 宿主收到的 JSON 与一切正常逐字节相同,唯一线索只剩 stderr 上一行中文。
422
+ if (event.completionStatus)
423
+ result.deliveryStatus = event.completionStatus;
392
424
  if (!isJson && !verbose)
393
425
  console.log(result.buffer);
394
426
  return {};
package/src/cli/print.ts CHANGED
@@ -29,7 +29,9 @@ import { workspaceIndexService } from '../context/workspaceIndex.js';
29
29
  import { normalizeImageDetail } from '../multimodal.js';
30
30
  import type { DeliverableManifest, FileActivityMetadata, HarnessDiagnostics, ImageInputSpec, LoopEvent, Message, Provider } from '../types.js';
31
31
  import { runWithPlugins } from '../plugins/pluginRunner.js';
32
- import { buildTransientPromptBundle } from '../prompts/system.js';
32
+ import { buildBaseSystemPrompt, buildTransientPromptBundle } from '../prompts/system.js';
33
+ import { getOrCreateSessionState, sessionStateKey } from '../session/sessionState.js';
34
+ import { hydrateSessionWorktree } from '../tools/worktree/sessionWorktree.js';
33
35
  import { ALL_TOOLS } from '../tools/index.js';
34
36
  import { createPromptCacheContext } from '../llm/promptCache.js';
35
37
  import {
@@ -96,6 +98,15 @@ export interface PrintResult {
96
98
  deliverables?: DeliverableManifest['files'];
97
99
  warnings?: string[];
98
100
  harnessDiagnostics?: HarnessDiagnostics;
101
+ /**
102
+ * 内核 `turn_done` 给出的交付判定(loop.ts 的四态词表)。
103
+ *
104
+ * 与下面 json 里那个从 stopReason+deliverables 重算的 `completionStatus` **不是**
105
+ * 一回事:那个只回答「跑完了没、产物验没验」,这个回答「模型宣称的事到底做没做」。
106
+ * 尤其 `claims_unverified` —— 模型说「测试已全部通过」但本轮证据台账里没有一条
107
+ * 成功的验证记录 —— 是重不重要全看它能不能过进程边界。
108
+ */
109
+ deliveryStatus?: string;
99
110
  }
100
111
 
101
112
  /** handleEvent 返回的结构 */
@@ -189,8 +200,16 @@ export function extractImageOptions(args: string[]): {
189
200
  * stop_reason = end_turn | max_turns | stalled | error | interrupted | config_error
190
201
  * num_turns = assistant_message 事件计数
191
202
  * error = 失败文案(成功时 null)
203
+ * warnings = 本轮所有非致命警告(与 stderr 同源;宿主要结构化的那一份)
192
204
  * session_file(可选)= 当前对话落盘路径;拿得到才填,拿不到省略该键
193
205
  *
206
+ * completionStatus = 本层从 stop_reason + deliverables 重算:跑完了没、产物验没验
207
+ * delivery_status(可选)= 内核 turn_done 的交付判定,**回答的是另一个问题**:
208
+ * completed / delivery_incomplete / delivery_unverified / claims_unverified。
209
+ * 两者并列而非替代 —— `ok:true` 配 `delivery_status:'claims_unverified'` 是
210
+ * 完全合法的一行:Agent 确实正常收尾了,但它宣称的「测试已通过」没有证据支撑。
211
+ * 判断「任务是否真做成」的宿主应当读它,而不是只看 ok。
212
+ *
194
213
  * 导出供单测断言。
195
214
  */
196
215
  export function emitJsonResult(result: PrintResult, sessionFile?: string): void {
@@ -218,6 +237,7 @@ export function emitJsonResult(result: PrintResult, sessionFile?: string): void
218
237
  num_turns: result.numTurns,
219
238
  error: result.error,
220
239
  completionStatus,
240
+ ...(result.deliveryStatus ? { delivery_status: result.deliveryStatus } : {}),
221
241
  deliverables,
222
242
  verification: allVerified ? 'passed' : deliverables.length > 0 ? 'not_run' : 'none',
223
243
  warnings,
@@ -325,10 +345,16 @@ export async function runPrintMode(
325
345
  });
326
346
 
327
347
  try {
328
- const cwd = options.workspace?.cwd ?? process.cwd();
329
- const workspace = await workspaceIndexService.findByPath(cwd).catch(() => null);
348
+ const workspaceRoot = options.workspace?.cwd ?? process.cwd();
349
+ const workspace = await workspaceIndexService.findByPath(workspaceRoot).catch(() => null);
330
350
  const sessionId = options.workspace?.sessionId ?? 'default';
331
- const conversationKey = `workspace:${workspace?.id ?? cwd}:session:${sessionId}`;
351
+ const sessionState = getOrCreateSessionState(sessionStateKey({
352
+ surface: 'cli', workspaceId: workspace?.id,
353
+ cwd: workspace?.id ? undefined : workspaceRoot, sessionId,
354
+ }));
355
+ await hydrateSessionWorktree({ sessionState, workspaceId: workspace?.id, sessionId, cwd: workspaceRoot });
356
+ const cwd = sessionState.activeWorktree?.path ?? workspaceRoot;
357
+ const conversationKey = `workspace:${workspace?.id ?? workspaceRoot}:session:${sessionId}`;
332
358
  const computerRun = options.computerUseMode && options.computerUseMode !== 'disabled'
333
359
  ? createCliComputerRun({
334
360
  mode: options.computerUseMode,
@@ -352,6 +378,9 @@ export async function runPrintMode(
352
378
  computer: computerRun,
353
379
  });
354
380
  const turnTools = filterToolsForRuntime(ALL_TOOLS, runtimeContext);
381
+ const stablePrompt = await buildBaseSystemPrompt(cwd, turnTools);
382
+ if (history[0]?.role === 'system') history[0] = { ...history[0], content: stablePrompt };
383
+ else history.unshift({ role: 'system', content: stablePrompt });
355
384
  const transientBundle = await buildTransientPromptBundle({
356
385
  cwd,
357
386
  tools: turnTools,
@@ -359,6 +388,7 @@ export async function runPrintMode(
359
388
  surface: 'cli',
360
389
  cwd,
361
390
  workspaceId: workspace?.id,
391
+ sessionId,
362
392
  interactiveAsk: false,
363
393
  memoryAccess: runtimeContext.memoryAccess,
364
394
  currentUserInput: prompt,
@@ -375,6 +405,7 @@ export async function runPrintMode(
375
405
  imageMaxLongSidePixel: imageOptions.imageMaxLongSidePixel,
376
406
  allowedTools: turnTools.map((tool) => tool.name),
377
407
  runtimeContext,
408
+ sessionState,
378
409
  ...(transientBundle ? { transientBundle } : {}),
379
410
  promptCache: createPromptCacheContext(
380
411
  `workspace:${workspace?.id ?? cwd}:session:${sessionId}`,
@@ -496,12 +527,19 @@ export function handleEvent(
496
527
  // 通用非致命警告(如 workflow 多余参数、pause 被自动跳过等)始终走 stderr,
497
528
  // 不受 verbose 控制 —— 这是用户必须看到的信号,避免静默丢弃语义被忽略。
498
529
  process.stderr.write(`${event.message}\n`);
530
+ // 同时收进结局契约:stderr 是给人看的,spawn 它的宿主要的是结构化的一条。
531
+ // (此前 PrintResult.warnings 声明了、读了、也输出了,就是没有任何地方写它 ——
532
+ // json 那行的 warnings 恒为空数组。)
533
+ (result.warnings ??= []).push(event.message);
499
534
  return {};
500
535
 
501
536
  case 'turn_done':
502
537
  // text 模式:非 verbose 时这里一次性把累积答案打到 stdout(旧行为,逐字节不变)。
503
538
  // json 模式:只记 stopReason='end_turn',stdout 保持静默。
504
539
  result.stopReason = 'end_turn';
540
+ // 内核的交付判定必须过进程边界:模型谎称「已验证」时,若不记这一笔,
541
+ // 宿主收到的 JSON 与一切正常逐字节相同,唯一线索只剩 stderr 上一行中文。
542
+ if (event.completionStatus) result.deliveryStatus = event.completionStatus;
505
543
  if (!isJson && !verbose) console.log(result.buffer);
506
544
  return {};
507
545
 
@@ -28,7 +28,9 @@ import { saveContext } from '../context/persistContext.js';
28
28
  import { setAskHandler } from '../askBridge.js';
29
29
  import { normalizeImageDetail } from '../multimodal.js';
30
30
  import { runWithPlugins } from '../plugins/pluginRunner.js';
31
- import { buildTransientPromptBundle } from '../prompts/system.js';
31
+ import { buildBaseSystemPrompt, buildTransientPromptBundle } from '../prompts/system.js';
32
+ import { getOrCreateSessionState, sessionStateKey } from '../session/sessionState.js';
33
+ import { hydrateSessionWorktree } from '../tools/worktree/sessionWorktree.js';
32
34
  import { ALL_TOOLS } from '../tools/index.js';
33
35
  import { createPromptCacheContext } from '../llm/promptCache.js';
34
36
  import { createAgentRuntimeContext, filterToolsForRuntime, memoryContextFromRuntime, } from '../runtime/agentRuntimeContext.js';
@@ -267,10 +269,16 @@ export async function runStreamJsonMode(provider, args, initialHistory, options)
267
269
  void finalize().finally(() => process.exit(130));
268
270
  });
269
271
  try {
270
- const cwd = options.workspace?.cwd ?? getWorkingDir();
271
- const workspace = await workspaceIndexService.findByPath(cwd).catch(() => null);
272
+ const workspaceRoot = options.workspace?.cwd ?? getWorkingDir();
273
+ const workspace = await workspaceIndexService.findByPath(workspaceRoot).catch(() => null);
272
274
  const sessionId = options.workspace?.sessionId ?? 'default';
273
- const conversationKey = `workspace:${workspace?.id ?? cwd}:session:${sessionId}`;
275
+ const sessionState = getOrCreateSessionState(sessionStateKey({
276
+ surface: 'headless', workspaceId: workspace?.id,
277
+ cwd: workspace?.id ? undefined : workspaceRoot, sessionId,
278
+ }));
279
+ await hydrateSessionWorktree({ sessionState, workspaceId: workspace?.id, sessionId, cwd: workspaceRoot });
280
+ const cwd = sessionState.activeWorktree?.path ?? workspaceRoot;
281
+ const conversationKey = `workspace:${workspace?.id ?? workspaceRoot}:session:${sessionId}`;
274
282
  const computerRun = options.computerUseMode && options.computerUseMode !== 'disabled'
275
283
  ? createCliComputerRun({
276
284
  mode: options.computerUseMode,
@@ -294,6 +302,11 @@ export async function runStreamJsonMode(provider, args, initialHistory, options)
294
302
  computer: computerRun,
295
303
  });
296
304
  const turnTools = filterToolsForRuntime(ALL_TOOLS, runtimeContext);
305
+ const stablePrompt = await buildBaseSystemPrompt(cwd, turnTools);
306
+ if (history[0]?.role === 'system')
307
+ history[0] = { ...history[0], content: stablePrompt };
308
+ else
309
+ history.unshift({ role: 'system', content: stablePrompt });
297
310
  const transientBundle = await buildTransientPromptBundle({
298
311
  cwd,
299
312
  tools: turnTools,
@@ -301,6 +314,7 @@ export async function runStreamJsonMode(provider, args, initialHistory, options)
301
314
  surface: 'headless',
302
315
  cwd,
303
316
  workspaceId: workspace?.id,
317
+ sessionId,
304
318
  interactiveAsk: control,
305
319
  memoryAccess: runtimeContext.memoryAccess,
306
320
  currentUserInput: prompt,
@@ -316,6 +330,7 @@ export async function runStreamJsonMode(provider, args, initialHistory, options)
316
330
  imageDetail: cliImageOptions.imageDetail,
317
331
  imageMaxLongSidePixel: cliImageOptions.imageMaxLongSidePixel,
318
332
  runtimeContext,
333
+ sessionState,
319
334
  allowedTools: turnTools.map((tool) => tool.name),
320
335
  ...(transientBundle ? { transientBundle } : {}),
321
336
  promptCache: createPromptCacheContext(`workspace:${workspace?.id ?? cwd}:session:${sessionId}`),
@@ -33,7 +33,9 @@ export type { AskAnswer } from '../askBridge.js';
33
33
  import { normalizeImageDetail } from '../multimodal.js';
34
34
  import type { ImageInputSpec, Message, Provider } from '../types.js';
35
35
  import { runWithPlugins } from '../plugins/pluginRunner.js';
36
- import { buildTransientPromptBundle } from '../prompts/system.js';
36
+ import { buildBaseSystemPrompt, buildTransientPromptBundle } from '../prompts/system.js';
37
+ import { getOrCreateSessionState, sessionStateKey } from '../session/sessionState.js';
38
+ import { hydrateSessionWorktree } from '../tools/worktree/sessionWorktree.js';
37
39
  import { ALL_TOOLS } from '../tools/index.js';
38
40
  import { createPromptCacheContext } from '../llm/promptCache.js';
39
41
  import {
@@ -344,10 +346,16 @@ export async function runStreamJsonMode(
344
346
  });
345
347
 
346
348
  try {
347
- const cwd = options.workspace?.cwd ?? getWorkingDir();
348
- const workspace = await workspaceIndexService.findByPath(cwd).catch(() => null);
349
+ const workspaceRoot = options.workspace?.cwd ?? getWorkingDir();
350
+ const workspace = await workspaceIndexService.findByPath(workspaceRoot).catch(() => null);
349
351
  const sessionId = options.workspace?.sessionId ?? 'default';
350
- const conversationKey = `workspace:${workspace?.id ?? cwd}:session:${sessionId}`;
352
+ const sessionState = getOrCreateSessionState(sessionStateKey({
353
+ surface: 'headless', workspaceId: workspace?.id,
354
+ cwd: workspace?.id ? undefined : workspaceRoot, sessionId,
355
+ }));
356
+ await hydrateSessionWorktree({ sessionState, workspaceId: workspace?.id, sessionId, cwd: workspaceRoot });
357
+ const cwd = sessionState.activeWorktree?.path ?? workspaceRoot;
358
+ const conversationKey = `workspace:${workspace?.id ?? workspaceRoot}:session:${sessionId}`;
351
359
  const computerRun = options.computerUseMode && options.computerUseMode !== 'disabled'
352
360
  ? createCliComputerRun({
353
361
  mode: options.computerUseMode,
@@ -371,6 +379,9 @@ export async function runStreamJsonMode(
371
379
  computer: computerRun,
372
380
  });
373
381
  const turnTools = filterToolsForRuntime(ALL_TOOLS, runtimeContext);
382
+ const stablePrompt = await buildBaseSystemPrompt(cwd, turnTools);
383
+ if (history[0]?.role === 'system') history[0] = { ...history[0], content: stablePrompt };
384
+ else history.unshift({ role: 'system', content: stablePrompt });
374
385
  const transientBundle = await buildTransientPromptBundle({
375
386
  cwd,
376
387
  tools: turnTools,
@@ -378,6 +389,7 @@ export async function runStreamJsonMode(
378
389
  surface: 'headless',
379
390
  cwd,
380
391
  workspaceId: workspace?.id,
392
+ sessionId,
381
393
  interactiveAsk: control,
382
394
  memoryAccess: runtimeContext.memoryAccess,
383
395
  currentUserInput: prompt,
@@ -393,6 +405,7 @@ export async function runStreamJsonMode(
393
405
  imageDetail: cliImageOptions.imageDetail,
394
406
  imageMaxLongSidePixel: cliImageOptions.imageMaxLongSidePixel,
395
407
  runtimeContext,
408
+ sessionState,
396
409
  allowedTools: turnTools.map((tool) => tool.name),
397
410
  ...(transientBundle ? { transientBundle } : {}),
398
411
  promptCache: createPromptCacheContext(
@@ -0,0 +1,25 @@
1
+ /**
2
+ * 浏览器与 Node/Bun 都可安全加载的纯配置默认值。
3
+ * 不得在此模块引入 node:*、文件系统或运行时路径逻辑。
4
+ */
5
+ export const DEFAULT_SUBAGENTS_CONFIG = {
6
+ enabled: true,
7
+ maxCallsPerTurn: 6,
8
+ maxConcurrent: 3,
9
+ defaultWriteIsolation: 'none',
10
+ timeoutMs: 1_800_000,
11
+ exploreMaxTurns: 50,
12
+ generalMaxTurns: 100,
13
+ };
14
+ export const DEFAULT_BROWSER_CONFIG = {
15
+ enabled: true,
16
+ profileId: 'default',
17
+ developerMode: false,
18
+ allowedCdpOrigins: [],
19
+ };
20
+ export const DEFAULT_COMPUTER_USE_CONFIG = {
21
+ enabled: true,
22
+ imagePolicy: 'single-adaptive',
23
+ maxImagePixels: 1_600_000,
24
+ observationFrames: 1,
25
+ };
@@ -0,0 +1,33 @@
1
+ import type {
2
+ BrowserConfig,
3
+ ComputerUseConfig,
4
+ SubagentsConfig,
5
+ } from './configFile.js';
6
+
7
+ /**
8
+ * 浏览器与 Node/Bun 都可安全加载的纯配置默认值。
9
+ * 不得在此模块引入 node:*、文件系统或运行时路径逻辑。
10
+ */
11
+ export const DEFAULT_SUBAGENTS_CONFIG: SubagentsConfig = {
12
+ enabled: true,
13
+ maxCallsPerTurn: 6,
14
+ maxConcurrent: 3,
15
+ defaultWriteIsolation: 'none',
16
+ timeoutMs: 1_800_000,
17
+ exploreMaxTurns: 50,
18
+ generalMaxTurns: 100,
19
+ };
20
+
21
+ export const DEFAULT_BROWSER_CONFIG: BrowserConfig = {
22
+ enabled: true,
23
+ profileId: 'default',
24
+ developerMode: false,
25
+ allowedCdpOrigins: [],
26
+ };
27
+
28
+ export const DEFAULT_COMPUTER_USE_CONFIG: ComputerUseConfig = {
29
+ enabled: true,
30
+ imagePolicy: 'single-adaptive',
31
+ maxImagePixels: 1_600_000,
32
+ observationFrames: 1,
33
+ };
@@ -5,27 +5,8 @@ import { dirname, join } from 'node:path';
5
5
  import { execSiblingDir } from '../utils/greenRoot.js';
6
6
  import { normalizePromptCacheMode } from '../llm/promptCache.js';
7
7
  import { CONFIG_LIMITS, normalizeLimited } from './numericLimits.js';
8
- export const DEFAULT_SUBAGENTS_CONFIG = {
9
- enabled: true,
10
- maxCallsPerTurn: 6,
11
- maxConcurrent: 3,
12
- defaultWriteIsolation: 'none',
13
- timeoutMs: 1_800_000,
14
- exploreMaxTurns: 50,
15
- generalMaxTurns: 100,
16
- };
17
- export const DEFAULT_BROWSER_CONFIG = {
18
- enabled: true,
19
- profileId: 'default',
20
- developerMode: false,
21
- allowedCdpOrigins: [],
22
- };
23
- export const DEFAULT_COMPUTER_USE_CONFIG = {
24
- enabled: true,
25
- imagePolicy: 'single-adaptive',
26
- maxImagePixels: 1_600_000,
27
- observationFrames: 1,
28
- };
8
+ import { DEFAULT_BROWSER_CONFIG, DEFAULT_COMPUTER_USE_CONFIG, DEFAULT_SUBAGENTS_CONFIG, } from './configDefaults.js';
9
+ export { DEFAULT_BROWSER_CONFIG, DEFAULT_COMPUTER_USE_CONFIG, DEFAULT_SUBAGENTS_CONFIG, } from './configDefaults.js';
29
10
  const DEFAULT_CONTEXT_WINDOW = 128_000;
30
11
  const LOCK_STALE_MS = 120_000;
31
12
  const LOCK_WAIT_MS = 15_000;
@@ -7,6 +7,17 @@ import { execSiblingDir } from '../utils/greenRoot.js';
7
7
  import type { PromptCacheConfig } from '../types.js';
8
8
  import { normalizePromptCacheMode } from '../llm/promptCache.js';
9
9
  import { CONFIG_LIMITS, normalizeLimited } from './numericLimits.js';
10
+ import {
11
+ DEFAULT_BROWSER_CONFIG,
12
+ DEFAULT_COMPUTER_USE_CONFIG,
13
+ DEFAULT_SUBAGENTS_CONFIG,
14
+ } from './configDefaults.js';
15
+
16
+ export {
17
+ DEFAULT_BROWSER_CONFIG,
18
+ DEFAULT_COMPUTER_USE_CONFIG,
19
+ DEFAULT_SUBAGENTS_CONFIG,
20
+ } from './configDefaults.js';
10
21
 
11
22
  export type ImageProtocol = 'auto' | 'chat' | 'images';
12
23
  export type ApiFormat = 'openai' | 'anthropic';
@@ -95,30 +106,6 @@ export interface MinimalAgentConfig {
95
106
  savedAt: number;
96
107
  }
97
108
 
98
- export const DEFAULT_SUBAGENTS_CONFIG: SubagentsConfig = {
99
- enabled: true,
100
- maxCallsPerTurn: 6,
101
- maxConcurrent: 3,
102
- defaultWriteIsolation: 'none',
103
- timeoutMs: 1_800_000,
104
- exploreMaxTurns: 50,
105
- generalMaxTurns: 100,
106
- };
107
-
108
- export const DEFAULT_BROWSER_CONFIG: BrowserConfig = {
109
- enabled: true,
110
- profileId: 'default',
111
- developerMode: false,
112
- allowedCdpOrigins: [],
113
- };
114
-
115
- export const DEFAULT_COMPUTER_USE_CONFIG: ComputerUseConfig = {
116
- enabled: true,
117
- imagePolicy: 'single-adaptive',
118
- maxImagePixels: 1_600_000,
119
- observationFrames: 1,
120
- };
121
-
122
109
  /**
123
110
  * 加模型是提交式弹窗、顾问改字段都是有界枚举/数字——不会出现「输入中途非法值」,
124
111
  * 所以 v3 时代的 provider/advisor 草稿桶随 schema 一起删除,只剩 tools(imageGeneration/
@@ -1,4 +1,4 @@
1
- import { DEFAULT_BROWSER_CONFIG, DEFAULT_COMPUTER_USE_CONFIG, } from './configFile.js';
1
+ import { DEFAULT_BROWSER_CONFIG, DEFAULT_COMPUTER_USE_CONFIG, } from './configDefaults.js';
2
2
  /**
3
3
  * CLI 和 Desktop 共用的脱敏配置视图。这里不返回任何凭据,也不读取 Runtime
4
4
  * 状态;它只是把 V4 权威配置稳定地投影成可展示、可比较的管理模型。
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  DEFAULT_BROWSER_CONFIG,
3
3
  DEFAULT_COMPUTER_USE_CONFIG,
4
- type ModelConfig,
5
- } from './configFile.js';
4
+ } from './configDefaults.js';
5
+ import type { ModelConfig } from './configFile.js';
6
6
  import type { ConfigSnapshot } from './configService.js';
7
7
 
8
8
  export type ManagementStatus = 'unconfigured' | 'configured' | 'incomplete';
@@ -373,6 +373,30 @@ export class ConfigService {
373
373
  return null;
374
374
  return { ...advisor, provider: resolveProvider(entry), supportsVision: entry.supportsVision !== false };
375
375
  }
376
+ /**
377
+ * 同步版 advisor 可用性探测——仅用于 resolveAvailability 这类工具列表探测场景。
378
+ * 读当前内存快照(可能略滞后于磁盘最新值),但调用频率高、对实时性要求不高,
379
+ * 下一轮 LLM 请求前 configService.reload() 会刷新。
380
+ *
381
+ * 为什么不用 advisor() 的 fresh reload:
382
+ * resolveAvailability 是在主循环构造工具集时同步调用的,不能 await;
383
+ * 而且每次工具集构造都读盘的话开销太大(主循环每轮都可能构造一次)。
384
+ */
385
+ advisorAvailabilitySync() {
386
+ const config = this.snapshot.config;
387
+ const advisor = config?.advisor;
388
+ if (!advisor?.modelId)
389
+ return { enabled: false, mode: 'auto', modelId: null, configured: false };
390
+ const entry = config?.models?.find((m) => m.id === advisor.modelId);
391
+ if (!entry?.apiKey)
392
+ return { enabled: false, mode: 'auto', modelId: null, configured: false };
393
+ return {
394
+ enabled: advisor.enabled !== false,
395
+ mode: advisor.mode === 'manual' ? 'manual' : 'auto',
396
+ modelId: advisor.modelId,
397
+ configured: true,
398
+ };
399
+ }
376
400
  async subagents() {
377
401
  return (await this.reload()).config?.subagents ?? DEFAULT_SUBAGENTS_CONFIG;
378
402
  }
@@ -431,6 +431,29 @@ export class ConfigService {
431
431
  return { ...advisor, provider: resolveProvider(entry), supportsVision: entry.supportsVision !== false };
432
432
  }
433
433
 
434
+ /**
435
+ * 同步版 advisor 可用性探测——仅用于 resolveAvailability 这类工具列表探测场景。
436
+ * 读当前内存快照(可能略滞后于磁盘最新值),但调用频率高、对实时性要求不高,
437
+ * 下一轮 LLM 请求前 configService.reload() 会刷新。
438
+ *
439
+ * 为什么不用 advisor() 的 fresh reload:
440
+ * resolveAvailability 是在主循环构造工具集时同步调用的,不能 await;
441
+ * 而且每次工具集构造都读盘的话开销太大(主循环每轮都可能构造一次)。
442
+ */
443
+ advisorAvailabilitySync(): { enabled: boolean; mode: 'auto' | 'manual'; modelId: string | null; configured: boolean } {
444
+ const config = this.snapshot.config;
445
+ const advisor = config?.advisor;
446
+ if (!advisor?.modelId) return { enabled: false, mode: 'auto', modelId: null, configured: false };
447
+ const entry = config?.models?.find((m) => m.id === advisor.modelId);
448
+ if (!entry?.apiKey) return { enabled: false, mode: 'auto', modelId: null, configured: false };
449
+ return {
450
+ enabled: advisor.enabled !== false,
451
+ mode: advisor.mode === 'manual' ? 'manual' : 'auto',
452
+ modelId: advisor.modelId,
453
+ configured: true,
454
+ };
455
+ }
456
+
434
457
  async subagents(): Promise<SubagentsConfig> {
435
458
  return (await this.reload()).config?.subagents ?? DEFAULT_SUBAGENTS_CONFIG;
436
459
  }
@@ -379,6 +379,10 @@ async function runCompaction(messages, provider, signal, chatFn = chat, promptCa
379
379
  ...(systemMsg ? [systemMsg] : []),
380
380
  {
381
381
  role: 'user',
382
+ // ⚠️ 这里**故意不带** provider 原始报错:本条消息会进 history,既落盘也会在
383
+ // 后续轮次重新发给模型,错误体里可能夹带 URL / 请求体 / 凭据。
384
+ // 排障线索走 `compact_failed` 事件(只到 stderr / UI,不进 history)。
385
+ // `compact.test.ts::reports snip fallback without exposing the raw LLM error` 锁定此约定。
382
386
  content: '(LLM 摘要压缩失败,已使用规则式截断继续会话)'
383
387
  + (failureLedger
384
388
  ? `\n\n<user_messages>\n以下是被截掉的那部分对话里**用户的原话**(程序逐字抽取)。摘要不存在,这里就是唯一残留的历史要求。\n${failureLedger}\n</user_messages>`
@@ -491,6 +491,10 @@ async function runCompaction(
491
491
  ...(systemMsg ? [systemMsg] : []),
492
492
  {
493
493
  role: 'user',
494
+ // ⚠️ 这里**故意不带** provider 原始报错:本条消息会进 history,既落盘也会在
495
+ // 后续轮次重新发给模型,错误体里可能夹带 URL / 请求体 / 凭据。
496
+ // 排障线索走 `compact_failed` 事件(只到 stderr / UI,不进 history)。
497
+ // `compact.test.ts::reports snip fallback without exposing the raw LLM error` 锁定此约定。
494
498
  content: '(LLM 摘要压缩失败,已使用规则式截断继续会话)'
495
499
  + (failureLedger
496
500
  ? `\n\n<user_messages>\n以下是被截掉的那部分对话里**用户的原话**(程序逐字抽取)。摘要不存在,这里就是唯一残留的历史要求。\n${failureLedger}\n</user_messages>`