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,112 @@
1
+ /**
2
+ * host/settings-store — 数据目录用户配置文件 `<dataDir>/settings.json` 读写件
3
+ * (04 §9 定形块⑥·M3 兜缝——2026-09-11 审批分档批 ap-3)。
4
+ *
5
+ * 定位:sandbox 缺省档**持久用户配置层**——§8 三级解析挂第四层的载体:
6
+ * 工具参数 > 会话策略 > CLI 旗标(逐次) > **本文件(持久缺省)** > 代码常量。
7
+ * 持久位不覆盖逐次显式位(装配根做 gaps 填充,run-entry 只传显式胜者)。
8
+ *
9
+ * 两键面(本批定形):`sandboxMode?` / `approvalPolicy?`——预设展开的持久
10
+ * 缺省位;**非敏感件**(两旋钮无秘密,不入 SENSITIVE_READ_BASENAMES——
11
+ * 模型可读面不设防,恰三件敏感清单测试锁不动)。
12
+ *
13
+ * 读写纪律(与 tool-policy-store 同族——「文件即用户资产」律):
14
+ * - 读侧缺席 = {}(零负担首启);文件级坏 JSON = warn 降级 {}(配置层坏形
15
+ * 取缺省 = 现状常量,非 fail-stop 面);键级坏值 = 忽略该键 + warn 点名
16
+ * (好键照常生效);未知键 = warn 不动文件(人可手编他键,机器不覆写)。
17
+ * - 写侧**合并保留未知键**(预设写盘只动两键,用户手编的其他键原样存活);
18
+ * 原子替换(tmp + rename——防撕裂);坏形期拒写('rejected'——与
19
+ * appendToolPolicyEntry 同律,机器不在坏文件上覆写扩大破坏)。
20
+ */
21
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
22
+ import { join } from 'node:path';
23
+ /** 配置文件名(数据目录单段——04 §9 ⑥「本批定名」) */
24
+ export const SETTINGS_BASENAME = 'settings.json';
25
+ const SANDBOX_MODES = ['read-only', 'workspace-write', 'danger'];
26
+ const APPROVAL_POLICIES = ['ask', 'never'];
27
+ /**
28
+ * 装配期读用户配置(缺席 {} 零负担;坏 JSON 降级 {} + unhealthy;键级坏值
29
+ * 忽略点名、未知键 warn 不动文件)。
30
+ */
31
+ export function readHostSettings(dataDir, options = {}) {
32
+ const warn = options.warn ?? ((message) => process.stderr.write(`${message}\n`));
33
+ const path = join(dataDir, SETTINGS_BASENAME);
34
+ if (!existsSync(path))
35
+ return { settings: {}, healthy: true };
36
+ let raw;
37
+ try {
38
+ raw = readFileSync(path, 'utf8');
39
+ }
40
+ catch (err) {
41
+ warn(`配置读取失败(${path}):${err instanceof Error ? err.message : String(err)}——视同缺席`);
42
+ return { settings: {}, healthy: false };
43
+ }
44
+ let doc;
45
+ try {
46
+ doc = JSON.parse(raw);
47
+ }
48
+ catch (err) {
49
+ warn(`配置坏形(${path},JSON 解析失败:${err instanceof Error ? err.message : String(err)})——视同缺席;手改修复前回写拒(防机器覆写扩大破坏)`);
50
+ return { settings: {}, healthy: false };
51
+ }
52
+ if (typeof doc !== 'object' || doc === null || Array.isArray(doc)) {
53
+ warn(`配置坏形(${path},顶层须为对象)——视同缺席;手改修复前回写拒`);
54
+ return { settings: {}, healthy: false };
55
+ }
56
+ // 键级校验:两键值域闭集外忽略点名;未知键 warn 保留不动(读侧不区分
57
+ // 「保留在内存」与否——本面只产两键,未知键经写侧合并律存活)
58
+ const record = doc;
59
+ const { sandboxMode, approvalPolicy, ...rest } = record;
60
+ const unknownKeys = Object.keys(rest);
61
+ if (unknownKeys.length > 0) {
62
+ warn(`配置含未知键 ${unknownKeys.join('、')}(${path})——本面不消费、保留不动`);
63
+ }
64
+ const settings = {};
65
+ if (sandboxMode !== undefined) {
66
+ if (typeof sandboxMode === 'string' && SANDBOX_MODES.includes(sandboxMode)) {
67
+ settings.sandboxMode = sandboxMode;
68
+ }
69
+ else {
70
+ warn(`配置键 sandboxMode 值域外(read-only|workspace-write|danger)——忽略该键`);
71
+ }
72
+ }
73
+ if (approvalPolicy !== undefined) {
74
+ if (typeof approvalPolicy === 'string' && APPROVAL_POLICIES.includes(approvalPolicy)) {
75
+ settings.approvalPolicy = approvalPolicy;
76
+ }
77
+ else {
78
+ warn(`配置键 approvalPolicy 值域外(ask|never)——忽略该键`);
79
+ }
80
+ }
81
+ return { settings, healthy: true };
82
+ }
83
+ /**
84
+ * 写侧合并面:**只动两键**(在场键落值、缺席键不动现状——预设展开只写该
85
+ * 预设携带的旋钮),未知键原样保留(用户手编面不因预设切换损毁);原子
86
+ * 替换落盘(tmp + rename);文件级坏形期拒写。
87
+ */
88
+ export function writeHostSettings(dataDir, patch, options = {}) {
89
+ // 坏形拒写第一闸:复用读侧全套校验(在坏文件上合并 = 覆写扩大破坏)
90
+ const load = readHostSettings(dataDir, options);
91
+ if (!load.healthy)
92
+ return 'rejected';
93
+ // 合并基 = 原文件解析出的全键形(含未知键——保留律)。重读一次拿未剥离
94
+ // 的原始对象(readHostSettings 产物只含两键,未知键需原始面承载)
95
+ const path = join(dataDir, SETTINGS_BASENAME);
96
+ let base = {};
97
+ if (existsSync(path)) {
98
+ const parsed = JSON.parse(readFileSync(path, 'utf8'));
99
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed))
100
+ base = parsed;
101
+ }
102
+ const next = { ...base };
103
+ if (patch.sandboxMode !== undefined)
104
+ next.sandboxMode = patch.sandboxMode;
105
+ if (patch.approvalPolicy !== undefined)
106
+ next.approvalPolicy = patch.approvalPolicy;
107
+ mkdirSync(dataDir, { recursive: true });
108
+ const tmp = `${path}.tmp`;
109
+ writeFileSync(tmp, `${JSON.stringify(next, null, 2)}\n`, 'utf8');
110
+ renameSync(tmp, path);
111
+ return 'written';
112
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * host/signals — 进程编舞(04 §1 信号与退出序;批 12c)。
3
+ *
4
+ * 信号序:SIGINT① → 触发优雅序(04 §1 六步——由调用方注入的 onGraceful
5
+ * 承载:runtime.shutdown 即 ①abort→②closer drain→③flush→④hooks→⑤LIFO);
6
+ * 优雅窗内 SIGINT② → 立即退 130(第二次按键 = 用户明示放弃优雅);SIGTERM
7
+ * 同 SIGINT①(serve stop 的进程外管理动词必经 TERM——幂等重入不另起第二套
8
+ * 停机编舞)。崩溃路径:未捕获异常先同步写 crash.log 再退(退 1 执行失败
9
+ * 档);unhandledRejection 同律处置(裁量钉位:Node 缺省会升格为
10
+ * uncaughtException,显式接住 = fail-loud 且报文带「未处理拒绝」指向根因)。
11
+ *
12
+ * 全注入可测:信号注册(register)/ 终局(exit)均注——测试位收调用序,
13
+ * main 装配位接 process.on/process.exit。
14
+ */
15
+ /**
16
+ * 装配信号编舞(main 装配位调用一次)。
17
+ *
18
+ * 状态机:quiet →(SIGINT①/SIGTERM)→ draining →(SIGINT②)→ 立即 130。
19
+ * SIGTERM 在 draining 态幂等直返(优雅序只起一次);SIGINT② 只认 draining
20
+ * 态的第二次按键。
21
+ */
22
+ export function installSignalChoreography(options) {
23
+ const exit = options.exit ?? ((code) => process.exit(code));
24
+ const register = options.register ?? ((signal, listener) => process.on(signal, listener));
25
+ let draining = false; // 优雅序在飞位(SIGTERM 幂等 / SIGINT② 判据)
26
+ let terminated = false; // 终局幂等位(镜像 process.exit——已退则迟到链不再终局)
27
+ const finish = (code) => {
28
+ if (terminated)
29
+ return;
30
+ terminated = true;
31
+ exit(code);
32
+ };
33
+ register('SIGINT', () => {
34
+ if (!draining) {
35
+ draining = true;
36
+ void options
37
+ .onGraceful()
38
+ .then((code) => finish(code ?? 0))
39
+ .catch(() => finish(1)); // 优雅序自身抛错 = 执行失败档收场
40
+ return;
41
+ }
42
+ finish(130); // 第二次按键——明示放弃优雅,立即退
43
+ });
44
+ register('SIGTERM', () => {
45
+ if (draining)
46
+ return; // 已在优雅序——幂等直返(不另起第二套停机)
47
+ draining = true;
48
+ void options
49
+ .onGraceful()
50
+ .then((code) => finish(code ?? 0))
51
+ .catch(() => finish(1));
52
+ });
53
+ }
54
+ /**
55
+ * 装配崩溃编舞:未捕获异常/未处理拒绝 → 先同步写 crash.log 再退 1。
56
+ *
57
+ * 取证写失败不再拦退(崩溃路径唯一允许的静默归 writeCrashLog 实现侧);
58
+ * 退码 1 = 执行失败档(04 §1 退出序不预设崩溃退码,07 §5 三态归档裁量)。
59
+ */
60
+ export function installCrashChoreography(options) {
61
+ const exit = options.exit ?? ((code) => process.exit(code));
62
+ const register = options.register ?? ((event, listener) => process.on(event, listener));
63
+ const handle = (error) => {
64
+ options.writeCrashLog(error); // 同步先写(进程生命周期取证)
65
+ exit(1);
66
+ };
67
+ register('uncaughtException', handle);
68
+ register('unhandledRejection', (reason) => handle(reason instanceof Error ? reason : new Error(`未处理拒绝:${String(reason)}`)));
69
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * host/single-instance — 单活跃机执法件(05 篇 §6.6;04 §1 运行时侧语义)。
3
+ *
4
+ * 同一数据目录同一时刻恰一活跃进程:启动即写活跃标记(pid + 启动时戳),
5
+ * 二次启动标记在场且 pid 活 → `HOST_DATA_DIR_BUSY` 拒入;pid 死 → 接管
6
+ * (陈旧标记清理后覆写)。serve 宿主(含 --daemon)是合法持有者——标记语义
7
+ * 对 TUI/serve 两宿主同形;daemon 猝死 = 标记随进程亡,下次启动 pid 判死后
8
+ * 接管(既有语义零新增)。豁免面(只读诊断/管理动词)由调用方裁——本件只
9
+ * 提供 acquire/release 原语不预判豁免。
10
+ *
11
+ * 裁量钉位(规范未明文、本笔定形):标记文件名 `active.json`(05 §6.6 只钉
12
+ * 「数据目录下活跃标记文件(pid + 启动时戳)」未定名);记录形
13
+ * `{ pid, startedAt }` 单 JSON 行。
14
+ */
15
+ import { readFileSync, unlinkSync, writeFileSync, mkdirSync } from 'node:fs';
16
+ import { join } from 'node:path';
17
+ import { BaseError } from '../contracts/index.js';
18
+ /** 活跃标记文件名(数据目录下——本笔裁量钉位) */
19
+ export const ACTIVE_MARKER_BASENAME = 'active.json';
20
+ /** acquire 失败面——标记在场且 pid 活(拒入;不打既有进程——fail-loud 不执法) */
21
+ export class DataDirBusyError extends BaseError {
22
+ constructor(busy) {
23
+ super('HOST_DATA_DIR_BUSY', `数据目录已有活跃进程(pid ${busy.pid},启动于 ${new Date(busy.startedAt).toISOString()})——同一数据目录同一时刻恰一活跃进程;如确系残留标记且该 pid 已死会自动接管`);
24
+ }
25
+ }
26
+ /** 进程探活缺省实现:信号 0 探测(ESRCH = 死;EPERM = 活但非属主——按活计) */
27
+ function defaultIsAlive(pid) {
28
+ try {
29
+ process.kill(pid, 0);
30
+ return true;
31
+ }
32
+ catch (err) {
33
+ return err.code === 'EPERM';
34
+ }
35
+ }
36
+ /**
37
+ * 占据活跃标记(启动序第一步——先占标记再开库,拒双开于开库之前)。
38
+ *
39
+ * 判序:标记文件在场且可解析 → pid 活(isAlive 注入判)→ 拒(DataDirBusyError
40
+ * / HOST_DATA_DIR_BUSY);pid 死 → 接管覆写(tookOver = true);标记残缺
41
+ * (坏 JSON/缺字段)→ 视同陈旧接管(宁接管勿误拒——标记非真相源,durable
42
+ * 日志才是)。标记缺席 → 直写(tookOver = false)。
43
+ */
44
+ export function acquireActiveMarker(dataDir, opts = {}) {
45
+ const pid = opts.pid ?? process.pid;
46
+ const now = opts.now ?? (() => Date.now());
47
+ const isAlive = opts.isAlive ?? defaultIsAlive;
48
+ const ensureDir = opts.ensureDir ?? ((dir) => mkdirSync(dir, { recursive: true }));
49
+ const writeFile = opts.writeFile ?? ((path, text) => writeFileSync(path, text));
50
+ const readFile = opts.readFile ?? ((path) => readFileSync(path, 'utf8'));
51
+ const tryUnlink = opts.tryUnlink ??
52
+ ((path) => {
53
+ try {
54
+ unlinkSync(path);
55
+ }
56
+ catch {
57
+ // ENOENT 幂等;其他错上抛(fail-loud——标记不可清是数据目录病态)
58
+ }
59
+ });
60
+ const markerPath = join(dataDir, ACTIVE_MARKER_BASENAME);
61
+ ensureDir(dataDir);
62
+ let tookOver = false;
63
+ let prevText = null;
64
+ try {
65
+ prevText = readFile(markerPath);
66
+ }
67
+ catch {
68
+ prevText = null; // 标记缺席(ENOENT)——首占非接管
69
+ }
70
+ if (prevText !== null) {
71
+ let prev = null;
72
+ try {
73
+ prev = JSON.parse(prevText);
74
+ }
75
+ catch {
76
+ prev = null; // 坏 JSON——残缺标记
77
+ }
78
+ if (prev !== null && typeof prev.pid === 'number' && Number.isInteger(prev.pid) && prev.pid > 0) {
79
+ if (isAlive(prev.pid)) {
80
+ // 活进程持锁——拒入(携带既有记录供诊断面呈现)
81
+ throw new DataDirBusyError({
82
+ pid: prev.pid,
83
+ startedAt: typeof prev.startedAt === 'number' ? prev.startedAt : 0,
84
+ });
85
+ }
86
+ tookOver = true; // pid 判死 → 陈旧标记接管
87
+ }
88
+ else {
89
+ tookOver = true; // 残缺(坏 JSON / pid 字段坏)→ 视同陈旧(宁接管勿误拒)
90
+ }
91
+ }
92
+ const record = { pid, startedAt: now() };
93
+ writeFile(markerPath, `${JSON.stringify(record)}\n`);
94
+ let released = false;
95
+ return {
96
+ tookOver,
97
+ record,
98
+ release: () => {
99
+ if (released)
100
+ return;
101
+ released = true;
102
+ tryUnlink(markerPath);
103
+ },
104
+ };
105
+ }
@@ -0,0 +1,291 @@
1
+ import { canonicalWorkspaceRoot } from '../context/index.js';
2
+ import { EXCLUDED_FROM_DERIVED_SURFACE, IN_PROCESS_CAPABILITIES } from '../subagent/index.js';
3
+ import { formatSkillBlock, validateSkillRefList } from '../skills/index.js';
4
+ /** 派生面结构性恒留名(fs 四名——子自建结构性;bash 恒弃另列) */
5
+ const STRUCTURALLY_KEPT = new Set(EXCLUDED_FROM_DERIVED_SURFACE.filter((name) => name !== 'bash'));
6
+ /** 委派会话登记表工厂(装配根自持单例) */
7
+ export function createDelegationSessionTracker() {
8
+ const depths = new Map();
9
+ return {
10
+ record(sessionId, depth) {
11
+ depths.set(sessionId, depth);
12
+ },
13
+ depthOf(sessionId) {
14
+ return depths.get(sessionId);
15
+ },
16
+ release(sessionId) {
17
+ depths.delete(sessionId);
18
+ },
19
+ };
20
+ }
21
+ /**
22
+ * 派生面整形器(工厂侧执法律):bash 恒弃(永不升格总则特例——子管道无
23
+ * 人在场应答 def 内部升权);fs 四名恒留(子自建结构性——白名单不含亦在,
24
+ * 与 deriveToolSurface 剔除语义互补:那层剔父名集、这层整子实面);其余
25
+ * 按白名单滤(白名单缺省 = 全留——service 交集已在 availableTools 在场
26
+ * 时算过,缺席透传形此处亦透传)。
27
+ */
28
+ export function shapeDerivedTools(whitelist) {
29
+ const allow = whitelist !== undefined ? new Set(whitelist) : undefined;
30
+ return (tools) => tools.filter((tool) => {
31
+ if (tool.name === 'bash')
32
+ return false;
33
+ if (STRUCTURALLY_KEPT.has(tool.name))
34
+ return true;
35
+ return allow === undefined || allow.has(tool.name);
36
+ });
37
+ }
38
+ /**
39
+ * 尾扫末条 assistant 消息文本(黑盒输出面——子会话事件流的最后一条
40
+ * assistant/message 的 text 块拼接;缺席 = 空串诚实回执)。
41
+ */
42
+ function lastAssistantText(events) {
43
+ for (let i = events.length - 1; i >= 0; i -= 1) {
44
+ const event = events[i];
45
+ if (event === undefined || event.type !== 'assistant/message')
46
+ continue;
47
+ const blocks = event.data?.content;
48
+ if (!Array.isArray(blocks))
49
+ return '';
50
+ return blocks
51
+ .filter((block) => block.type === 'text')
52
+ .map((block) => block.text)
53
+ .join('');
54
+ }
55
+ return '';
56
+ }
57
+ /** RunResult 终态 → SubagentStopReason 映射(completed→stop/aborted→aborted/failed→error) */
58
+ function stopReasonOf(status) {
59
+ if (status === 'completed')
60
+ return 'stop';
61
+ if (status === 'failed')
62
+ return 'error';
63
+ return 'aborted';
64
+ }
65
+ /**
66
+ * RP4 机器账铸造(in-process 工厂结算位——04 §10 structured 定形段):
67
+ * 从子会话事件流单次扫铸 {turnCount, messageCount, usage 两桶}。
68
+ * - turnCount = 'turn/start' 计数;
69
+ * - messageCount = user/message + assistant/message 计数(模型可见消息——
70
+ * 结算通知「(N 条消息)」取数位);
71
+ * - usage = 末条 assistant/message 计量(05 §1.1 通道词律同源——主 loop
72
+ * 末条 assistant)的两桶投影;零 turn 诚实 null。
73
+ */
74
+ function forgeAccountFacts(events) {
75
+ let turnCount = 0;
76
+ let messageCount = 0;
77
+ let lastUsage;
78
+ for (const event of events) {
79
+ if (event.type === 'turn/start')
80
+ turnCount += 1;
81
+ if (event.type === 'user/message' || event.type === 'assistant/message')
82
+ messageCount += 1;
83
+ if (event.type === 'assistant/message') {
84
+ const usage = event.data?.usage;
85
+ // 窄 guard 两桶(durable 回读形防御)——在场即视为末条计量(后扫覆盖前扫)
86
+ if (typeof usage === 'object' &&
87
+ usage !== null &&
88
+ typeof usage.input === 'number' &&
89
+ typeof usage.output === 'number') {
90
+ lastUsage = usage;
91
+ }
92
+ }
93
+ }
94
+ return { turnCount, messageCount, lastUsage };
95
+ }
96
+ /**
97
+ * 机器账组装(structured + 顶层 usage 同值双填——同源一次铸造):顶层位
98
+ * = 末条 assistant 完整 Usage 透传(第三方 provider 只报顶层的独立保留位);
99
+ * structured.usage = 同源两桶投影(无计量诚实 null)。
100
+ */
101
+ function forgeMachineAccount(input) {
102
+ const facts = forgeAccountFacts(input.events);
103
+ const structured = {
104
+ childSessionId: input.childSessionId,
105
+ // jobName 仅 background 形在场(与注册表条目/通知文案同名——one-shot 无 Job 身份)
106
+ ...(input.request.background === true && input.request.name !== undefined ? { jobName: input.request.name } : {}),
107
+ durationMs: Date.now() - input.startedAt,
108
+ turnCount: facts.turnCount,
109
+ messageCount: facts.messageCount,
110
+ usage: facts.lastUsage !== undefined ? { input: facts.lastUsage.input, output: facts.lastUsage.output } : null,
111
+ stopReason: input.stopReason,
112
+ };
113
+ return { structured, usage: facts.lastUsage };
114
+ }
115
+ /**
116
+ * in-process 子代理 provider(真工厂——SubagentProvider 契约的本体实装)。
117
+ * capabilities 五布尔恒真(IN_PROCESS_CAPABILITIES 单源)。
118
+ */
119
+ export function createInProcessSubagentProvider(options) {
120
+ const { stack, tracker, warn } = options;
121
+ return {
122
+ capabilities: IN_PROCESS_CAPABILITIES,
123
+ async run(request) {
124
+ // reserve 线起跑前执法(04 §5——总预算 90% 已越线则不起跑):诚实回执
125
+ // aborted + diagnostic,不建会话不耗预算——余量留给主循环写终态
126
+ if (options.reserveBreached?.() === true) {
127
+ return {
128
+ output: '',
129
+ stopReason: 'aborted',
130
+ diagnostic: '后台预算已达 90% reserve 线——子代理不起跑,余量留给主循环写终态(04 §5)',
131
+ };
132
+ }
133
+ // skills 键执法(06 §11.6 spawn 永久注入模):形状三验工厂侧统一复验
134
+ //(单执法点覆盖声明式 def/程序化 def/直呼 request 全腿——解析层管形状、
135
+ // 工厂兜底:程序化与直呼腿不经解析层);存在性逐名解析,任一缺席 =
136
+ // 拒 spawn 回执缺口 fail-ask(不建会话不耗预算——reserve 线同形)。
137
+ // disable-model-invocation 隐藏件不受限(作者侧声明面——清单滤除律
138
+ // 只限模型自动装载面)。
139
+ let childSystemPrompt = request.systemPrompt;
140
+ if (request.skills !== undefined && request.skills.length > 0) {
141
+ const shapeErrors = validateSkillRefList(request.skills);
142
+ if (shapeErrors.length > 0) {
143
+ return {
144
+ output: '',
145
+ stopReason: 'aborted',
146
+ diagnostic: `skills 键形状违例:${shapeErrors.join(';')}——子代理不起跑(06 §11.6 形状护栏)`,
147
+ };
148
+ }
149
+ if (options.resolveSkill === undefined) {
150
+ return {
151
+ output: '',
152
+ stopReason: 'aborted',
153
+ diagnostic: '技能注册表不可达(无插件装配形)——skills 键子代理不起跑(fail-closed,06 §11.6)',
154
+ };
155
+ }
156
+ const missing = [];
157
+ const blocks = [];
158
+ for (const ref of request.skills) {
159
+ const skill = options.resolveSkill(ref);
160
+ if (skill === undefined) {
161
+ missing.push(ref);
162
+ }
163
+ else {
164
+ blocks.push(formatSkillBlock(skill, skill.content));
165
+ }
166
+ }
167
+ if (missing.length > 0) {
168
+ return {
169
+ output: '',
170
+ stopReason: 'aborted',
171
+ diagnostic: `技能未解析(${missing.join('、')})——子代理不起跑(06 §11.6 skills 键 fail-ask);可查 <available_skills> 清单核对名称`,
172
+ };
173
+ }
174
+ // 全命中:具名块拼入子 systemPrompt 尾(systemPrompt 缺席 = 空串起拼
175
+ //——注入不依赖其在场;与清单渐进披露并存——额外正文常驻非替代披露)
176
+ childSystemPrompt = `${request.systemPrompt ?? ''}\n\n${blocks.join('\n\n')}`;
177
+ }
178
+ // service 已在 request.tools 位算好 effectiveTools(availableTools 在场
179
+ // = 派生面∩白名单交集;缺席 = 透传白名单)——整形器以之为准
180
+ const whitelist = request.tools;
181
+ // 委派边界①:审批型升权路由——子会话 ask 落父会话审批面(ownership
182
+ // 织入注释的执法位:恰一键 {sessionId: 父}——子代理在父会话内发起的
183
+ // 审批落回父会话呈现面)。background 形先发挂起通知(恰一条幂等由
184
+ // 父 driver dedupeKey 面执法;one-shot 不发——挂着等待即知情)。
185
+ const parentSessionId = request.parentSessionId;
186
+ const askApproval = parentSessionId !== undefined
187
+ ? async (req, opts) => {
188
+ if (request.background === true && request.notifyApproval !== undefined && req.approvalId !== undefined) {
189
+ try {
190
+ await request.notifyApproval({
191
+ approvalId: req.approvalId,
192
+ toolName: req.toolName ?? request.name ?? '未知工具',
193
+ ...(req.reason !== undefined ? { reason: req.reason } : {}),
194
+ });
195
+ }
196
+ catch (err) {
197
+ warn(`审批挂起通知失败(不阻断 ask 本体):${err instanceof Error ? err.message : String(err)}`);
198
+ }
199
+ }
200
+ return stack.channels.askApproval(parentSessionId, req, opts);
201
+ }
202
+ : undefined;
203
+ // 子会话装配(真工厂核心):origin 'delegation' durable 归因;model/
204
+ // systemPrompt def 直传(skills 注入后形);shapeTools 派生面整形;
205
+ // askApproval 升父面。startedAt 在建会话前取(机器账 durationMs 计量
206
+ // 含装配段——「子运行墙钟」从委派受理起算)。
207
+ const startedAt = Date.now();
208
+ const child = stack.manager.create({
209
+ origin: 'delegation',
210
+ workspaceRoot: canonicalWorkspaceRoot(),
211
+ ...(request.name !== undefined ? { title: request.name } : {}),
212
+ ...(request.model !== undefined ? { model: request.model } : {}),
213
+ ...(childSystemPrompt !== undefined ? { systemPrompt: childSystemPrompt } : {}),
214
+ shapeTools: shapeDerivedTools(whitelist),
215
+ ...(askApproval !== undefined ? { askApproval } : {}),
216
+ });
217
+ const driver = child.driver;
218
+ tracker.record(child.sessionId, request.depth ?? 1);
219
+ // 协作停止桥(只 background 形注入——one-shot 父同步等无停止位):
220
+ // JobHandle 置 stopping → 轮询观察 → abort 子 run;reserve 线观察
221
+ // (04 §5——非交互子代理 90% 线强停)同轮合流:越线 → abort + 记因
222
+ //(回执 diagnostic 归因预算而非泛 aborted)
223
+ let reserveStopped = false;
224
+ const observeStop = request.stopRequested;
225
+ const observeReserve = options.reserveBreached;
226
+ let timer;
227
+ if (observeStop !== undefined || observeReserve !== undefined) {
228
+ timer = setInterval(() => {
229
+ if (observeStop !== undefined && observeStop()) {
230
+ driver.abort();
231
+ return;
232
+ }
233
+ if (observeReserve !== undefined && observeReserve()) {
234
+ reserveStopped = true;
235
+ driver.abort();
236
+ }
237
+ }, 500);
238
+ timer.unref();
239
+ }
240
+ try {
241
+ // 子 prompt 提交:source 缺省 'user'(父即子的用户;origin
242
+ // 'delegation' 在会话行是 durable 归因——EventSource 闭集无该词)
243
+ const outcome = await driver.submit(request.prompt);
244
+ const events = driver.session.events();
245
+ const output = lastAssistantText(events);
246
+ // 机器账铸造(RP4——已起跑位含防御位都铸:childSessionId 已有、
247
+ // 计量如实〔防御位 turnCount 0 形〕;pre-spawn 拒径不铸〔无会话〕)
248
+ const account = forgeMachineAccount({
249
+ childSessionId: child.sessionId,
250
+ request,
251
+ startedAt,
252
+ events,
253
+ stopReason: outcome.status === 'injected' || outcome.status === 'wake-refused'
254
+ ? 'aborted'
255
+ : stopReasonOf(outcome.status),
256
+ });
257
+ if (outcome.status === 'injected' || outcome.status === 'wake-refused') {
258
+ // 理论不达防御位(新会话无停摆/无唤醒件)——诚实回执不伪装完成
259
+ return {
260
+ output,
261
+ stopReason: 'aborted',
262
+ diagnostic: `子会话提交未起跑(${outcome.status})`,
263
+ structured: account.structured,
264
+ ...(account.usage !== undefined ? { usage: account.usage } : {}),
265
+ };
266
+ }
267
+ return {
268
+ output,
269
+ stopReason: stopReasonOf(outcome.status),
270
+ ...(reserveStopped && outcome.status === 'aborted'
271
+ ? { diagnostic: '后台预算达 90% reserve 线——子代理强停,余量留给主循环写终态(04 §5)' }
272
+ : {}),
273
+ ...(outcome.status === 'failed' && outcome.errorMessage !== undefined
274
+ ? { diagnostic: outcome.errorMessage }
275
+ : {}),
276
+ structured: account.structured,
277
+ ...(account.usage !== undefined ? { usage: account.usage } : {}),
278
+ };
279
+ }
280
+ finally {
281
+ if (timer !== undefined)
282
+ clearInterval(timer);
283
+ tracker.release(child.sessionId);
284
+ // 单会话收口(05 §7 retire 律):dismantle 终态停摆 + 摘活体登记
285
+ //(原 driver.dismantle 直调的「活体登记回收挂账」销账——durable 面
286
+ // 不受影响,log 仍可查)
287
+ stack.manager.retire(child.sessionId);
288
+ }
289
+ },
290
+ };
291
+ }