berry-agent 0.1.0-alpha.1

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 (401) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +96 -0
  3. package/dist/.api-emit.stamp +1 -0
  4. package/dist/.build-meta.json +6 -0
  5. package/dist/agent/events.js +1 -0
  6. package/dist/agent/index.js +2 -0
  7. package/dist/agent/loop.js +139 -0
  8. package/dist/agent/queue.js +119 -0
  9. package/dist/agent/stream.js +62 -0
  10. package/dist/agent/tools-batch.js +221 -0
  11. package/dist/agent/types.js +8 -0
  12. package/dist/api/berry-agent-llm.d.ts +18 -0
  13. package/dist/api/berry-agent.d.ts +9 -0
  14. package/dist/api/surface.json +1632 -0
  15. package/dist/api/tsconfig.paths.json +12 -0
  16. package/dist/api/typebox-compile.d.ts +2 -0
  17. package/dist/api/typebox-value.d.ts +2 -0
  18. package/dist/api/typebox.d.ts +2 -0
  19. package/dist/browser/cdp.js +256 -0
  20. package/dist/browser/codes.js +31 -0
  21. package/dist/browser/discover.js +107 -0
  22. package/dist/browser/engine.js +169 -0
  23. package/dist/browser/index.js +17 -0
  24. package/dist/browser/install.js +342 -0
  25. package/dist/browser/page.js +321 -0
  26. package/dist/browser/service.js +158 -0
  27. package/dist/browser/tools.js +198 -0
  28. package/dist/browser/types.js +81 -0
  29. package/dist/channels/ask-queue.js +82 -0
  30. package/dist/channels/commands.js +129 -0
  31. package/dist/channels/engine/cell.js +143 -0
  32. package/dist/channels/engine/diff.js +143 -0
  33. package/dist/channels/engine/engine.js +416 -0
  34. package/dist/channels/engine/index.js +8 -0
  35. package/dist/channels/engine/input-keys.js +86 -0
  36. package/dist/channels/engine/input.js +596 -0
  37. package/dist/channels/engine/memory-io.js +71 -0
  38. package/dist/channels/engine/process-io.js +38 -0
  39. package/dist/channels/engine/types.js +17 -0
  40. package/dist/channels/engine/width.js +122 -0
  41. package/dist/channels/index.js +15 -0
  42. package/dist/channels/registry.js +56 -0
  43. package/dist/channels/sdk/admit.js +29 -0
  44. package/dist/channels/sdk/backend.js +102 -0
  45. package/dist/channels/sdk/cursor.js +56 -0
  46. package/dist/channels/sdk/index.js +20 -0
  47. package/dist/channels/sdk/jsonl.js +208 -0
  48. package/dist/channels/sdk/protocol.js +24 -0
  49. package/dist/channels/sdk/schema.js +91 -0
  50. package/dist/channels/sdk/wire-core.js +446 -0
  51. package/dist/channels/service.js +169 -0
  52. package/dist/channels/tui/autocomplete/autocomplete.js +42 -0
  53. package/dist/channels/tui/autocomplete/file-mentions.js +106 -0
  54. package/dist/channels/tui/autocomplete/popup.js +148 -0
  55. package/dist/channels/tui/autocomplete/provider.js +7 -0
  56. package/dist/channels/tui/autocomplete/token.js +68 -0
  57. package/dist/channels/tui/backend/ansi-rows.js +211 -0
  58. package/dist/channels/tui/backend/main-screen.js +207 -0
  59. package/dist/channels/tui/backend/osc.js +91 -0
  60. package/dist/channels/tui/backend/transcript.js +230 -0
  61. package/dist/channels/tui/backend/tui-backend.js +915 -0
  62. package/dist/channels/tui/editor/editor-model.js +543 -0
  63. package/dist/channels/tui/editor/editor-view.js +140 -0
  64. package/dist/channels/tui/editor/editor.js +275 -0
  65. package/dist/channels/tui/editor/undo-stack.js +35 -0
  66. package/dist/channels/tui/editor/visual-lines.js +126 -0
  67. package/dist/channels/tui/editor/word-nav.js +106 -0
  68. package/dist/channels/tui/history/history-viewer.js +403 -0
  69. package/dist/channels/tui/index.js +3 -0
  70. package/dist/channels/tui/layout.js +167 -0
  71. package/dist/channels/tui/markdown/blocks.js +111 -0
  72. package/dist/channels/tui/markdown/inline.js +75 -0
  73. package/dist/channels/tui/markdown/markdown.js +182 -0
  74. package/dist/channels/tui/memory/memory-viewer.js +540 -0
  75. package/dist/channels/tui/overlay/alt-screen.js +105 -0
  76. package/dist/channels/tui/overlay/overlay.js +57 -0
  77. package/dist/channels/tui/overlay/select-confirm.js +156 -0
  78. package/dist/channels/tui/panels/todo-panel.js +54 -0
  79. package/dist/channels/tui/panels/tool-progress-panel.js +93 -0
  80. package/dist/channels/tui/scroll/scroll-view.js +254 -0
  81. package/dist/channels/tui/status/status-line.js +76 -0
  82. package/dist/channels/tui/text.js +38 -0
  83. package/dist/channels/tui/theme.js +41 -0
  84. package/dist/channels/types.js +11 -0
  85. package/dist/channels/ui-core.js +209 -0
  86. package/dist/checkpoint/capture.js +45 -0
  87. package/dist/checkpoint/codes.js +33 -0
  88. package/dist/checkpoint/command.js +82 -0
  89. package/dist/checkpoint/gate.js +48 -0
  90. package/dist/checkpoint/index.js +20 -0
  91. package/dist/checkpoint/restore.js +172 -0
  92. package/dist/checkpoint/store.js +247 -0
  93. package/dist/checkpoint/types.js +4 -0
  94. package/dist/checkpoint/walk.js +146 -0
  95. package/dist/compaction/ccr-tools.js +112 -0
  96. package/dist/compaction/ccr.js +84 -0
  97. package/dist/compaction/codes.js +21 -0
  98. package/dist/compaction/index.js +18 -0
  99. package/dist/compaction/policy.js +200 -0
  100. package/dist/compaction/service.js +422 -0
  101. package/dist/compaction/slots.js +121 -0
  102. package/dist/compaction/types.js +10 -0
  103. package/dist/context/codes.js +40 -0
  104. package/dist/context/events.js +160 -0
  105. package/dist/context/index.js +13 -0
  106. package/dist/context/logger.js +136 -0
  107. package/dist/context/scope.js +182 -0
  108. package/dist/context/workspace.js +104 -0
  109. package/dist/contracts/agent-events.d.ts +73 -0
  110. package/dist/contracts/agent-events.js +16 -0
  111. package/dist/contracts/api.d.ts +249 -0
  112. package/dist/contracts/api.js +299 -0
  113. package/dist/contracts/approval.d.ts +37 -0
  114. package/dist/contracts/approval.js +9 -0
  115. package/dist/contracts/env-ref.d.ts +41 -0
  116. package/dist/contracts/env-ref.js +32 -0
  117. package/dist/contracts/errors.d.ts +56 -0
  118. package/dist/contracts/errors.js +266 -0
  119. package/dist/contracts/events.d.ts +70 -0
  120. package/dist/contracts/events.js +329 -0
  121. package/dist/contracts/index.d.ts +32 -0
  122. package/dist/contracts/index.js +26 -0
  123. package/dist/contracts/llm.d.ts +275 -0
  124. package/dist/contracts/llm.js +12 -0
  125. package/dist/contracts/messages.d.ts +63 -0
  126. package/dist/contracts/messages.js +65 -0
  127. package/dist/contracts/redact.d.ts +46 -0
  128. package/dist/contracts/redact.js +223 -0
  129. package/dist/contracts/tools.d.ts +197 -0
  130. package/dist/contracts/tools.js +33 -0
  131. package/dist/contracts/types.d.ts +302 -0
  132. package/dist/contracts/types.js +50 -0
  133. package/dist/contracts/ui.d.ts +122 -0
  134. package/dist/contracts/ui.js +15 -0
  135. package/dist/conversation/agent-service.js +61 -0
  136. package/dist/conversation/approval-wiring.js +82 -0
  137. package/dist/conversation/backoff.js +38 -0
  138. package/dist/conversation/codes.js +47 -0
  139. package/dist/conversation/control-tools.js +102 -0
  140. package/dist/conversation/control.js +210 -0
  141. package/dist/conversation/driver.js +922 -0
  142. package/dist/conversation/index.js +12 -0
  143. package/dist/conversation/model-visible.js +169 -0
  144. package/dist/conversation/open-tools.js +126 -0
  145. package/dist/conversation/reseed.js +132 -0
  146. package/dist/conversation/sessions.js +215 -0
  147. package/dist/conversation/todo.js +203 -0
  148. package/dist/conversation/types.js +35 -0
  149. package/dist/conversation/wiring.js +204 -0
  150. package/dist/credentials/codes.js +59 -0
  151. package/dist/credentials/commands.js +186 -0
  152. package/dist/credentials/env-ref.js +34 -0
  153. package/dist/credentials/index.js +28 -0
  154. package/dist/credentials/migration.js +18 -0
  155. package/dist/credentials/oauth.js +266 -0
  156. package/dist/credentials/refresh.js +152 -0
  157. package/dist/credentials/secrets.js +118 -0
  158. package/dist/credentials/types.js +30 -0
  159. package/dist/exec/bash.js +279 -0
  160. package/dist/exec/codes.js +37 -0
  161. package/dist/exec/env.js +99 -0
  162. package/dist/exec/environment.js +39 -0
  163. package/dist/exec/git-guard.js +623 -0
  164. package/dist/exec/index.js +24 -0
  165. package/dist/exec/registry.js +120 -0
  166. package/dist/exec/spawn.js +281 -0
  167. package/dist/exec/tail.js +77 -0
  168. package/dist/exec/types.js +5 -0
  169. package/dist/goal/codes.js +33 -0
  170. package/dist/goal/command.js +175 -0
  171. package/dist/goal/fold.js +121 -0
  172. package/dist/goal/gates.js +122 -0
  173. package/dist/goal/index.js +22 -0
  174. package/dist/goal/migration.js +48 -0
  175. package/dist/goal/service.js +501 -0
  176. package/dist/goal/todo-tool.js +174 -0
  177. package/dist/goal/types.js +1 -0
  178. package/dist/goal/update-tool.js +46 -0
  179. package/dist/host/approval-cmd.js +276 -0
  180. package/dist/host/assembly.js +1146 -0
  181. package/dist/host/boot-failures.js +104 -0
  182. package/dist/host/budget-advisory.js +74 -0
  183. package/dist/host/budget-broadcast.js +50 -0
  184. package/dist/host/builtins.js +54 -0
  185. package/dist/host/cli.js +544 -0
  186. package/dist/host/codes.js +120 -0
  187. package/dist/host/config-schema.js +195 -0
  188. package/dist/host/conversation-stack.js +654 -0
  189. package/dist/host/core-plugins.js +1685 -0
  190. package/dist/host/credentials-cmd.js +42 -0
  191. package/dist/host/disclosure.js +30 -0
  192. package/dist/host/dispatch.js +105 -0
  193. package/dist/host/doors-cmd.js +154 -0
  194. package/dist/host/dump-config.js +44 -0
  195. package/dist/host/hook-dispatch-guard.js +27 -0
  196. package/dist/host/import-gate.js +153 -0
  197. package/dist/host/index.js +54 -0
  198. package/dist/host/issue-session.js +241 -0
  199. package/dist/host/loader.js +372 -0
  200. package/dist/host/main.js +161 -0
  201. package/dist/host/manifest.js +381 -0
  202. package/dist/host/mcp-entry.js +53 -0
  203. package/dist/host/plugin-boot.js +952 -0
  204. package/dist/host/plugin-context.js +672 -0
  205. package/dist/host/plugin-install.js +480 -0
  206. package/dist/host/plugin-reload.js +124 -0
  207. package/dist/host/plugin-store.js +462 -0
  208. package/dist/host/plugin-tools.js +359 -0
  209. package/dist/host/plugin-uninstall.js +257 -0
  210. package/dist/host/plugins-cmd.js +302 -0
  211. package/dist/host/plugins-command.js +130 -0
  212. package/dist/host/plugins-config.js +195 -0
  213. package/dist/host/prompt-sections.js +133 -0
  214. package/dist/host/run-entry.js +544 -0
  215. package/dist/host/runtime.js +209 -0
  216. package/dist/host/scheduler-tick.js +294 -0
  217. package/dist/host/serve-daemon.js +501 -0
  218. package/dist/host/serve-entry.js +279 -0
  219. package/dist/host/session-anchor.js +41 -0
  220. package/dist/host/sessions-cmd.js +219 -0
  221. package/dist/host/sessions-face.js +47 -0
  222. package/dist/host/settings-store.js +112 -0
  223. package/dist/host/signals.js +69 -0
  224. package/dist/host/single-instance.js +105 -0
  225. package/dist/host/subagent-factory.js +291 -0
  226. package/dist/host/testkit/harness.js +283 -0
  227. package/dist/host/testkit/index.js +14 -0
  228. package/dist/host/testkit/matrix.js +302 -0
  229. package/dist/host/tool-policy-store.js +177 -0
  230. package/dist/host/triggers.js +212 -0
  231. package/dist/host/tui-entry.js +216 -0
  232. package/dist/host/webui-bridge.js +165 -0
  233. package/dist/issue/codes.js +38 -0
  234. package/dist/issue/filter.js +146 -0
  235. package/dist/issue/github.js +116 -0
  236. package/dist/issue/index.js +17 -0
  237. package/dist/issue/mount.js +70 -0
  238. package/dist/issue/poll.js +58 -0
  239. package/dist/issue/service.js +341 -0
  240. package/dist/issue/tools.js +70 -0
  241. package/dist/issue/types.js +34 -0
  242. package/dist/issue/webhook.js +126 -0
  243. package/dist/llm/codes.js +77 -0
  244. package/dist/llm/complete.js +196 -0
  245. package/dist/llm/events.js +15 -0
  246. package/dist/llm/index.js +24 -0
  247. package/dist/llm/inflight.js +65 -0
  248. package/dist/llm/model-id.js +58 -0
  249. package/dist/llm/provider-face.d.ts +32 -0
  250. package/dist/llm/provider-face.js +34 -0
  251. package/dist/llm/recovery.js +146 -0
  252. package/dist/llm/runtime.js +46 -0
  253. package/dist/llm/stream-fn.js +147 -0
  254. package/dist/lsp/codes.js +31 -0
  255. package/dist/lsp/connection.js +147 -0
  256. package/dist/lsp/frame.js +110 -0
  257. package/dist/lsp/index.js +16 -0
  258. package/dist/lsp/inject.js +58 -0
  259. package/dist/lsp/instance.js +264 -0
  260. package/dist/lsp/service.js +522 -0
  261. package/dist/lsp/tools.js +53 -0
  262. package/dist/lsp/types.js +201 -0
  263. package/dist/mcp/bridge.js +222 -0
  264. package/dist/mcp/codes.js +21 -0
  265. package/dist/mcp/index.js +19 -0
  266. package/dist/mcp/jsonrpc.js +230 -0
  267. package/dist/mcp/service.js +125 -0
  268. package/dist/mcp/tools.js +145 -0
  269. package/dist/mcp/types.js +108 -0
  270. package/dist/memory/cite.js +131 -0
  271. package/dist/memory/codes.js +50 -0
  272. package/dist/memory/command.js +71 -0
  273. package/dist/memory/consolidate.js +219 -0
  274. package/dist/memory/cycle.js +109 -0
  275. package/dist/memory/dao.js +878 -0
  276. package/dist/memory/diff.js +238 -0
  277. package/dist/memory/extract.js +181 -0
  278. package/dist/memory/fts.js +58 -0
  279. package/dist/memory/index.js +42 -0
  280. package/dist/memory/inject.js +354 -0
  281. package/dist/memory/merge.js +178 -0
  282. package/dist/memory/migration.js +132 -0
  283. package/dist/memory/pollution.js +59 -0
  284. package/dist/memory/port.js +385 -0
  285. package/dist/memory/review.js +201 -0
  286. package/dist/memory/scan.js +89 -0
  287. package/dist/memory/tools.js +508 -0
  288. package/dist/memory/types.js +140 -0
  289. package/dist/obs/codes.js +38 -0
  290. package/dist/obs/db.js +115 -0
  291. package/dist/obs/index.js +20 -0
  292. package/dist/obs/rollup.js +61 -0
  293. package/dist/obs/service.js +363 -0
  294. package/dist/obs/session-tools.js +282 -0
  295. package/dist/obs/session-view.js +225 -0
  296. package/dist/obs/tool.js +97 -0
  297. package/dist/obs/types.js +1 -0
  298. package/dist/persist/audit.js +63 -0
  299. package/dist/persist/aux.js +39 -0
  300. package/dist/persist/codes.js +35 -0
  301. package/dist/persist/index.js +30 -0
  302. package/dist/persist/load-history.js +50 -0
  303. package/dist/persist/migrations.js +37 -0
  304. package/dist/persist/paths.js +108 -0
  305. package/dist/persist/persistence.js +229 -0
  306. package/dist/persist/schema.js +99 -0
  307. package/dist/persist/secret-box.js +95 -0
  308. package/dist/persist/store.js +706 -0
  309. package/dist/persist/write-behind.js +238 -0
  310. package/dist/safety/approval.js +171 -0
  311. package/dist/safety/bwrap.js +123 -0
  312. package/dist/safety/codes.js +57 -0
  313. package/dist/safety/danger.js +505 -0
  314. package/dist/safety/gate.js +226 -0
  315. package/dist/safety/index.js +24 -0
  316. package/dist/safety/presets.js +73 -0
  317. package/dist/safety/roots.js +139 -0
  318. package/dist/safety/sandbox.js +214 -0
  319. package/dist/safety/seatbelt.js +88 -0
  320. package/dist/safety/sensitive.js +34 -0
  321. package/dist/safety/tool-policy.js +203 -0
  322. package/dist/safety/types.js +13 -0
  323. package/dist/scheduler/codes.js +54 -0
  324. package/dist/scheduler/cron-backend.js +135 -0
  325. package/dist/scheduler/engine.js +280 -0
  326. package/dist/scheduler/gates.js +60 -0
  327. package/dist/scheduler/index.js +25 -0
  328. package/dist/scheduler/migration.js +22 -0
  329. package/dist/scheduler/runner.js +171 -0
  330. package/dist/scheduler/schedule.js +193 -0
  331. package/dist/scheduler/service.js +283 -0
  332. package/dist/scheduler/tick.js +139 -0
  333. package/dist/scheduler/types.js +1 -0
  334. package/dist/sdk/http.js +865 -0
  335. package/dist/sdk/index.js +22 -0
  336. package/dist/sdk/mcp.js +289 -0
  337. package/dist/sdk/plugin-route-registry.js +126 -0
  338. package/dist/sdk/plugin-routes.js +149 -0
  339. package/dist/sdk/security.js +116 -0
  340. package/dist/sdk/types.js +32 -0
  341. package/dist/session/budget.js +159 -0
  342. package/dist/session/codes.js +40 -0
  343. package/dist/session/derive.js +180 -0
  344. package/dist/session/event-data.js +1 -0
  345. package/dist/session/fork.js +72 -0
  346. package/dist/session/import-gates.js +148 -0
  347. package/dist/session/index.js +18 -0
  348. package/dist/session/recover.js +103 -0
  349. package/dist/session/session.js +279 -0
  350. package/dist/session/snapshot.js +107 -0
  351. package/dist/skills/agents.js +242 -0
  352. package/dist/skills/codes.js +51 -0
  353. package/dist/skills/discovery.js +266 -0
  354. package/dist/skills/frontmatter.js +215 -0
  355. package/dist/skills/index.js +28 -0
  356. package/dist/skills/load.js +118 -0
  357. package/dist/skills/manage.js +201 -0
  358. package/dist/skills/registry.js +138 -0
  359. package/dist/skills/render.js +124 -0
  360. package/dist/skills/sections.js +171 -0
  361. package/dist/skills/types.js +21 -0
  362. package/dist/subagent/codes.js +50 -0
  363. package/dist/subagent/declarative.js +55 -0
  364. package/dist/subagent/index.js +19 -0
  365. package/dist/subagent/notify.js +51 -0
  366. package/dist/subagent/provide.js +7 -0
  367. package/dist/subagent/registry.js +149 -0
  368. package/dist/subagent/service.js +338 -0
  369. package/dist/subagent/surface.js +44 -0
  370. package/dist/subagent/tool.js +153 -0
  371. package/dist/subagent/types.js +25 -0
  372. package/dist/tools/apply-patch.js +164 -0
  373. package/dist/tools/codes.js +109 -0
  374. package/dist/tools/fs.js +501 -0
  375. package/dist/tools/index.js +17 -0
  376. package/dist/tools/observed.js +99 -0
  377. package/dist/tools/pipeline.js +176 -0
  378. package/dist/tools/protected-read.js +54 -0
  379. package/dist/tools/registry.js +250 -0
  380. package/dist/tools/search.js +451 -0
  381. package/dist/tools/worktree.js +327 -0
  382. package/dist/web/codes.js +37 -0
  383. package/dist/web/gate.js +43 -0
  384. package/dist/web/hygiene.js +195 -0
  385. package/dist/web/index.js +16 -0
  386. package/dist/web/service.js +207 -0
  387. package/dist/web/ssrf-guard.js +44 -0
  388. package/dist/web/tool.js +76 -0
  389. package/dist/web/types.js +10 -0
  390. package/dist/webui/assets/index-B-t2O8gh.js +22 -0
  391. package/dist/webui/assets/index-DOlgSZ9Q.css +2 -0
  392. package/dist/webui/index.html +14 -0
  393. package/dist/webui/index.js +14 -0
  394. package/dist/webui/server.js +549 -0
  395. package/dist/webui/types.js +38 -0
  396. package/examples/README.md +26 -0
  397. package/examples/minimal-code-plugin/entry.js +43 -0
  398. package/examples/minimal-code-plugin/package.json +12 -0
  399. package/examples/pure-skill-pack/package.json +14 -0
  400. package/examples/pure-skill-pack/skills/markdown-table/SKILL.md +32 -0
  401. package/package.json +86 -0
@@ -0,0 +1,544 @@
1
+ /**
2
+ * host/run-entry — `berry-agent run` 单次执行入口装配(批 20b;07 §5 run
3
+ * 权威条文全量兑现)。
4
+ *
5
+ * 定位:CLI 单发的薄编舞层——装配公共段(assembly——与 TUI/serve 同一合成
6
+ * 代码路径)+ tick 双形态预解析 + 会话选取四形 + `cli-run` 通道后端(事件
7
+ * 消费与输出三档)+ max-turns 到帽收场 + 退出码三态。模型循环本体归
8
+ * conversation 驱动,本件零侵入(maxTurns/输出档均为装配层消费位)。
9
+ *
10
+ * 输出三档(07 §5):
11
+ * - text(缺省):stdout 只出末条 assistant 文本——CI 管道友好的「问一句得
12
+ * 一句」;run 进行中的 keepalive 与一切诊断走 stderr(stdout 纯净律)。
13
+ * - json:终值单对象(sessionId/status/turns/usage 汇总/末条 assistant 文本)
14
+ * 一行 NDJSON——机器消费面;活体增量不出 stdout。
15
+ * - stream:AgentEvent NDJSON 直出(每事件一行)——与 serve 线协议同源的
16
+ * 事件流面;心跳即事件流一员(工具/turn 族事件自带节拍)不另设 keepalive。
17
+ * --no-delta:三档共用的线面退订(message_update 增量滤除;整消息事件仍在)。
18
+ *
19
+ * 退出码三态(07 §5):0 成功收场;1 执行失败(failed·aborted·truncated
20
+ * 同档);2 用法错。2 档除解析层 argv 执法外,本层再收运行期才能判定的
21
+ * 配置态:tick 行缺席 = jobs 配置漂移(add 过的行被删改)、件缺席形
22
+ * (--no-plugins 下 --tick 不可用)。
23
+ * 铁律:错误路径必及时非零退出(挂死即 P0)。
24
+ *
25
+ * headless 审批无挂死(04 §9 无静默审批):本后端 capabilities 阻塞原语
26
+ * 全 false——capability false 即不参与竞速;waterfall 无人应答 → 立即
27
+ * unavailable(source='timeout'),run 不挂。
28
+ *
29
+ * --background 记账(04 §5 后台道入口):起跑前 canAfford('background')
30
+ * 预检(拒在起跑前——零跑零账);settle 后对本次 run 新增的各 assistant
31
+ * 消息落 llm/usage 底账(deterministic callId `run:<sessionId>:<seq>`——
32
+ * write-behind 批落重试的去重锚点;priority background——聚合只计后台道)。
33
+ *
34
+ * goal 编舞(19c-3 挂账批 #99 全数兑现——驱动侧三件落 driver/assembly):
35
+ * recordTurn 记账/agent_pre_step 预算复验/轮间沉淀全经 driver 层 seam,本
36
+ * 入口零 goal 感知(三入口统一——TUI/webui/issue 同律零重复挂点)。
37
+ */
38
+ import { renameSync, writeFileSync } from 'node:fs';
39
+ import { cwd as processCwd, pid, stderr as processStderr, stdout as processStdout } from 'node:process';
40
+ import { canonicalWorkspaceRoot } from '../context/index.js';
41
+ import { isStandardMessage } from '../contracts/index.js';
42
+ import { diagnoseProviderFailure } from '../llm/index.js';
43
+ import { FIRE_WALL_TIMEOUT_MS, nextFireAt, realIsPidAlive } from '../scheduler/index.js';
44
+ import { approvalPresetOf } from '../safety/index.js';
45
+ import { assembleHostStack } from './assembly.js';
46
+ import { openWebuiFace } from './webui-bridge.js';
47
+ /**
48
+ * run 主入口:装配 → 执行体 → 六步退出序 → 退出码。装配失败两档呈报同
49
+ * serve-entry(crashed = 意外异常,crash.log 已在装配件内写;干净退出档 =
50
+ * 启动失败——单活跃机/开库/启用清单损坏)。一切执行体异常 = 崩溃取证 +
51
+ * 退 1;shutdown 恒走(幂等——早退路径与信号路径同享 closer 保证)。
52
+ */
53
+ export async function runRunEntry(options) {
54
+ const out = options.stdout ?? processStdout;
55
+ const err = options.stderr ?? processStderr;
56
+ const outputFormat = options.flags.outputFormat ?? 'text';
57
+ // —— 装配公共段(批 19a-3 迁 assembly 件——与 TUI/serve 同一合成代码路径:
58
+ // 运行时→logger→共享根→栈→**插件装载**〔core 注册表 + enabled.yaml 真跑〕;
59
+ // --ephemeral → memory 形零落盘;--read-only → 沙箱档 read-only 单发覆盖;
60
+ // --preset <名> → 权限预设两旋钮逐次覆盖(ap-3——逐次生效不写盘,与
61
+ // --read-only 在解析层互斥;来源标注随旋钮注入供 /approval status 呈现)——
62
+ const assembly = await assembleHostStack({
63
+ runtime: {
64
+ ...(options.dataDir !== undefined ? { dataDir: options.dataDir } : {}),
65
+ ...(options.flags.ephemeral ? { memory: true } : {}),
66
+ },
67
+ noPlugins: options.flags.noPlugins,
68
+ // 快速试件透传(--plugin-file——03 §7 生态启动批 eco-3a;缺席形省键)
69
+ ...(options.flags.pluginFile !== undefined ? { pluginFile: options.flags.pluginFile } : {}),
70
+ debug: options.flags.debug,
71
+ version: options.version ?? '0.0.0-unknown',
72
+ ...(options.providers !== undefined ? { providers: options.providers } : {}),
73
+ ...(options.model !== undefined ? { model: options.model } : {}),
74
+ ...(options.env !== undefined ? { env: options.env } : {}),
75
+ ...(options.flags.readOnly
76
+ ? { sandboxMode: () => 'read-only', sandboxModeSource: 'CLI --read-only' }
77
+ : options.flags.preset !== undefined
78
+ ? {
79
+ sandboxMode: () => approvalPresetOf(options.flags.preset).sandboxMode,
80
+ sandboxModeSource: `CLI --preset ${options.flags.preset}`,
81
+ approvalPolicy: approvalPresetOf(options.flags.preset).approvalPolicy,
82
+ approvalPolicySource: `CLI --preset ${options.flags.preset}`,
83
+ }
84
+ : {}),
85
+ ...(options.onRuntime !== undefined ? { onRuntime: options.onRuntime } : {}),
86
+ });
87
+ if (!assembly.ok) {
88
+ err.write(`${assembly.crashed ? `run 执行失败:${assembly.message}` : assembly.message}\n`);
89
+ return assembly.exitCode;
90
+ }
91
+ const { runtime, stack, scope } = assembly;
92
+ let exitCode = 1;
93
+ try {
94
+ exitCode = await executeRun({ options, outputFormat, out, err, runtime, stack, scope });
95
+ }
96
+ catch (error) {
97
+ runtime.writeCrashLog(error); // 崩溃取证先行(memory 形跳过——件内语义)
98
+ err.write(`run 执行失败:${error instanceof Error ? error.message : String(error)}\n`);
99
+ exitCode = 1;
100
+ }
101
+ // 六步退出序(幂等——manager 拆解/write-behind flush 必达;信号路径已走
102
+ // shutdown 时同调无副作用)
103
+ await runtime.shutdown().catch(() => { });
104
+ return exitCode;
105
+ }
106
+ /** 执行体(装配成功后的全流程——一切 return 即本 run 的退出码) */
107
+ async function executeRun(ctx) {
108
+ const { options, outputFormat, out, err, runtime, stack, scope } = ctx;
109
+ const flags = options.flags;
110
+ // —— ⓪ tick settle 记账位(04 §12 无人值守执行链定形注律 3 乙案侧——
111
+ // 2026-09-13 全面复盘修复批:乙案让位律原单向兑现——子进程读行让位而自跑
112
+ // 零占用记账、收场零结算(lastOutcome 不落账/last_fire_at 不推进/僵行清扫
113
+ // 空转)。本批兑现双向:起跑 claim(activePid = 本 CLI pid——引擎侧跨进程
114
+ // 在飞判定与僵行清扫由此见本实例)+ 收场 settle(真跑 settleFire 三笔推进
115
+ // / 零跑 gated settleGated 只推进 next)。乙案形态引擎不在环——CLI 是该
116
+ // fire 的唯一记账方 ——
117
+ let tickJob;
118
+ /** 结算单点(单笔幂等——settle 后再呼零副作用;非 tick 形恒零副作用) */
119
+ const settleTick = (outcome) => {
120
+ if (tickJob === undefined)
121
+ return;
122
+ const { dao, row, firedAt } = tickJob;
123
+ tickJob = undefined;
124
+ const nowIso = new Date().toISOString();
125
+ // next 从结算时刻取下一刻(错过不重放同律——引擎 settle 段同锚)
126
+ const next = nextFireAt(row.schedule, new Date(nowIso));
127
+ if (outcome.reason === 'gated') {
128
+ dao.settleGated(row.name, outcome, next, nowIso);
129
+ }
130
+ else {
131
+ dao.settleFire(row.name, firedAt, outcome, next, nowIso);
132
+ }
133
+ // claim 清账(settle 对偶——settleFire/settleGated 不动 active_pid 列,本侧补清)
134
+ dao.setActive(row.name, null, null, nowIso);
135
+ };
136
+ /** tick 行早退收账(claim 后 run 未起跑形——spawn 形落账诚实记「没起来」) */
137
+ const abortTick = (error) => {
138
+ settleTick({
139
+ trigger: 'cron',
140
+ reason: 'spawn',
141
+ error: error.slice(0, 200),
142
+ finishedAt: new Date().toISOString(),
143
+ });
144
+ };
145
+ // —— ① tick 形预解析(jobs 行读取 + goal 挂钟行 wake 判定先行——提示词与
146
+ // 会话归属都可能来自行内,须在会话选取前定形)——
147
+ let message = options.message;
148
+ let cwdAnchor = options.cwd ?? processCwd();
149
+ let goalSessionId; // goal 挂钟行:提交目标 = goal 绑定会话
150
+ if (flags.tick !== undefined) {
151
+ const schedFace = scope.tryGet('scheduler');
152
+ if (schedFace === undefined) {
153
+ err.write('--tick 需要 core:scheduler 件在场(--no-plugins 或件禁用形态不可用)\n');
154
+ return 2;
155
+ }
156
+ const row = schedFace.service.getJob(flags.tick);
157
+ if (row === undefined) {
158
+ // 行缺席 = 配置漂移档(add 过的行被删/改名——cron/引擎侧 argv 过期)
159
+ err.write(`--tick 行缺席:jobs 表无「${flags.tick}」行(检查行是否被删改——/tick list 可查在册行)\n`);
160
+ return 2;
161
+ }
162
+ // —— 让位律(u-2 定形注③乙案并存窗子进程侧腿——与引擎 fire 前跨进程
163
+ // 在飞判定双向对称):行 activePid 非空非己且活体未超墙钟 = 宿主甲案/
164
+ // 他乙案实例真在飞 → 本子进程诚实让位(yielded 结局、next 原样推进、
165
+ // 不动 last_fire_at、退出码 0——让位非失败零新错误码);死残账(占位
166
+ // 进程已死)与超钟残账(活+超墙钟——pid 复用误判险不 yield)均清占用
167
+ // 照跑。跨进程不 kill(pid 复用误杀险——「执行前抢占」条款对跨进程实例
168
+ // 不越权);残账兜底 = 引擎侧 start 僵行清扫与 fire 覆写自愈。
169
+ if (row.activePid !== null && row.activePid !== pid) {
170
+ const nowIso = new Date().toISOString();
171
+ const startedAtMs = row.activeStartedAt !== null ? Date.parse(row.activeStartedAt) : 0;
172
+ if (realIsPidAlive(row.activePid) && Date.now() - startedAtMs <= FIRE_WALL_TIMEOUT_MS) {
173
+ const outcome = {
174
+ trigger: 'cron',
175
+ reason: 'yielded',
176
+ error: `他实例在飞(activePid ${row.activePid})——诚实让位不双跑`,
177
+ finishedAt: nowIso,
178
+ };
179
+ schedFace.dao.settleGated(row.name, outcome, row.nextFireAt, nowIso);
180
+ err.write(`--tick 行「${row.name}」他实例在飞(activePid ${row.activePid})——让位退出\n`);
181
+ return 0;
182
+ }
183
+ // 死残账/超钟残账:清占用照跑(引擎侧覆写自愈兜底)
184
+ schedFace.dao.setActive(row.name, null, null, nowIso);
185
+ }
186
+ if (row.builtin && row.name.startsWith('goal-')) {
187
+ // goal 挂钟行:wake 判定先行(重绑护栏/唤醒预算/停滞硬停全在 goal 服务
188
+ // 单源裁决);goal 会话由裁决选取——续接族旗标显式拒(不静默忽略)
189
+ const goalFace = scope.tryGet('goal');
190
+ if (goalFace === undefined) {
191
+ err.write('goal 挂钟行在场而 core:goal 件未装载——配置漂移(goal 唤醒不可用)\n');
192
+ return 2;
193
+ }
194
+ if (flags.session !== undefined || flags.continueLatest || flags.fork !== undefined) {
195
+ err.write('--tick goal 挂钟行与 --session/--continue/--fork 不兼容(goal 会话由 wake 裁决选取)\n');
196
+ return 2;
197
+ }
198
+ const goalId = row.name.slice('goal-'.length);
199
+ const decision = await goalFace.service.wake(goalId, {
200
+ // tick 载体即挂钟等价触发(CLI 手点与引擎 spawn 同 argv 不可分——归
201
+ // clock 道落账,归因串带行名可审计)
202
+ trigger: 'clock',
203
+ attribution: `tick:${row.name}`,
204
+ });
205
+ if (!decision.landed) {
206
+ // 诚实零跑:不落即本轮无事(inactive/stalled/wake_budget 各由 message
207
+ // 说明)——零跑零账退 0。gated 结局照记 + next 推进(2026-09-13 修复批:
208
+ // 引擎侧 spawnGoalRow gated 同律——乙案此前零 settle,行永 due 反复过
209
+ // wake 判定面)
210
+ const gatedAt = new Date().toISOString();
211
+ schedFace.dao.settleGated(row.name, {
212
+ trigger: 'cron',
213
+ reason: 'gated',
214
+ error: `goal「${goalId}」本轮未唤醒(${decision.reason})`,
215
+ finishedAt: gatedAt,
216
+ }, nextFireAt(row.schedule, new Date(gatedAt)), gatedAt);
217
+ err.write(`goal「${goalId}」本轮未唤醒(${decision.reason}):${decision.message}\n`);
218
+ return 0;
219
+ }
220
+ message = row.prompt; // 挂钟行 prompt = promptSnapshot 落账形
221
+ goalSessionId = decision.goal.sessionId;
222
+ }
223
+ else {
224
+ // 用户任务行:prompt 即提交文本;行内 cwd 锚优先(任务的工作区语义)
225
+ message = row.prompt;
226
+ if (row.cwd !== null)
227
+ cwdAnchor = row.cwd;
228
+ }
229
+ // 起跑 claim(律 3 乙案侧——2026-09-13 修复批):本 CLI 进程 pid 落占用面
230
+ // + 起跑钟;此后本 run 期引擎侧跨进程在飞判定见他实例让位、kill -9 残账
231
+ // 由僵行清扫回收(让位律由单向变双向真值)。收场对偶在 ⑨ settleTick。
232
+ const firedAtIso = new Date().toISOString();
233
+ schedFace.dao.setActive(row.name, pid, firedAtIso, firedAtIso);
234
+ tickJob = { dao: schedFace.dao, row, firedAt: firedAtIso };
235
+ }
236
+ // —— ② --background 预检(04 §5 后台道预算闸门——拒在起跑前,零跑零账)——
237
+ if (flags.background && !stack.llm.canAfford('background')) {
238
+ // tick 形同步 gated 落账(2026-09-13 修复批:引擎 daily_budget 门同律——
239
+ // 非真跑不动 last_fire_at、next 照推进;非 tick 形 settleTick 零副作用)
240
+ settleTick({
241
+ trigger: 'cron',
242
+ reason: 'gated',
243
+ gate: 'daily_budget',
244
+ error: '当日后台道预算不可负担(canAfford 假)',
245
+ finishedAt: new Date().toISOString(),
246
+ });
247
+ err.write('当日后台道预算已尽(LLM_BUDGET_EXCEEDED 语义——后台日池已满):明日再跑或去 --background 走前台道\n');
248
+ return 1;
249
+ }
250
+ // —— ③ 会话选取四形(缺省 = 新建——「每次 run 默认新会话」07 §5;goal 挂钟
251
+ // 行的会话由 wake 裁决先行,此处只幂等 open 落驱动)——
252
+ const workspaceRoot = canonicalWorkspaceRoot(cwdAnchor);
253
+ let sessionId;
254
+ if (goalSessionId !== undefined) {
255
+ try {
256
+ sessionId = stack.manager.open(goalSessionId).sessionId; // 幂等——goal 绑定会话续接
257
+ }
258
+ catch (error) {
259
+ abortTick('goal 会话打开失败');
260
+ err.write(`goal 会话打开失败:${error instanceof Error ? error.message : String(error)}\n`);
261
+ return 1;
262
+ }
263
+ }
264
+ else if (flags.session !== undefined) {
265
+ try {
266
+ sessionId = stack.manager.open(flags.session).sessionId; // missing 即 throw(fail-loud)
267
+ }
268
+ catch {
269
+ abortTick('--session 会话不存在');
270
+ err.write(`--session 会话不存在:${flags.session}\n`);
271
+ return 1;
272
+ }
273
+ }
274
+ else if (flags.continueLatest) {
275
+ // 与无参 TUI 同源选取键:cwd 归一根取最新——有则续接无则新建
276
+ sessionId = stack.openStartupSession(cwdAnchor).sessionId;
277
+ }
278
+ else if (flags.fork !== undefined) {
279
+ // 分叉目标:显式 id 或 cwd 最新;边界快照缺省 lastClosedBoundary
280
+ let sourceId = flags.fork.id;
281
+ if (sourceId === undefined) {
282
+ const [latest] = stack.manager.list({ workspaceRoot, limit: 1 });
283
+ if (latest === undefined) {
284
+ abortTick('--fork 无源会话');
285
+ err.write('--fork 未带 id 且当前工作区无既有会话——无从分叉\n');
286
+ return 1;
287
+ }
288
+ sourceId = latest.id;
289
+ }
290
+ let forked;
291
+ try {
292
+ forked = await stack.manager.fork(sourceId);
293
+ }
294
+ catch (error) {
295
+ abortTick('--fork 失败');
296
+ err.write(`--fork 失败:${error instanceof Error ? error.message : String(error)}\n`);
297
+ return 1;
298
+ }
299
+ if (forked.status === 'vetoed') {
300
+ // session_before_fork 钩子否决——非错误路径但 run 无法进行(退 1 执行失败档)
301
+ abortTick('--fork 被钩子否决');
302
+ err.write(`--fork 被钩子否决:${forked.reason}\n`);
303
+ return 1;
304
+ }
305
+ sessionId = forked.sessionId;
306
+ }
307
+ else {
308
+ sessionId = stack.manager.create({ workspaceRoot }).sessionId;
309
+ }
310
+ // —— ④ --port 咬合(18a-3' 三入口咬合——run 形与 serve 前台同段:面与 run
311
+ // 并存双活,closer 挂退出序;sdk 件缺席 = warn 不开面〔件禁用语义族〕)——
312
+ if (flags.port !== undefined) {
313
+ const sdkKit = scope.tryGet('sdk-http-face');
314
+ if (sdkKit === undefined) {
315
+ err.write('warn:core:sdk 件未装载——--port 人面不开(run 本体不受累)\n');
316
+ }
317
+ else {
318
+ const mountKit = scope.tryGet('webui-face-mount');
319
+ try {
320
+ await openWebuiFace({
321
+ stack,
322
+ runtime,
323
+ port: flags.port,
324
+ ...(mountKit !== undefined ? { mountKit } : {}),
325
+ ...(options.onWebuiOpen !== undefined ? { onOpen: options.onWebuiOpen } : {}),
326
+ });
327
+ }
328
+ catch (error) {
329
+ // 开面失败(如端口占用 EADDRINUSE)= 预期内环境态——干净呈报不写 crash.log
330
+ abortTick('--port 开面失败');
331
+ err.write(`--port 开面失败:${error instanceof Error ? error.message : String(error)}\n`);
332
+ return 1;
333
+ }
334
+ }
335
+ }
336
+ // —— ⑤ 事件消费位聚合态(onEnvelope 回调写、settle 段读——单后端单 run 单线程)——
337
+ let turnsEnded = 0;
338
+ let truncated = false; // --max-turns 到帽收场语义位(CLI 级——run 本体仍 aborted)
339
+ let lastAssistantText;
340
+ const usageSum = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0 };
341
+ const backend = {
342
+ id: 'cli-run',
343
+ // 阻塞原语全 false:headless 单发无人在屏——capability false 即不参与竞速
344
+ //(04 §9 无静默审批:waterfall 无人应答立即 unavailable,run 不挂死)
345
+ capabilities: {
346
+ notify: true,
347
+ confirm: false,
348
+ select: false,
349
+ input: false,
350
+ approval: false,
351
+ setStatus: false,
352
+ setWidget: false,
353
+ },
354
+ hasAudience: () => true, // stdout 消费者在场(管道/重定向/TTY 皆观众)
355
+ notify: (text, nOpts) => {
356
+ // stdout 纯净律:通知恒走 stderr;warn/error 带档前缀(人读分级)
357
+ const prefix = nOpts?.level === 'warn' || nOpts?.level === 'error' ? `[${nOpts.level}] ` : '';
358
+ err.write(`${prefix}${text}\n`);
359
+ },
360
+ onEnvelope: (env) => {
361
+ if (env.sessionId !== sessionId)
362
+ return; // 多会话信封——只消费本 run 的
363
+ const event = env.event;
364
+ // --no-delta:线面退订 message_update(07 §5——整消息事件不受影响)
365
+ if (flags.noDelta && event.type === 'message_update')
366
+ return;
367
+ // max-turns 执法位:turn_start 到达且已收满帽数 turn_end = 第 N+1 轮已
368
+ // 起步——到帽收场(interrupt 协作中止;自然收场不开第 N+1 轮故无误伤;
369
+ // turn_start 先于模型调用发射,abort 落位确定)
370
+ if (event.type === 'turn_start' && flags.maxTurns !== undefined && turnsEnded >= flags.maxTurns && !truncated) {
371
+ truncated = true;
372
+ stack.interrupt(sessionId);
373
+ return;
374
+ }
375
+ if (event.type === 'turn_end')
376
+ turnsEnded += 1;
377
+ if (event.type === 'message_end' && isStandardMessage(event.message) && event.message.role === 'assistant') {
378
+ const assistant = event.message;
379
+ // 末条 assistant 文本(text 块拼接——thinking 不入人读产物)
380
+ lastAssistantText = assistant.content
381
+ .filter((block) => block.type === 'text')
382
+ .map((block) => block.text)
383
+ .join('');
384
+ addUsageInto(usageSum, assistant.usage);
385
+ }
386
+ // stream 档:AgentEvent NDJSON 直出(07 §5——与 serve 线协议同源事件面)
387
+ if (outputFormat === 'stream')
388
+ out.write(`${JSON.stringify(event)}\n`);
389
+ },
390
+ };
391
+ stack.channels.addBackend(backend);
392
+ // keepalive(text/json 档——07 §5:stdout 纯净,活体行走 stderr;stream 档
393
+ // 心跳即事件流一员不另设)
394
+ const startedAt = Date.now();
395
+ const keepaliveTimer = outputFormat === 'stream'
396
+ ? undefined
397
+ : setInterval(() => {
398
+ err.write(`run 进行中(${Math.round((Date.now() - startedAt) / 1000)}s)\n`);
399
+ }, options.keepaliveIntervalMs ?? 5_000);
400
+ keepaliveTimer?.unref?.(); // 不拖进程生命周期(事件环外定时器)
401
+ // —— ⑥ 提交(fire-and-forget 面的 await 侧——回执即收场凭据;source 归因:
402
+ // tick 形 'schedule'〔挂钟调度触发〕、余 'channel:cli'〔CLI 管道喂入〕;
403
+ // backgroundLane 声明与后台道记账同条件〔flags.background——04 §5 修复批:
404
+ // 预警分族与记账同一车道判定〕)——
405
+ const source = flags.tick !== undefined ? 'schedule' : 'channel:cli';
406
+ const runPromise = stack.submitText(sessionId, message, {
407
+ source,
408
+ ...(flags.background ? { backgroundLane: true } : {}),
409
+ });
410
+ if (runPromise === undefined) {
411
+ // 理论不达防御:③四形必落驱动——到达即装配 bug,fail-loud(经外层崩溃档)
412
+ throw new Error(`会话 ${sessionId} 无驱动在册——提交序不可达`);
413
+ }
414
+ let result;
415
+ try {
416
+ result = await runPromise;
417
+ }
418
+ finally {
419
+ if (keepaliveTimer !== undefined)
420
+ clearInterval(keepaliveTimer);
421
+ stack.channels.removeBackend('cli-run'); // 幂等——重复摘除 no-op
422
+ }
423
+ if (result.status === 'injected' || result.status === 'wake-refused') {
424
+ // 本入口无 backgroundWake 载体/停摆期注入语义——到达即异常态,诚实退 1
425
+ abortTick(`提交回执异常(${result.status})`);
426
+ err.write(`提交回执异常(${result.status})——run 未起跑\n`);
427
+ return 1;
428
+ }
429
+ // 此后 result 收窄为 RunResult(completed/aborted/failed 三终态)
430
+ // —— ⑦ --background 记账:已上移组合根 settled 桥接单点(04 §5 2026-09-13
431
+ // #41/#44 定形——五入口 run 路统一〔run/tick/TUI/webui/issue 前后台全入账〕,
432
+ // priority 由回执 backgroundLane 声明;此处再扫即双计,旧段退役)——
433
+ // —— ⑧ goal recordTurn 挂点已上移驱动层(批 #99 三入口统一:TUI/webui/
434
+ // issue/run CLI 全经 driver launch settled 链的 onRunSettled 回执——此处
435
+ // 再记一笔即双计,故移除;userInitiated 归因由回执 seeds 窗扫承载)——
436
+ // —— ⑨ settle:CLI 级收场语义(truncated 到帽位在 run 本体 aborted 之上
437
+ // 如实标注)+ 退出码三态映射(completed → 0;failed/aborted/truncated → 1)——
438
+ const finalStatus = truncated ? 'truncated' : result.status;
439
+ let exitCode;
440
+ if (finalStatus === 'completed') {
441
+ exitCode = 0;
442
+ }
443
+ else {
444
+ exitCode = 1;
445
+ if (finalStatus === 'truncated') {
446
+ err.write(`已到 --max-turns ${flags.maxTurns} 帽收场(truncated——到帽截断非终态失败,收场语义位如实标注)\n`);
447
+ }
448
+ else if (result.status === 'failed') {
449
+ // provider 两形态产品级文案(07 §5 provider 产品级文案律);其余失败原文直出
450
+ const diagnostic = diagnoseProviderFailure({ errorMessage: result.errorMessage }, stack.model);
451
+ if (diagnostic !== undefined) {
452
+ err.write(`${diagnostic.hint}\n`);
453
+ }
454
+ else if (result.errorMessage !== undefined && result.errorMessage !== '') {
455
+ err.write(`run 失败:${result.errorMessage}\n`);
456
+ }
457
+ else {
458
+ err.write('run 失败(无错误说明)\n');
459
+ }
460
+ }
461
+ else if (result.errorMessage !== undefined && result.errorMessage !== '') {
462
+ err.write(`run 中止:${result.errorMessage}\n`); // aborted 带说明时呈报(SIGINT 路径说明缺席为常态)
463
+ }
464
+ }
465
+ // —— ⑨' tick settle(律 3 乙案侧收场腿——2026-09-13 修复批):终态镜像落
466
+ // jobs 行(completed→exit_code 0+preview / failed→exit_code 1+error /
467
+ // truncated·aborted→killed;preview 帽 200 与 RunOutcome 契约同值)。崩溃/
468
+ // 信号路径不达本段——claim 残账由引擎僵行清扫回收(律 3 既有兜底语义)——
469
+ if (tickJob !== undefined) {
470
+ const finishedAt = new Date().toISOString();
471
+ if (finalStatus === 'completed') {
472
+ const outcome = { trigger: 'cron', reason: 'exit_code', exitCode: 0, finishedAt };
473
+ if (lastAssistantText !== undefined)
474
+ outcome.finalTextPreview = lastAssistantText.slice(0, 200);
475
+ settleTick(outcome);
476
+ }
477
+ else if (finalStatus === 'truncated') {
478
+ settleTick({
479
+ trigger: 'cron',
480
+ reason: 'killed',
481
+ error: `--max-turns ${flags.maxTurns} 到帽截断(truncated)`,
482
+ finishedAt,
483
+ });
484
+ }
485
+ else if (result.status === 'failed') {
486
+ settleTick({
487
+ trigger: 'cron',
488
+ reason: 'exit_code',
489
+ exitCode: 1,
490
+ error: (result.errorMessage ?? 'run 失败(无错误说明)').slice(0, 200),
491
+ finishedAt,
492
+ });
493
+ }
494
+ else {
495
+ settleTick({ trigger: 'cron', reason: 'killed', error: 'run 中止(协作/信号 abort)', finishedAt });
496
+ }
497
+ }
498
+ // —— ⑩ 输出三档的终值面(stream 档活体已直出——无终值行)——
499
+ if (outputFormat === 'text') {
500
+ if (lastAssistantText !== undefined)
501
+ out.write(`${lastAssistantText}\n`);
502
+ }
503
+ else if (outputFormat === 'json') {
504
+ // 终值单对象(07 §5——机器消费面:识别/计量/产物一屏尽收)
505
+ const summary = {
506
+ sessionId,
507
+ status: finalStatus,
508
+ ...(result.stopReason !== undefined ? { stopReason: result.stopReason } : {}),
509
+ ...(result.status === 'failed' && result.errorMessage !== undefined ? { errorMessage: result.errorMessage } : {}),
510
+ turns: turnsEnded,
511
+ usage: usageSum,
512
+ ...(lastAssistantText !== undefined ? { lastMessage: lastAssistantText } : {}),
513
+ };
514
+ out.write(`${JSON.stringify(summary)}\n`);
515
+ }
516
+ if (flags.outputLastMessage !== undefined) {
517
+ // 末条 assistant 文本原子写(同目录 tmp + rename——CI 取产物不解析 stdout;
518
+ // 内容即纯文本零尾随换行——字节确定性读侧友好)
519
+ const target = flags.outputLastMessage;
520
+ try {
521
+ const tmp = `${target}.tmp-${pid}`;
522
+ writeFileSync(tmp, lastAssistantText ?? '');
523
+ renameSync(tmp, target);
524
+ }
525
+ catch (error) {
526
+ err.write(`--output-last-message 写入失败:${error instanceof Error ? error.message : String(error)}\n`);
527
+ return 1;
528
+ }
529
+ }
530
+ return exitCode;
531
+ }
532
+ /** usage 累加(totalTokens 派生重算——四桶和;可选子集上报才累加) */
533
+ function addUsageInto(sum, usage) {
534
+ sum.input += usage.input;
535
+ sum.output += usage.output;
536
+ sum.cacheRead += usage.cacheRead;
537
+ sum.cacheWrite += usage.cacheWrite;
538
+ if (usage.cacheWrite1h !== undefined)
539
+ sum.cacheWrite1h = (sum.cacheWrite1h ?? 0) + usage.cacheWrite1h;
540
+ if (usage.reasoning !== undefined)
541
+ sum.reasoning = (sum.reasoning ?? 0) + usage.reasoning;
542
+ sum.totalTokens = sum.input + sum.output + sum.cacheRead + sum.cacheWrite;
543
+ }
544
+ // 计量桶映射单源已上移 llm/events.ts usageBucketsOf(04 §5 #41/#44 桥接单点化——旧扫退役)