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,149 @@
1
+ /**
2
+ * Job 注册表机器(04 §10——ctx.jobs 的注册表面)。
3
+ *
4
+ * 进程内状态,终态不镜像落库(明文豁免条款——durable 面由各消费件自落)。
5
+ * 状态机 running →(可选 stopping)→ 唯一终态 completed/killed/failed;
6
+ * first-wins:首个终态落定即封,后续结算静默弃;done promise 永不 reject
7
+ * (等待方只处理终值,异常面走 failed 终态不走红 Promise)。
8
+ *
9
+ * 归属围栏:条目带 owner sessionId——会话关闭按 owner 收口其在飞 Job
10
+ * (打断/杀并落 killed)。registerKind 未登记种类拒 JOB_KIND_UNKNOWN。
11
+ * 活体通知 job_settled(总线词,内存直推——emit 注入位,装配根接线到
12
+ * context 事件总线并预注册词汇)。终态条目保序保留帽 256 FIFO(进程
13
+ * 生命周期内跨会话有界——供 UI 回看与结算对账,超帽即弃最老)。
14
+ */
15
+ import { BaseError, } from '../contracts/index.js';
16
+ /** 终态条目保序保留帽(04 §10——FIFO 截断) */
17
+ export const JOB_RETENTION_CAP = 256;
18
+ /**
19
+ * 建 Job 注册表。并发语义:单进程内 JS 单线执行保证 register/settle/
20
+ * closeOwner 的检查-落定区间原子(无 await 穿插);done promise 链与
21
+ * emit 异步腿在终态落定后异步推进,不回染状态机。
22
+ */
23
+ export function createJobRegistry(options = {}) {
24
+ const now = options.now ?? Date.now;
25
+ const warn = options.warn ?? ((message) => console.warn(message));
26
+ const kinds = new Set();
27
+ /** 在飞条目(注册序) */
28
+ const live = new Map();
29
+ /** 终态保留列(注册序 FIFO——帽 256 截尾) */
30
+ const settledHistory = [];
31
+ let idSeq = 0;
32
+ const countRunning = (kind) => {
33
+ let n = 0;
34
+ for (const job of live.values())
35
+ if (job.entry.kind === kind)
36
+ n += 1;
37
+ return n;
38
+ };
39
+ /** 落终态(first-wins 单点):改条目、resolve done、移保留列、发射通知 */
40
+ const finalize = (job, terminal) => {
41
+ if (job.entry.terminal !== undefined) {
42
+ warn(`Job ${job.entry.id} 已终态 ${job.entry.terminal.status}——后续结算 ${terminal.status} 静默弃(first-wins)`);
43
+ return false;
44
+ }
45
+ const full = { ...terminal, at: now() };
46
+ // 条目快照换新(外部持有的旧快照保持终态前视图——快照语义非活引用)
47
+ job.entry = { ...job.entry, status: full.status, terminal: full };
48
+ live.delete(job.entry.id);
49
+ settledHistory.push(job.entry);
50
+ if (settledHistory.length > JOB_RETENTION_CAP)
51
+ settledHistory.shift();
52
+ job.resolveDone(full);
53
+ // 活体通知异步腿不阻塞结算序(emit 异常隔离——通知失败不反卷终态)
54
+ void Promise.resolve(options.emit?.({ entry: job.entry })).catch(() => undefined);
55
+ return true;
56
+ };
57
+ /**
58
+ * 协作停止内部单点(stop 与 closeOwner 共用):置 stopping + 路由 onStop。
59
+ * 路由异常 warn 隔离不反卷状态机(04 §10 定形——中止路由失联不废停止语义)。
60
+ */
61
+ const stopInternal = (job) => {
62
+ if (job.entry.status !== 'running')
63
+ return; // 已终态/stopping 零动作(幂等)
64
+ job.entry = { ...job.entry, status: 'stopping' };
65
+ if (job.onStop !== undefined) {
66
+ try {
67
+ job.onStop();
68
+ }
69
+ catch (err) {
70
+ warn(`Job ${job.entry.id} 协作中止路由抛错(已置 stopping 继续收口):${err instanceof Error ? err.message : String(err)}`);
71
+ }
72
+ }
73
+ };
74
+ return {
75
+ registerKind(kind) {
76
+ kinds.add(kind);
77
+ },
78
+ hasKind(kind) {
79
+ return kinds.has(kind);
80
+ },
81
+ register(input) {
82
+ if (!kinds.has(input.kind)) {
83
+ throw new BaseError('JOB_KIND_UNKNOWN', `Job kind「${input.kind}」未登记——装载期先 registerKind(词汇注册表纪律)`);
84
+ }
85
+ const limit = options.parallelLimits?.[input.kind];
86
+ if (limit !== undefined && countRunning(input.kind) >= limit) {
87
+ throw new BaseError('JOB_LIMIT_REACHED', `Job kind「${input.kind}」在飞数已达并行帽 ${limit}——拒新注册(04 §10 注册表面口)`);
88
+ }
89
+ const id = `job-${++idSeq}`;
90
+ const entry = {
91
+ id,
92
+ name: input.name,
93
+ kind: input.kind,
94
+ owner: input.owner,
95
+ status: 'running',
96
+ startedAt: now(),
97
+ };
98
+ let resolveDone;
99
+ // done 永不 reject:只 resolve 终值(等待方只处理终值——异常面走
100
+ // failed 终态不走红 Promise)
101
+ const done = new Promise((resolve) => {
102
+ resolveDone = resolve;
103
+ });
104
+ const job = { entry, resolveDone, ...(input.onStop !== undefined ? { onStop: input.onStop } : {}) };
105
+ live.set(id, job);
106
+ return {
107
+ get entry() {
108
+ return job.entry;
109
+ },
110
+ done,
111
+ stop() {
112
+ // 协作停止请求:置 stopping + 协作中止路由(结算权归 runner——stop
113
+ // 不直接终态);已终态/stopping 零动作(幂等)
114
+ stopInternal(job);
115
+ },
116
+ settle(terminal) {
117
+ return finalize(job, terminal);
118
+ },
119
+ };
120
+ },
121
+ async closeOwner(owner) {
122
+ const owned = [...live.values()].filter((job) => job.entry.owner === owner);
123
+ for (const job of owned) {
124
+ // 归属收口两拍(04 §10 定形):先协作停止(置 stopping + onStop 路由
125
+ // ——「打断」的机构位:run 侧中止回执晚到因 first-wins 静默弃属预期),
126
+ // 再兜底 finalize killed(Job 条目立即终态不悬空;detail 载归因)
127
+ stopInternal(job);
128
+ finalize(job, { status: 'killed', detail: `归属围栏收口(owner ${owner})` });
129
+ }
130
+ return owned.map((job) => job.entry);
131
+ },
132
+ list() {
133
+ // 在飞在前(注册序)、终态保留在后(FIFO 序)——两侧条目形同源(快照)
134
+ return [...[...live.values()].map((job) => job.entry), ...settledHistory];
135
+ },
136
+ running() {
137
+ return [...live.values()]
138
+ .filter((job) => job.entry.status === 'running' || job.entry.status === 'stopping')
139
+ .map((job) => job.entry);
140
+ },
141
+ get(id) {
142
+ return live.get(id)?.entry ?? settledHistory.find((job) => job.id === id);
143
+ },
144
+ };
145
+ }
146
+ /** 终态判定读面(状态词汇闭集外判——呈现/对账面共用) */
147
+ export function isTerminalStatus(status) {
148
+ return status === 'completed' || status === 'killed' || status === 'failed';
149
+ }
@@ -0,0 +1,338 @@
1
+ /**
2
+ * 委派机器(04 §10 两种收场/委派边界三层/静默退化防御的执法面)。
3
+ *
4
+ * 编舞序钉死:background 终态「通知先落、Job 条目后销」(结算通知
5
+ * 无条件先于归属释放——「完成了但父永远不知道」不存在);one-shot
6
+ * 父同步等结果(黑盒——结果不重试,error 形立即结算给父)。
7
+ *
8
+ * 委派边界三层执法位:① 审批路由 = notifyApproval 闭包注入 request
9
+ * (组合根桥父会话审批面);② 档位快照 = in-process 工厂自带(host
10
+ * 装配位——本件不立法);③ 深度帽 + bash 排除 = 本件(深度拒 +
11
+ * 派生面结构性剔除五名)。
12
+ *
13
+ * 注册面两腿(04 §10):声明式腿 registerProvider(core:skills 解析层
14
+ * 物化调用——词法在解析层执法);程序化腿 registerProgrammatic(03 §2.2
15
+ * 第十二动词 ctx.agent.registerSubagentProvider 受局面——D 批 D-2)。
16
+ * 两腿同一 providers 册(run 路由单源):**按注册者 id 分域存名**(owner
17
+ * 维度——归因与防冒名载体,name 镜像裸词无域前缀);撞名比对经派生
18
+ * 工具名 `agent_<name>`(前缀单射——同名即同派生名):跨层〔撞声明式
19
+ * first-wins 全序既有位〕与跨插件同名统一经此拒。
20
+ */
21
+ import { BaseError, SUBAGENT_DEPTH_MAX, } from '../contracts/index.js';
22
+ import { defBoundProvider } from './declarative.js';
23
+ import { subagentSettledContent } from './notify.js';
24
+ import { deriveToolSurface, findPrecheckGaps, intersectToolWhitelist } from './surface.js';
25
+ import { DEFAULT_SUBAGENT_PROVIDER, } from './types.js';
26
+ /** diagnostic 上报帽(04 §10——≤4096 截断;契约面截断执法位在机器) */
27
+ const DIAGNOSTIC_MAX = 4096;
28
+ /** 子代理名词法帽(06 §11.2 name ≤64——与技能词法同源同值) */
29
+ const PROVIDER_NAME_MAX = 64;
30
+ /**
31
+ * 单父在飞子代理扇出帽缺省值(04 §10 扇出帽段——码面缺省参数非契约
32
+ * 常数:细节包值 20 对交互场景,本宿主子代理面向编排/调研,取向 8 与
33
+ * Job per-kind 帽〔issue 2/trigger 4〕量级相称、宿主 lane 帽 16 内留
34
+ * 交互余量;与委派深度帽〔缺省 3〕正交——深度拦嵌套、本帽拦扇出)。
35
+ */
36
+ export const DEFAULT_SUBAGENT_FANOUT_LIMIT = 8;
37
+ /** 扇出帽 env 旋钮名(07 §8 env 表——与 BERRY_AGENT_MAX_CONCURRENT_RUNS 同形族) */
38
+ export const ENV_MAX_CONCURRENT_SUBAGENTS = 'BERRY_AGENT_MAX_CONCURRENT_SUBAGENTS';
39
+ /**
40
+ * 扇出帽容量解析(04 §10 扇出帽段):解析序 = 显式覆盖位 >
41
+ * env `BERRY_AGENT_MAX_CONCURRENT_SUBAGENTS` > 缺省 8。非正整数
42
+ * fail-loud 拒(RangeError)——空帽/坏帽是死配置(字串形全串 /^\d+$/
43
+ * 判防 parseInt 截停,同 resolveRunLaneCapacity 律)。
44
+ */
45
+ export function resolveSubagentFanoutLimit(override, env) {
46
+ const raw = override ?? env[ENV_MAX_CONCURRENT_SUBAGENTS];
47
+ if (raw === undefined)
48
+ return DEFAULT_SUBAGENT_FANOUT_LIMIT;
49
+ let value;
50
+ if (typeof raw === 'number') {
51
+ value = raw;
52
+ }
53
+ else if (/^\d+$/.test(raw)) {
54
+ value = Number.parseInt(raw, 10);
55
+ }
56
+ else {
57
+ value = Number.NaN;
58
+ }
59
+ if (!Number.isInteger(value) || value < 1) {
60
+ throw new RangeError(`子代理扇出帽须为正整数,收到 ${String(raw)}——空帽/坏帽是死配置(${ENV_MAX_CONCURRENT_SUBAGENTS} / maxConcurrentPerParent)`);
61
+ }
62
+ return value;
63
+ }
64
+ /**
65
+ * 子代理名词法违例清单(空 = 合法)。裸词四查:字符集/长度/首尾连字符/
66
+ * 连续连字符——与 skills validateSkillName 同形本地复刻(02 §4.1 无
67
+ * subagent→skills 边,词法真源 06:386 frontmatter name 行,不经 import 共享)。
68
+ */
69
+ function validateProviderName(name) {
70
+ const violations = [];
71
+ if (name.length > PROVIDER_NAME_MAX) {
72
+ violations.push(`超 ${PROVIDER_NAME_MAX} 字符(现 ${name.length})`);
73
+ }
74
+ if (!/^[a-z0-9-]+$/.test(name)) {
75
+ violations.push('含非法字符(只许小写字母/数字/连字符)');
76
+ }
77
+ if (name.startsWith('-') || name.endsWith('-')) {
78
+ violations.push('首尾不得为连字符');
79
+ }
80
+ if (name.includes('--')) {
81
+ violations.push('不得含连续连字符');
82
+ }
83
+ return violations;
84
+ }
85
+ /** diagnostic 截断(保头——契约面 ≤4096 的单点执法) */
86
+ function truncateDiagnostic(text) {
87
+ if (text === undefined)
88
+ return undefined;
89
+ if (text.length <= DIAGNOSTIC_MAX)
90
+ return text;
91
+ return `${text.slice(0, DIAGNOSTIC_MAX)}…(截断)`;
92
+ }
93
+ /** 结果归一(截断 diagnostic——黑盒回执面的机器侧整形) */
94
+ function normalizeResult(result) {
95
+ const diagnostic = truncateDiagnostic(result.diagnostic);
96
+ if (diagnostic === result.diagnostic)
97
+ return result;
98
+ return { ...result, ...(diagnostic !== undefined ? { diagnostic } : {}) };
99
+ }
100
+ /** provider 异常 → 黑盒 error 结果(结果不重试——重试是父的策略不是子代理机制) */
101
+ function errorResult(err) {
102
+ const message = err instanceof Error ? err.message : String(err);
103
+ return { output: '', stopReason: 'error', diagnostic: message };
104
+ }
105
+ /** stopReason → Job 终态映射(stop→completed / error→failed / aborted→killed) */
106
+ function terminalOf(result) {
107
+ if (result.stopReason === 'stop')
108
+ return { status: 'completed' };
109
+ if (result.stopReason === 'aborted')
110
+ return { status: 'killed', ...(result.diagnostic !== undefined ? { detail: result.diagnostic } : {}) };
111
+ return { status: 'failed', ...(result.diagnostic !== undefined ? { detail: result.diagnostic } : {}) };
112
+ }
113
+ /**
114
+ * 建委派机器。kind 'subagent' 构造即登记(本件自有 kind——exec 'process' /
115
+ * issue 'issue' 各消费件装载期自登,同注册表分立)。
116
+ */
117
+ export function createSubagentService(options) {
118
+ const warn = options.warn ?? ((message) => console.warn(message));
119
+ const providers = new Map();
120
+ options.registry.registerKind('subagent');
121
+ // ── 单父扇出闸(04 §10 扇出帽段——per-parentSessionId 内存位) ──
122
+ // 同 lane 帽哲学:满帽排队非拒收 FIFO(工具执行体内等待、释放依序续跑);
123
+ // 分账 = lane 拦「新 run 诞生」全局,本帽拦「单父子代理扇出」;one-shot
124
+ // 与 background 同池同帽(同父语义连续)。
125
+ const maxConcurrentPerParent = resolveSubagentFanoutLimit(options.maxConcurrentPerParent, options.env ?? {});
126
+ const fanout = new Map();
127
+ /**
128
+ * 同步试位(true = 已得位——**零异步边界**:one-shot 受理面同步跑到
129
+ * provider.run,不因取位引入微任务〔「调用后同步推杆」的既有时序假设
130
+ * 不破坏〕;false = 满帽,经 waitSlot 排队)。
131
+ */
132
+ function tryAcquireSlot(parentSessionId) {
133
+ let slot = fanout.get(parentSessionId);
134
+ if (slot === undefined) {
135
+ slot = { active: 0, waiters: [] };
136
+ fanout.set(parentSessionId, slot);
137
+ }
138
+ if (slot.active < maxConcurrentPerParent) {
139
+ slot.active++;
140
+ return true;
141
+ }
142
+ return false;
143
+ }
144
+ /** 满帽等待(FIFO 排队——释放时位直接移交:active 不减不增);排队项无独立停止位〔stopRequested 桥排队中缺席 = 诚实边界,成文不隐藏〕 */
145
+ function waitSlot(parentSessionId) {
146
+ const slot = fanout.get(parentSessionId);
147
+ if (slot === undefined)
148
+ return Promise.resolve(); // 防御位(理论不可达——tryAcquire 失败必已建槽)
149
+ return new Promise((resolve) => {
150
+ slot.waiters.push(resolve);
151
+ });
152
+ }
153
+ /** 释放(等待队首直接接位——位移交不经过计数;队空归零删键防 Map 无界驻留) */
154
+ function releaseSlot(parentSessionId) {
155
+ const slot = fanout.get(parentSessionId);
156
+ if (slot === undefined)
157
+ return; // 防御位(理论不可达——acquire 必先建槽)
158
+ const waiter = slot.waiters.shift();
159
+ if (waiter !== undefined) {
160
+ waiter();
161
+ return;
162
+ }
163
+ slot.active--;
164
+ if (slot.active <= 0 && slot.waiters.length === 0)
165
+ fanout.delete(parentSessionId);
166
+ }
167
+ const service = {
168
+ registerProvider(name, provider, opts) {
169
+ // 声明式腿(core:skills/core:subagent 物化调用):词法在解析层执法
170
+ // (agents.ts——name 是注册键不宽容),此处只管词法身份面撞名
171
+ if (providers.has(name)) {
172
+ throw new BaseError('SUBAGENT_PROVIDER_EXISTS', `named provider「${name}」已注册(注册方 ${providers.get(name)?.owner})——撞名拒(静态绑定面)`);
173
+ }
174
+ const entry = { provider, owner: opts?.owner ?? 'declarative' };
175
+ providers.set(name, entry);
176
+ // 注销器只摘本人条目(三律②——镜像 registerProgrammatic;reload 全摘
177
+ // 重挂的撤位载体〔RP5〕;在飞委派闭包持有 provider 引用跑完自灭)
178
+ return () => {
179
+ if (providers.get(name) === entry)
180
+ providers.delete(name);
181
+ };
182
+ },
183
+ registerProgrammatic(owner, def) {
184
+ // ── 闸一 撞名(前置格式闸——在册名必已合法,「名字被占用」更指向根因;
185
+ // AGENT_ROLE/TRIGGER 同序):比对经派生工具名 agent_<name>(前缀单射,
186
+ // 同名即同派生名)——撞声明式 first-wins 全序既有位或兄弟插件程序化
187
+ // 注册位统一经此拒;在册方 owner 入 message(分域存名的归因兑现)
188
+ const existing = providers.get(def.name);
189
+ if (existing !== undefined) {
190
+ throw new BaseError('SUBAGENT_PROVIDER_EXISTS', `named provider「${def.name}」已注册(注册方 ${existing.owner})——派生工具 agent_${def.name} 重影即契约面漂移,拒绝式(04 §10 程序化注册槽)`);
191
+ }
192
+ // ── 闸二 裸词词法(06 §11.6 声明式 name 同形——词法真源 06 §11.2)
193
+ const violations = validateProviderName(def.name);
194
+ if (violations.length > 0) {
195
+ throw new BaseError('SUBAGENT_NAME_INVALID', `子代理名「${def.name}」非裸词:${violations.join(';')}(镜像 06 §11.6 声明式 name 同形——04 §10 程序化注册槽)`);
196
+ }
197
+ const entry = { provider: defBoundProvider(def, service), owner, def };
198
+ providers.set(def.name, entry);
199
+ // 注销器只摘本人条目(三律②——防过期时序误摘接任者);卸载回卷即
200
+ // 释放名(同名重注册无残留占用);在飞委派闭包持有 provider 引用
201
+ // 跑完自灭(黑盒结果不重试律兼容——不撤运行中的委派只撤注册位)
202
+ return () => {
203
+ if (providers.get(def.name) === entry)
204
+ providers.delete(def.name);
205
+ };
206
+ },
207
+ programmaticProviders() {
208
+ const entries = [];
209
+ for (const entry of providers.values()) {
210
+ if (entry.def !== undefined)
211
+ entries.push({ def: entry.def, owner: entry.owner });
212
+ }
213
+ return entries;
214
+ },
215
+ getProvider(name) {
216
+ return providers.get(name)?.provider;
217
+ },
218
+ providerNames() {
219
+ return [...providers.keys()];
220
+ },
221
+ async run(input) {
222
+ // ── 委派边界③:深度帽(根 = 1;超帽拒——防自嵌套爆栈) ──
223
+ if (input.depth > SUBAGENT_DEPTH_MAX) {
224
+ throw new BaseError('SUBAGENT_DEPTH_EXCEEDED', `委派深度 ${input.depth} 超帽 ${SUBAGENT_DEPTH_MAX}——拒自嵌套爆栈(04 §10 委派边界③)`);
225
+ }
226
+ // ── 静态绑定路由:provider 缺席拒(模型不可见动态选择器) ──
227
+ const providerName = input.providerName ?? DEFAULT_SUBAGENT_PROVIDER;
228
+ const provider = providers.get(providerName)?.provider;
229
+ if (provider === undefined) {
230
+ throw new BaseError('SUBAGENT_PROVIDER_UNKNOWN', `委派路由「${providerName}」未注册——in-process 真工厂归 host 装配批接线`);
231
+ }
232
+ // ── 静默退化防御②:spawn 前预检闸(fail-ask 不降级瞎跑) ──
233
+ const gaps = findPrecheckGaps(input.requiresTools, input.availableTools);
234
+ if (gaps.length > 0) {
235
+ throw new BaseError('SUBAGENT_PRECHECK_FAILED', `前置要求工具缺席——拒起跑并回执缺口:${gaps.join('、')}${input.availableTools === undefined ? '(宿主工具面不可枚举——fail-closed 全列)' : ''}`);
236
+ }
237
+ // background 能力预检(同预检闸族——provider 声明不受理即拒)
238
+ if (input.background === true && provider.capabilities.background !== true) {
239
+ throw new BaseError('SUBAGENT_PRECHECK_FAILED', `provider「${providerName}」不支持后台收场(capabilities.background=false)——预检拒`);
240
+ }
241
+ // ── 派生工具面 + 白名单交集(04 §10:父面 − 五名 ∩ 白名单) ──
242
+ const effectiveTools = input.availableTools !== undefined
243
+ ? intersectToolWhitelist(deriveToolSurface(input.availableTools), input.tools)
244
+ : input.tools; // 父面不可枚举(纯逻辑腿/无会话语境)——白名单透传,交集执法归 in-process 工厂
245
+ const jobName = input.name ?? providerName;
246
+ const request = {
247
+ prompt: input.prompt,
248
+ ...(effectiveTools !== undefined ? { tools: [...effectiveTools] } : {}),
249
+ ...(input.model !== undefined ? { model: input.model } : {}),
250
+ ...(input.systemPrompt !== undefined ? { systemPrompt: input.systemPrompt } : {}),
251
+ name: jobName,
252
+ depth: input.depth + 1, // 子栈委派工具的深度位(机器注入逐层 +1)
253
+ };
254
+ if (input.background === true) {
255
+ if (options.notify === undefined) {
256
+ warn('通知面缺席——background 结算通知将降级为注册表条目(装配残缺档;批 12 装配面接线位)');
257
+ }
258
+ const handle = options.registry.register({ kind: 'subagent', name: jobName, owner: input.parentSessionId });
259
+ // 子栈机器注入位:审批挂起路由(one-shot 不注入)+ 协作停止观察
260
+ const backgroundRequest = {
261
+ ...request,
262
+ background: true,
263
+ parentSessionId: input.parentSessionId,
264
+ ...(options.notify !== undefined
265
+ ? {
266
+ notifyApproval: (info) => options.notify.notifyApprovalPending({
267
+ parentSessionId: input.parentSessionId,
268
+ jobName,
269
+ approvalId: info.approvalId,
270
+ toolName: info.toolName,
271
+ ...(info.reason !== undefined ? { reason: info.reason } : {}),
272
+ }),
273
+ }
274
+ : {}),
275
+ stopRequested: () => handle.entry.status === 'stopping',
276
+ };
277
+ // 后台收场编舞(fire-and-forget——回执只携 Job 身份):register 先行
278
+ // (m5 定形——Job 条目先落 running〔帽满排队期状态面可见〕、run() 回执
279
+ // jobId 不因帽满延后、取位等待在条目落账后的执行段)→ 取位 → provider
280
+ // 黑盒跑完 → 通知先落(无条件先于归属释放)→ 结算钩子 → Job 条目后销
281
+ void (async () => {
282
+ // 同步 fast-path 优先(与 one-shot 同形——满帽才入异步 FIFO 等待)
283
+ if (!tryAcquireSlot(input.parentSessionId)) {
284
+ await waitSlot(input.parentSessionId);
285
+ }
286
+ let result;
287
+ try {
288
+ result = normalizeResult(await provider.run(backgroundRequest));
289
+ }
290
+ catch (err) {
291
+ result = errorResult(err);
292
+ }
293
+ finally {
294
+ releaseSlot(input.parentSessionId);
295
+ }
296
+ if (options.notify !== undefined) {
297
+ try {
298
+ await options.notify.notifySettled({
299
+ parentSessionId: input.parentSessionId,
300
+ content: subagentSettledContent({ jobName, result }),
301
+ });
302
+ }
303
+ catch (err) {
304
+ warn(`结算通知失败(不吞结算——注册表条目仍终态):${err instanceof Error ? err.message : String(err)}`);
305
+ }
306
+ }
307
+ try {
308
+ await options.onSettled?.({ parentSessionId: input.parentSessionId, jobName, result });
309
+ }
310
+ catch (err) {
311
+ warn(`结算钩子异常(不吞结算):${err instanceof Error ? err.message : String(err)}`);
312
+ }
313
+ handle.settle(terminalOf(result));
314
+ })();
315
+ return { mode: 'background', jobName, jobId: handle.entry.id };
316
+ }
317
+ // ── one-shot:父同步等结果(黑盒——异常折 error 结果不重试) ──
318
+ // 校验闸(深度/路由/预检/能力)已全部跑完——取位在闸后(拒径不占位);
319
+ // 同步 fast-path 取位:有位情形零异步边界直达 provider.run(「run 调用
320
+ // 后同步推杆」的既有调用时序不因闸引入微任务而破坏——m-2 受理即已落
321
+ // 账律同族考量),满帽才入异步 FIFO 等待
322
+ if (!tryAcquireSlot(input.parentSessionId)) {
323
+ await waitSlot(input.parentSessionId);
324
+ }
325
+ try {
326
+ const result = normalizeResult(await provider.run(request));
327
+ return { mode: 'one-shot', result };
328
+ }
329
+ catch (err) {
330
+ return { mode: 'one-shot', result: errorResult(err) };
331
+ }
332
+ finally {
333
+ releaseSlot(input.parentSessionId);
334
+ }
335
+ },
336
+ };
337
+ return service;
338
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * 派生工具面计算(04 §10):子代理工具面 = 父会话工具面 − 五名
3
+ * (fs read/write/edit/ls + bash)∩ 白名单。纯函数——机器与测试共用。
4
+ */
5
+ import { EXCLUDED_FROM_DERIVED_SURFACE } from './types.js';
6
+ /**
7
+ * 派生面:父工具名序列 − 结构性排除五名。
8
+ * 五名剔除语义:fs 四名子自建(驱动层注册——结构性不入派生集);bash 系
9
+ * 「永不升格」总则的特例条款(子管道无人在场应答 def 内部升权)。
10
+ * 保序(父注册序);白名单含被排除名 → 该名被结构性剔除而非报错
11
+ * (结构性条款优先于声明面——不产生「声明了但拿不到」的歧义回执,
12
+ * 交集结果本身即回执)。
13
+ */
14
+ export function deriveToolSurface(parentToolNames) {
15
+ const excluded = new Set(EXCLUDED_FROM_DERIVED_SURFACE);
16
+ return parentToolNames.filter((name) => !excluded.has(name));
17
+ }
18
+ /**
19
+ * 白名单交集执法(04 §10 请求含工具白名单——与 def 的 tools 交集):
20
+ * 有效面 = 派生面 ∩ 白名单(保派生面注册序)。白名单缺省(undefined)=
21
+ * 全派生面;空白名单([])= 空面(合法——纯提示词子代理)。
22
+ */
23
+ export function intersectToolWhitelist(derivedSurface, whitelist) {
24
+ if (whitelist === undefined)
25
+ return [...derivedSurface];
26
+ const allow = new Set(whitelist);
27
+ return derivedSurface.filter((name) => allow.has(name));
28
+ }
29
+ /**
30
+ * 前置要求预检(04 §10 静默退化防御②):缺口 = requires 中不在可用面
31
+ * (父会话全工具名——注意判据是**全父面**非派生面:前置要求声明的是
32
+ * 「宿主工具面的前置要求」,与派生面剔除正交——模型若前置要求 bash
33
+ * 而父面有 bash,预检过(bash 的结构性剔除走派生面另一层))。
34
+ * 返回缺口清单(空 = 预检过)。
35
+ */
36
+ export function findPrecheckGaps(requiresTools, availableToolNames) {
37
+ if (requiresTools === undefined || requiresTools.length === 0)
38
+ return [];
39
+ // 可用面缺席 = 宿主工具面不可枚举——fail-closed 全列(不降级瞎跑)
40
+ if (availableToolNames === undefined)
41
+ return [...requiresTools];
42
+ const available = new Set(availableToolNames);
43
+ return requiresTools.filter((name) => !available.has(name));
44
+ }