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,177 @@
1
+ /**
2
+ * host/tool-policy-store — 跨会话工具策略表文件读写件(04 §9 粘性第 3 款
3
+ * 2026-09-07 定形块的装配侧执法;批 12f-4;2026-09-11 审批分档批六字段行形
4
+ * 扩 + 载体更名 tool-policy.json——本件即更名执法与旧文件升格迁移所在)。
5
+ *
6
+ * 载体 = 数据目录 `tool-policy.json`(单一用途用户资产文件——与
7
+ * boot-failures/ledger 同族 JSON;原名 `allowlist.json`,本批更名)。本件只
8
+ * 管文件 IO 与行校验;匹配判定归 safety 域纯函数半边(matchToolPolicy——
9
+ * engine 不碰文件),草案生成归审批服务(suggestedEntry)。
10
+ *
11
+ * 新旧文件共存序(04 §9 定形块③):**新文件在场即唯一源**(旧文件在场也
12
+ * 不读);**唯旧在场且新缺席才升格读入**(旧三字段形 decision 缺席归一
13
+ * allow——ap-1 读侧归一语义);**旧文件留置不删**(机器永不写入旧名——
14
+ * 升格只发生在读侧,新条目追加恒落新文件名;首次追加即把升格条目物化进
15
+ * 新文件,旧文件自此为惰性遗存。生态未启动实际零存量,防御性迁移)。
16
+ *
17
+ * 读写三律(定形块原文执法):
18
+ * - 读侧装配期载入:缺席 = 空清单零负担;**文件级坏形 = warn 降级视同空
19
+ * 清单**(advisory 免问面坏形降级方向 = 更严〔多问〕不是更松,fail-closed
20
+ * 同向——与 enabled.yaml 拒启律分立);**行级坏形 = 剔除该行 + warn 点名**
21
+ * (防类型脏行进匹配引擎;含 deny 条目带 expiresAt 的方向性坏形;文件不
22
+ * 因读侧改写——机器永不覆写用户手写面)。升格读入语境下旧文件即当前
23
+ * 源——旧文件坏形同律(坏形期回写拒,修复归用户手面)。
24
+ * - 写侧唯一正门(装配注入 persistToolPolicy 回调):**只产 allow 条目**
25
+ * (deny 唯用户手写);append 幂等去重(同 tool+pattern 不二写)+
26
+ * **原子替换**(临时文件 + rename——防撕裂丢条目);**坏形期回写拒**
27
+ * (机器不在坏形文件上追加——防覆写扩大破坏,修复归用户手面)。
28
+ * - fence(§7/§8 carve-out):本文件属数据目录族恒不可写——模型不可改自己
29
+ * 的免问面;本件写入只经装配层回调(用户显式按键触发),文件级只读校验
30
+ * 前置即坏形拒写的第一道闸。
31
+ */
32
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
33
+ import { join } from 'node:path';
34
+ /** 策略表文件名(数据目录单段——04 §9 定形块③定名,2026-09-11 审批分档批更名) */
35
+ export const TOOL_POLICY_BASENAME = 'tool-policy.json';
36
+ /**
37
+ * 旧载体名(更名前遗留)——升格读入用,**本件永不写入此名**(旧文件留置
38
+ * 不删;读侧敏感件保护两名单同列——safety/sensitive 防旧文件读敞门)。
39
+ */
40
+ export const LEGACY_ALLOWLIST_BASENAME = 'allowlist.json';
41
+ /**
42
+ * 单文件解析 + 行级校验(新旧文件同律——升格语境下旧三字段形 decision 缺席
43
+ * 归一 allow)。返回 healthy=false = 文件级坏形(调用方定降级语义)。
44
+ */
45
+ function loadPolicyFile(path, warn) {
46
+ let raw;
47
+ try {
48
+ raw = readFileSync(path, 'utf8');
49
+ }
50
+ catch (err) {
51
+ warn(`策略表读取失败(${path}):${err instanceof Error ? err.message : String(err)}——视同空清单`);
52
+ return { entries: [], healthy: false, unhealthyPath: path };
53
+ }
54
+ // 文件级三检:JSON 可解析 / 顶层对象 / entries 为数组——任一违例整文件降级
55
+ let doc;
56
+ try {
57
+ doc = JSON.parse(raw);
58
+ }
59
+ catch (err) {
60
+ warn(`策略表坏形(${path},JSON 解析失败:${err instanceof Error ? err.message : String(err)})——视同空清单;手改修复前回写拒(防机器覆写扩大破坏)`);
61
+ return { entries: [], healthy: false, unhealthyPath: path };
62
+ }
63
+ if (typeof doc !== 'object' || doc === null || Array.isArray(doc)) {
64
+ warn(`策略表坏形(${path},顶层须为对象 { "entries": [...] })——视同空清单;手改修复前回写拒`);
65
+ return { entries: [], healthy: false, unhealthyPath: path };
66
+ }
67
+ const rows = doc.entries;
68
+ if (!Array.isArray(rows)) {
69
+ warn(`策略表坏形(${path},entries 须为数组)——视同空清单;手改修复前回写拒`);
70
+ return { entries: [], healthy: false, unhealthyPath: path };
71
+ }
72
+ // 行级校验(拒绝式 schema——2026-09-11 审批分档批六字段扩:tool 非空字符串
73
+ // 必填、pattern 可选〔在场须为字符串——空串/缺席对整名族合法;fs/bash 族
74
+ // 条目缺席 = 引擎恒 miss 的惰性行,fail-closed 方向无害〕、decision 闭集
75
+ // 'allow'|'deny'〔缺席 = allow——旧三字段形升格读入语义〕、effect 闭集
76
+ // read|write|exec 可选、reason 可选字符串、expiresAt 可选数值〔**deny 条目带
77
+ // expiresAt = 坏形剔除**——deny 无 TTL,04 §9 定形块③方向性条款〕、未知键
78
+ // 拒)——违例剔行 warn 点名(文件不动:机器永不改写用户手写面)
79
+ const entries = [];
80
+ rows.forEach((row, index) => {
81
+ if (typeof row !== 'object' || row === null) {
82
+ warn(`策略表第 ${index + 1} 行非对象——剔除(文件未改动)`);
83
+ return;
84
+ }
85
+ const { tool, pattern, decision, effect, reason, expiresAt, ...rest } = row;
86
+ if (typeof tool !== 'string' || tool.length === 0) {
87
+ warn(`策略表第 ${index + 1} 行 tool 缺失或非字符串——剔除(文件未改动)`);
88
+ return;
89
+ }
90
+ if (pattern !== undefined && typeof pattern !== 'string') {
91
+ warn(`策略表第 ${index + 1} 行 pattern 须为字符串——剔除(文件未改动)`);
92
+ return;
93
+ }
94
+ if (decision !== undefined && decision !== 'allow' && decision !== 'deny') {
95
+ warn(`策略表第 ${index + 1} 行 decision 闭集外(allow|deny)——剔除(文件未改动)`);
96
+ return;
97
+ }
98
+ if (effect !== undefined && effect !== 'read' && effect !== 'write' && effect !== 'exec') {
99
+ warn(`策略表第 ${index + 1} 行 effect 闭集外(read|write|exec)——剔除(文件未改动)`);
100
+ return;
101
+ }
102
+ if (reason !== undefined && typeof reason !== 'string') {
103
+ warn(`策略表第 ${index + 1} 行 reason 须为字符串——剔除(文件未改动)`);
104
+ return;
105
+ }
106
+ if (expiresAt !== undefined && typeof expiresAt !== 'number') {
107
+ warn(`策略表第 ${index + 1} 行 expiresAt 须为数值——剔除(文件未改动)`);
108
+ return;
109
+ }
110
+ if (decision === 'deny' && expiresAt !== undefined) {
111
+ warn(`策略表第 ${index + 1} 行 deny 条目带 expiresAt 属坏形(deny 无 TTL——永久至手删)——剔除(文件未改动)`);
112
+ return;
113
+ }
114
+ const unknownKeys = Object.keys(rest);
115
+ if (unknownKeys.length > 0) {
116
+ warn(`策略表第 ${index + 1} 行未知键 ${unknownKeys.join('、')}——剔除(文件未改动)`);
117
+ return;
118
+ }
119
+ // 塑形落位:可选字段缺席即不带(JSON 面?键不落盘);decision 缺席归一 allow
120
+ entries.push({
121
+ tool,
122
+ decision: decision ?? 'allow',
123
+ ...(pattern !== undefined ? { pattern } : {}),
124
+ ...(effect !== undefined ? { effect: effect } : {}),
125
+ ...(reason !== undefined ? { reason } : {}),
126
+ ...(expiresAt !== undefined ? { expiresAt } : {}),
127
+ });
128
+ });
129
+ return { entries, healthy: true };
130
+ }
131
+ /**
132
+ * 装配期载入工具策略表(04 §9 定形块读侧律 + 更名升格序)。新文件在场即
133
+ * 唯一源;唯旧在场且新缺席才升格读入(旧文件留置不删);两文件皆缺席 =
134
+ * 空清单 healthy(零负担首启);文件级坏形 = 空 + warn + unhealthy;行级
135
+ * 坏形 = 剔行 + warn 点名。
136
+ */
137
+ export function readToolPolicy(dataDir, options = {}) {
138
+ const warn = options.warn ?? ((message) => process.stderr.write(`${message}\n`));
139
+ // 共存优先序:新名在场即唯一源(旧名在场也不读——防止双源漂移)
140
+ const path = join(dataDir, TOOL_POLICY_BASENAME);
141
+ if (existsSync(path))
142
+ return loadPolicyFile(path, warn);
143
+ // 升格读入:唯旧在场且新缺席——旧三字段形经行级归一(decision 缺省 allow)
144
+ const legacyPath = join(dataDir, LEGACY_ALLOWLIST_BASENAME);
145
+ if (existsSync(legacyPath)) {
146
+ warn(`allowlist.json 已更名 tool-policy.json(2026-09-11 审批分档批)——旧文件条目升格读入,旧文件留置不动(机器永不写旧名;新条目追加落新文件 ${TOOL_POLICY_BASENAME})`);
147
+ return loadPolicyFile(legacyPath, warn);
148
+ }
149
+ return { entries: [], healthy: true };
150
+ }
151
+ /**
152
+ * 写侧唯一正门:追加 **allow 条目**(04 §9 定形块写侧律 + 审批分档批③——
153
+ * 机器永不写 deny,deny 是用户主权面唯用户手写)。幂等去重(同 tool+pattern
154
+ * 的 allow 条目不二写——手写 deny 条目不参与去重键:引擎 deny 优先律下机器
155
+ * allow 追加恒 inert,但去重误报 duplicate 会吞掉用户按键的真实意图呈现);
156
+ * 原子替换落盘(tmp + rename);文件级坏形期拒写(修复归用户手面——升格
157
+ * 语境下旧文件坏形同拒)。写入恒落新名 tool-policy.json(升格条目随首次
158
+ * 追加物化进新文件,旧文件自此为惰性遗存)。
159
+ */
160
+ export function appendToolPolicyEntry(dataDir, draft) {
161
+ const path = join(dataDir, TOOL_POLICY_BASENAME);
162
+ // 坏形拒写第一闸:复用读侧全套校验(坏形期在坏文件上追加 = 覆写扩大破坏)
163
+ const load = readToolPolicy(dataDir);
164
+ if (!load.healthy)
165
+ return 'rejected';
166
+ if (load.entries.some((e) => e.decision !== 'deny' && e.tool === draft.tool && e.pattern === draft.pattern)) {
167
+ return 'duplicate';
168
+ }
169
+ // 机器写入恒带 decision:'allow'(正门只产 allow 条目——六字段形自证)
170
+ const entries = [...load.entries, { tool: draft.tool, pattern: draft.pattern, decision: 'allow' }];
171
+ // 原子替换:临时文件写全量后 rename——撕裂窗口不暴露半文件(用户资产完整性)
172
+ mkdirSync(dataDir, { recursive: true });
173
+ const tmp = `${path}.tmp`;
174
+ writeFileSync(tmp, `${JSON.stringify({ entries }, null, 2)}\n`, 'utf8');
175
+ renameSync(tmp, path);
176
+ return 'appended';
177
+ }
@@ -0,0 +1,212 @@
1
+ /**
2
+ * host/triggers — 触发器注册面(03 §2.2 行 108 ctx.triggers.register 第十一动词
3
+ * + §2.7 触发器行冲突律 + §4.6 开门制;2026-09-07 触发器面 C 批 C-2 注册面笔
4
+ * + C-3 starter 笔)。
5
+ *
6
+ * 一件三面:
7
+ * - **注册表本体**(单实例随装配根创建;插件经 ctx.triggers.register 间接消费
8
+ * ——窗口/频率护栏在 ctx 面执法,本件只管三闸与 starter 交接):
9
+ * 闸一 门检(`triggers.start-run` 高危面默认关——03 §4.6;core: 官方件直开
10
+ * 豁免〔装配即用户意图,§4.6 勘补判据——官方件随构建进包非生态授予面〕;
11
+ * 开门授予集经 `getOpens` **活体读取源**现取现判——与 fire 复检同源,
12
+ * /reload 撤位后旧 starter 复检即拒,开门可收回);
13
+ * 闸二 撞名(TRIGGER_NAME_EXISTS——词法身份面拒绝式,重影即审计归因歧义;
14
+ * 先于格式闸,「名字被占用」比「格式违例」更指向根因,registerMessageRole
15
+ * 同序);
16
+ * 闸三 名词法(TRIGGER_NAME_INVALID——域名两段式〔恰含一个 /、两段均小写
17
+ * 字母数字连字符〕且域前缀 == 本插件 id,core: 件去前缀取 name 段比对
18
+ * ——防跨插件冒名,registerSection 同法)。
19
+ * - **starter 一次性交接**:注册成功即回调 `def.fire(starter)` 注入起会闭包
20
+ * (03 §2.2 行 108 定形注记——重装载经重注册重新注入,旧 starter 因 fire
21
+ * 复检悬空失效不悬空越权)。
22
+ * - **starter 真身工厂**(createTriggerStarterFactory——装配根注入窄依赖面
23
+ * 造 `(pluginId, name) => starter`):fire 复检(活体开门收回)→ Job 受理
24
+ * **先于**起会(「fire 受理先过帽再起会话」——帽满 JOB_LIMIT_REACHED /
25
+ * 显式 jobKind 未登记 JOB_KIND_UNKNOWN,不起会不留孤儿)→ 起无头会话
26
+ * (origin 'trigger' + source `plugin:<id>` 归因 + per-fresh-session 模型
27
+ * 载体)→ 回执三终态映射 Job 终态(completed→completed / aborted→killed /
28
+ * failed→failed;injected/wake-refused = run 未起落 failed)。**starter
29
+ * 永不 throw**——插件事件源回调内执行,throw 即进程级风险;拒与失败一律
30
+ * warn 可观测 + Job 终态收口(无人值守鲁棒性)。
31
+ *
32
+ * 审计实况(2026-09-11 勘正——原注两处挂账均已飞):capability/used 逐次
33
+ * 审计已随 U3 落码批接线(audit_events 载体在场——assembly createAuditFace
34
+ * 单写者;v1 归因面另经事件流 source=`plugin:<id>` 闭集可查——03 §2.2 行 108
35
+ * 定形注记);JobHandle.stop→interrupt 桥接与插件卸载 closeOwner(pluginId)
36
+ * 收口均已随 Job 消费面批兑现(04 §10 定形:onStop 协作中止路由 + 卸载接线位
37
+ * = plugin-unload closer——本文件两桥腿 = starter 注册 onStop 路由到起会
38
+ * 驱动 abort;closeOwner 接线在 plugin-boot 装配件)。
39
+ */
40
+ import { BaseError } from '../contracts/index.js';
41
+ // internal 桶机制符号深导(门检裁决核——03 §4.6;开门是宿主裁决面非插件 API)
42
+ import { adjudicateCapabilityDoor } from '../contracts/api.js';
43
+ /** 触发器名词法(域名两段式——两段均小写字母起头的小写字母数字连字符串,messages CUSTOM_ROLE_RE 同法) */
44
+ const TRIGGER_NAME_RE = /^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/;
45
+ /** 域前缀归一(core: 件去前缀取 name 段比对——prompt-sections domainOf 同法) */
46
+ function domainOf(pluginId) {
47
+ return pluginId.startsWith('core:') ? (pluginId.slice('core:'.length) ?? pluginId) : pluginId;
48
+ }
49
+ /**
50
+ * 触发器注册表(单实例随装配根创建;core: 域与生态域同册——跨插件撞名互斥,
51
+ * 防官方件与生态插件同域重影)。零半注册残留:三闸全过才落册,fire 抛错回滚。
52
+ */
53
+ export class TriggerRegistry {
54
+ options;
55
+ /** 触发器名 → 条目(枚举序 = 注册序——Map 插入序) */
56
+ triggers = new Map();
57
+ constructor(options) {
58
+ this.options = options;
59
+ }
60
+ /**
61
+ * 注册触发器(拒绝式三闸:门检 → 撞名 → 格式)。
62
+ * @returns 注销器(只摘本人条目——重注后旧注销器不误摘接任者)
63
+ */
64
+ register(pluginId, def) {
65
+ // 闸一:高危面门检(03 §4.6——triggers.start-run 默认关;授予集活体现判,
66
+ // 与 fire 复检同源开门可收回)。core: 官方件直开豁免——装配即用户意图
67
+ if (!pluginId.startsWith('core:')) {
68
+ const verdict = adjudicateCapabilityDoor(this.options.getOpens(pluginId), 'triggers.start-run');
69
+ if (!verdict.ok) {
70
+ throw new BaseError('PLUGIN_CAPABILITY_DOOR_CLOSED', `${verdict.message}(插件 ${pluginId})`);
71
+ }
72
+ }
73
+ // 闸二:撞名(先于格式闸——在册名必已合法,「名字被占用」更指向根因)
74
+ const existing = this.triggers.get(def.name);
75
+ if (existing !== undefined) {
76
+ throw new BaseError('TRIGGER_NAME_EXISTS', `触发器 ${def.name} 已在册(注册方 ${existing.owner})——词法身份面拒绝式,重影即审计归因歧义(03 §2.7)`);
77
+ }
78
+ // 闸三:名词法(域名两段式 + 域前缀 == 本插件 id——core: 件去前缀比对)
79
+ const slash = def.name.indexOf('/');
80
+ if (!TRIGGER_NAME_RE.test(def.name) || slash <= 0 || def.name.slice(0, slash) !== domainOf(pluginId)) {
81
+ throw new BaseError('TRIGGER_NAME_INVALID', `触发器名 ${def.name} 非域名两段式或域前缀 ≠ 本插件 id(须 ${domainOf(pluginId)}/名——恰含一个 / 且两段均小写字母数字连字符;core: 件去前缀取 name 段比对——防跨插件冒名,03 §2.7)`);
82
+ }
83
+ const entry = { name: def.name, owner: pluginId, description: def.description };
84
+ this.triggers.set(def.name, entry);
85
+ // 注册成功即一次性交接 starter(重装载经重注册重新注入;旧 starter 因 fire
86
+ // 复检悬空失效)。fire 抛错 = 注册未完成:回滚在册面再传播(装载器归
87
+ // PLUGIN_APPLY_FAILED 处置,零半注册残留)
88
+ try {
89
+ def.fire(this.options.makeStarter(pluginId, def.name));
90
+ }
91
+ catch (err) {
92
+ this.triggers.delete(def.name);
93
+ throw err;
94
+ }
95
+ return () => {
96
+ // 注销器只摘本人条目(防过期时序误摘接任者——registerMessageRole 同守卫)
97
+ if (this.triggers.get(def.name) === entry)
98
+ this.triggers.delete(def.name);
99
+ };
100
+ }
101
+ /** 在册名单快照(枚举序 = 注册序——测试/审计读侧) */
102
+ list() {
103
+ return [...this.triggers.values()];
104
+ }
105
+ }
106
+ /* ---------------- starter 真身(C-3 装配批——起无头会话 + Job 托管编舞) ---------------- */
107
+ /** trigger kind 缺省并行帽(03 §2.2 行 108 定形注记——host 装配期自登 kind + 帽 4) */
108
+ export const TRIGGER_JOB_PARALLEL_LIMIT = 4;
109
+ /**
110
+ * starter 真身工厂(装配根消费——TriggerRegistry makeStarter 位注入)。
111
+ *
112
+ * 编舞序(受理先行的对称收口——任何一步失败都让 Job 落终态,不留 running
113
+ * 悬空条目):fire 复检(core: 豁免,拒 = warn + return)→ spec 轻校验 →
114
+ * Job 受理(先过帽再起会)→ 起会(origin 'trigger')→ 提交(source
115
+ * `plugin:<id>`)→ 回执三终态映射 Job 终态。回执腿 fire-and-forget:starter
116
+ * 同步返回,run 终态经 JobSettledEvent 活体通知可观测。
117
+ */
118
+ export function createTriggerStarterFactory(deps) {
119
+ return (pluginId, name) => (spec) => {
120
+ // —— fire 复检(03 §4.6 开门可收回):与注册闸同源活体判——/reload 撤位后
121
+ // 旧 starter 现判现拒;core: 官方件直开豁免(同注册闸判据)
122
+ if (!pluginId.startsWith('core:')) {
123
+ const verdict = adjudicateCapabilityDoor(deps.getOpens(pluginId), 'triggers.start-run');
124
+ if (!verdict.ok) {
125
+ deps.warn(`触发器 ${name} 起会被拒:${verdict.message}(插件 ${pluginId}——fire 复检,开门可收回)`);
126
+ return;
127
+ }
128
+ }
129
+ // —— spec 轻校验(插件代码运行期传参防御——TS 形状之外的运行时防线)
130
+ if (typeof spec.prompt !== 'string' || spec.prompt.length === 0) {
131
+ deps.warn(`触发器 ${name} 起会参数坏形:prompt 缺席或空(插件 ${pluginId})`);
132
+ return;
133
+ }
134
+ // —— Job 受理先于起会(「fire 受理先过帽再起会话」:帽满 JOB_LIMIT_REACHED /
135
+ // 显式 jobKind 未登记 JOB_KIND_UNKNOWN 都不造孤儿会话)。owner = 插件 id
136
+ // 围栏键(插件卸载 closeOwner(pluginId) 收口——Job 消费面批接线
137
+ // plugin-unload closer)。onStop 协作中止路由(Job 消费面批两桥之一):
138
+ // stop/closeOwner 置 stopping 后路由到起会驱动的 abort——run 协作中止
139
+ // (aborted 回执 settle killed 与收口兜底 first-wins 竞速,先落者胜);
140
+ // opened 闭包晚绑(受理先于起会——路由时 opened 必已赋值,防御判空
141
+ // 覆盖 create 抛错后的悬空路由形)
142
+ const title = spec.title ?? `${name} ${new Date().toISOString()}`;
143
+ let opened;
144
+ let job;
145
+ try {
146
+ job = deps.jobs.register({
147
+ kind: spec.jobKind ?? 'trigger',
148
+ name: title,
149
+ owner: pluginId,
150
+ onStop: () => {
151
+ opened?.driver.abort();
152
+ },
153
+ });
154
+ }
155
+ catch (err) {
156
+ deps.warn(`触发器 ${name} Job 受理失败(插件 ${pluginId}):${err instanceof BaseError ? `[${err.code}] ${err.message}` : String(err)}`);
157
+ return;
158
+ }
159
+ // —— 起无头会话(origin 'trigger';model 纯内存 per-fresh-session 载体)
160
+ try {
161
+ opened = deps.stack.manager.create({
162
+ origin: 'trigger',
163
+ workspaceRoot: deps.workspaceRoot(),
164
+ title,
165
+ ...(spec.model !== undefined ? { model: spec.model } : {}),
166
+ });
167
+ }
168
+ catch (err) {
169
+ const detail = `起会失败:${err instanceof Error ? err.message : String(err)}`;
170
+ job.settle({ status: 'failed', detail });
171
+ deps.warn(`触发器 ${name} 起会失败(插件 ${pluginId}):${detail}`);
172
+ return;
173
+ }
174
+ // —— 提交首条输入(source=`plugin:<id>` 归因——05 §3.1 受控注入位)
175
+ const submitted = deps.stack.submitText(opened.sessionId, spec.prompt, { source: `plugin:${pluginId}` });
176
+ if (submitted === undefined) {
177
+ job.settle({ status: 'failed', detail: 'run 未起——会话驱动缺席' });
178
+ deps.warn(`触发器 ${name} 提交失败:会话驱动缺席(插件 ${pluginId})`);
179
+ return;
180
+ }
181
+ // —— capability/used 逐次落账(05 §1.1——triggers.start-run 腿,C 批挂账
182
+ // U3-5 兑现):run 真起才算使用;core: 豁免门检照记(豁免免的是门不是账)
183
+ deps.onCapabilityUsed?.(pluginId, name);
184
+ // —— 回执 → Job 终态映射(aborted→killed 承 Job 终态词——SubagentStopReason
185
+ // 同映射;injected/wake-refused 两收执 = run 未起,落 failed 交代去向)
186
+ void submitted.then((result) => {
187
+ if (result.status === 'completed') {
188
+ job.settle({ status: 'completed' });
189
+ }
190
+ else if (result.status === 'aborted') {
191
+ job.settle({ status: 'killed', detail: 'run 被中止' });
192
+ }
193
+ else if (result.status === 'failed') {
194
+ job.settle({
195
+ status: 'failed',
196
+ ...(result.errorMessage !== undefined ? { detail: result.errorMessage } : {}),
197
+ });
198
+ }
199
+ else if (result.status === 'injected') {
200
+ job.settle({
201
+ status: 'failed',
202
+ detail: `run 未起——输入落 inject 通道(durable seq ${result.seq},随下次启动带入)`,
203
+ });
204
+ }
205
+ else {
206
+ job.settle({ status: 'failed', detail: 'run 未起——连续后台唤醒超帽拒收(wake-refused)' });
207
+ }
208
+ }, (err) => {
209
+ job.settle({ status: 'failed', detail: `run 异常:${err instanceof Error ? err.message : String(err)}` });
210
+ });
211
+ };
212
+ }
@@ -0,0 +1,216 @@
1
+ /**
2
+ * host/tui-entry — TUI 主入口装配(批 12e;07 §5 无参启动 = TUI 主入口)。
3
+ *
4
+ * 装配序(channels/service.ts 头注真源;公共段批 12f-3 抽 assembly.ts——与
5
+ * dump-config/plugins list 诊断命令同一合成代码路径):assembleHostStack
6
+ * (运行时→logger→根作用域/总线→conversation 栈→插件装载,:memory: 同构
7
+ * 纪律防侧门件)→ [本件 TUI 段] --port webui 开面(装载后/挂接前)→
8
+ * openStartupSession(07 §5 启动会话策略:cwd 归一根取最新续接、无则新建;
9
+ * resumeSessionId 在场 = 按 id 续接——sessions resume <id> 的 CLI 载体〔批 20d〕)
10
+ * → TuiBackend 组装(提交/打断/退出/命令分发/todo 回看/补全三源/version
11
+ * 基线/生产定时器)→ addBackend → start → registerSession → focus 首画 →
12
+ * 主循环 await 退出 → runtime.shutdown 六步退出序(closer 内含 backend.stop
13
+ * 出屏复原)。
14
+ *
15
+ * 退出码:0 = ctrl+d 空框优雅退出;1 = 运行时组装失败(单活跃机拒入/开库
16
+ * 失败——干净退出不写 crash.log,非崩溃)、启用清单损坏(同干净退出档)或
17
+ * 运行期异常(先 writeCrashLog 再退)。
18
+ * 信号路径独立:SIGINT①/SIGTERM → onGraceful → runtime.shutdown → exit(0)
19
+ * (main.ts 编舞;本件 closer 注册保证出屏复原在该路径同样执行)。
20
+ */
21
+ import { FileMentionSource, ProcessTerminalIO, TuiBackend } from '../channels/index.js';
22
+ import { canonicalWorkspaceRoot } from '../context/index.js';
23
+ import { foldTodoTable } from '../conversation/index.js';
24
+ import { sanitizeEntryForReadout } from '../memory/index.js';
25
+ import { assembleHostStack } from './assembly.js';
26
+ import { startSchedulerClock } from './core-plugins.js';
27
+ import { runWithSessionAnchor } from './session-anchor.js';
28
+ import { openWebuiFace } from './webui-bridge.js';
29
+ /**
30
+ * TUI 主入口。阻塞至用户退出(ctrl+d 空框)或异常;返回进程退出码。
31
+ */
32
+ export async function runTuiEntry(options) {
33
+ // —— 装配序公共段(12f-3 抽件):与 dump-config/plugins list 诊断命令同一
34
+ // 合成代码路径(07 §5 :memory: 同构纪律——防侧门件 assembly.ts 唯一真源);
35
+ // TUI 形 = 真数据目录 + 真库(memory 组合形归诊断命令)——
36
+ const assembly = await assembleHostStack({
37
+ runtime: {
38
+ ...(options.dataDir !== undefined ? { dataDir: options.dataDir } : {}),
39
+ ...(options.memory === true ? { memory: true } : {}),
40
+ },
41
+ noPlugins: options.flags.noPlugins === true,
42
+ // 快速试件透传(--plugin-file——03 §7 生态启动批 eco-3a;缺席形省键)
43
+ ...(options.flags.pluginFile !== undefined ? { pluginFile: options.flags.pluginFile } : {}),
44
+ debug: options.flags.debug === true,
45
+ version: options.version ?? '0.0.0',
46
+ ...(options.providers !== undefined ? { providers: options.providers } : {}),
47
+ ...(options.model !== undefined ? { model: options.model } : {}),
48
+ ...(options.env !== undefined ? { env: options.env } : {}),
49
+ ...(options.sandboxMode !== undefined ? { sandboxMode: options.sandboxMode } : {}),
50
+ ...(options.corePlugins !== undefined ? { corePlugins: options.corePlugins } : {}),
51
+ ...(options.onRuntime !== undefined ? { onRuntime: options.onRuntime } : {}),
52
+ });
53
+ if (!assembly.ok) {
54
+ // 两档呈报:crashed = 意外异常(crash.log 已在装配件内写——memory 形跳过);
55
+ // 干净退出档 = 启动失败(单活跃机/开库/启用清单损坏——不写 crash.log)
56
+ process.stderr.write(`${assembly.crashed ? `TUI 运行失败:${assembly.message}` : assembly.message}\n`);
57
+ return assembly.exitCode;
58
+ }
59
+ const { runtime, stack, scope } = assembly;
60
+ let exitCode = 0;
61
+ try {
62
+ // —— scheduler 挂钟起钟(批 20c——长驻形编舞):件在场即起(重启补推进
63
+ // + 排首轮轮询),停钟挂 closer(engine 真身定时器非 unref——不挂停钟
64
+ // 会把进程拖活到 60s belt 定时器);件缺席 = no-op(件禁用语义族)——
65
+ startSchedulerClock(scope, runtime);
66
+ // —— --port webui 统一 HTTP 面开面(批 12f-2c 落、18a-3' 三入口咬合;
67
+ // 03 §10.4 host 接线):装载后开面(插件注册面先就位)、TUI 挂接前开
68
+ // 网络面(closer 注册序先于 tui-backend——drain 时网络面先收口再出屏);
69
+ // backend 挂接在桥共用挂载段内完成(与 TuiBackend 并存扇出——多 backend
70
+ // 信封路由按 sessionId 各投各);横幅在 TUI 起屏后补发(开面时 backends
71
+ // 尚空 notify 扇出无人接帧——见下方 focus 后发)。批 19e 件在场执法:
72
+ // sdk 件缺席 = 面本体件禁用语义族——warn 一行不开面(TUI 屏本体不受
73
+ // 累);webui 件缺席 = 面开而 /api/* 404(mountKit 缺席形——openWebuiFace
74
+ // 内分档披露,横幅同步分档)——
75
+ let webuiOpen;
76
+ let webuiMounted = false;
77
+ if (options.flags.port !== undefined) {
78
+ const sdkKit = scope.tryGet('sdk-http-face');
79
+ if (sdkKit === undefined) {
80
+ process.stderr.write('warn:core:sdk 件未装载——--port 人面不开(07 §5 daemon 拒启同族;TUI 屏不受累)\n');
81
+ }
82
+ else {
83
+ const mountKit = scope.tryGet('webui-face-mount');
84
+ webuiMounted = mountKit !== undefined;
85
+ await openWebuiFace({
86
+ stack,
87
+ runtime,
88
+ port: options.flags.port,
89
+ ...(mountKit !== undefined ? { mountKit } : {}),
90
+ // U5-2:插件道路由受理器经 core:sdk kit 透传(snapshot/attachFace)
91
+ ...(sdkKit.pluginRoutes !== undefined ? { pluginRoutes: sdkKit.pluginRoutes } : {}),
92
+ onOpen: (info) => {
93
+ webuiOpen = info;
94
+ options.onWebuiOpen?.(info);
95
+ },
96
+ });
97
+ }
98
+ }
99
+ // 启动会话策略(07 §5):无参启动按 cwd 取最新会话——有则续接无则新建;
100
+ // resumeSessionId 在场(sessions resume <id> 的 CLI 载体)= 按 id 续接
101
+ // (与「按 cwd 取最新」互补)——id 缺席干净退 1(打错 id 不造新会话、不
102
+ // 写 crash.log;finally 仍走 shutdown 六步收口,此点尚未起 TUI 屏)
103
+ let session;
104
+ if (options.resumeSessionId !== undefined) {
105
+ let opened;
106
+ try {
107
+ opened = stack.manager.open(options.resumeSessionId);
108
+ }
109
+ catch {
110
+ process.stderr.write(`sessions resume 失败:会话不存在(${options.resumeSessionId})——用 sessions list 查在册 id\n`);
111
+ return 1;
112
+ }
113
+ // @ 文件补全锚 = 会话自身工作区根(行面现读;缺席回退启动 cwd)
114
+ const row = runtime.persistence.store.getSessionRow(options.resumeSessionId);
115
+ session = {
116
+ sessionId: opened.sessionId,
117
+ driver: opened.driver,
118
+ resumed: true,
119
+ workspaceRoot: canonicalWorkspaceRoot(row?.workspaceRoot ?? options.cwd ?? process.cwd()),
120
+ };
121
+ }
122
+ else {
123
+ session = stack.openStartupSession(options.cwd ?? process.cwd());
124
+ }
125
+ const io = options.io ?? new ProcessTerminalIO();
126
+ let quitResolve = () => { };
127
+ const quitDone = new Promise((resolve) => {
128
+ quitResolve = resolve;
129
+ });
130
+ // 补全命令源:通道核命令表 → '/' 前缀条目(@ 文件段源锚工作区根)
131
+ const mentions = new FileMentionSource({ basePath: session.workspaceRoot });
132
+ const rows = io.size().rows;
133
+ const backend = new TuiBackend(io, {
134
+ sessionId: session.sessionId,
135
+ onSubmit: (sessionId, text) => {
136
+ void stack.submitText(sessionId, text); // fire-and-forget——回执经信封回流
137
+ },
138
+ onInterrupt: (sessionId) => stack.interrupt(sessionId),
139
+ onQuit: () => quitResolve(),
140
+ // 命令执行窗自动锚(ix-2——07 §4.3 档位 2):发起会话 = 聚焦会话
141
+ //(兜底启动会话——焦点空悬时命令仍属 TUI 主会话);ALS 语境继承语义
142
+ //——命令 handler 及其 fire-and-forget 尾链零自觉锚定(显式 opts
143
+ // .sessionId 优先)。sessionId 实参同笔透传(CommandArgs 显式位)。
144
+ dispatchCommand: (input) => {
145
+ const sid = stack.channels.focusedId ?? session.sessionId;
146
+ return runWithSessionAnchor(sid, () => stack.channels.dispatchCommand(input, sid));
147
+ },
148
+ todoFor: (sessionId) => {
149
+ const driver = stack.driverOf(sessionId);
150
+ return driver === undefined ? null : foldTodoTable(driver.session.events());
151
+ },
152
+ autocomplete: {
153
+ commands: (query) => commandItems(stack.channels.listCommands(), query),
154
+ mentions: (query) => mentions.get(query),
155
+ },
156
+ // 装配实测定值(07 §4.1):编辑器可视行 = 终端高 30%(下钳 3)
157
+ maxVisibleLines: Math.max(3, Math.floor(rows * 0.3)),
158
+ ...(options.version !== undefined ? { version: options.version } : {}),
159
+ // 生产定时器注入(保活/帧帽真定时——缺省同步直出仅测试语义)
160
+ schedule: (fn, ms) => setTimeout(fn, ms),
161
+ cancelSchedule: (handle) => clearTimeout(handle),
162
+ });
163
+ // 出屏复原进退出序 closer——quit 路径与信号路径(onGraceful→shutdown)同享
164
+ runtime.registerCloser({ label: 'tui-backend', fn: () => backend.stop() });
165
+ // —— /memory 管理面材料注入(mm 批——06 §7 形态定形注):boot 已完成
166
+ //(assembleHostStack 内插件装载),core:memory 服务面在场(库座在位且件
167
+ // 装载)→ 后置注入 TuiBackend 后置位:ownerKeys/runExport 经服务面传真
168
+ // 身(导出闭包 = /memory 命令注册同一形),消毒函数直取 §8.2 统一函数
169
+ // 本尊(「同一函数」由装配保证——channels 侧窄面结构兼容 MemoryDao)。
170
+ // 件缺席 = 不注入(openMemory 返 false——/memory 命令核侧 notify 降级)
171
+ const memoryFace = scope.tryGet('memory');
172
+ if (memoryFace !== undefined) {
173
+ backend.setMemoryScreen({
174
+ ownerKeys: memoryFace.ownerKeys,
175
+ dao: memoryFace.dao,
176
+ sanitize: sanitizeEntryForReadout,
177
+ exportCommand: memoryFace.runExport,
178
+ });
179
+ }
180
+ stack.channels.addBackend(backend);
181
+ backend.start();
182
+ stack.channels.registerSession(session.sessionId);
183
+ await stack.channels.focus(session.sessionId); // 启动投影首画(含 resume 历史回读)
184
+ // webui 开面横幅(18a-3'):TuiBackend 起屏后经 channels.notify 扇出——
185
+ // notify 恒扇出(webui backend 同帧收到,浏览器通知位随活);横幅走屏
186
+ // 留痕面只带 URL,token 不入屏(令牌仅 stderr 一次性——屏流可回滚/截屏,
187
+ // 非披露通道);批 19e 分档:webui 件缺席形面仍开(SDK 面)——横幅诚实
188
+ // 报 HTTP 面形不虚报 Web 界面
189
+ if (webuiOpen !== undefined && webuiMounted) {
190
+ stack.channels.notify(session.sessionId, `Web 界面已开面:http://${webuiOpen.host}:${webuiOpen.port}/(访问令牌见启动 stderr——仅此一次显示)`, { level: 'info' });
191
+ }
192
+ else if (webuiOpen !== undefined) {
193
+ stack.channels.notify(session.sessionId, `HTTP 面已开面:http://${webuiOpen.host}:${webuiOpen.port}/(webui 件未装载——/v1/* 程序调用面在场,/api/* 404)`, { level: 'info' });
194
+ }
195
+ await quitDone; // 主循环——输入事件驱动,直至 ctrl+d 空框退出
196
+ }
197
+ catch (err) {
198
+ runtime.writeCrashLog(err); // 崩溃取证先行(memory 形跳过——件内语义)
199
+ process.stderr.write(`TUI 运行失败:${err instanceof Error ? err.message : String(err)}\n`);
200
+ exitCode = 1;
201
+ }
202
+ finally {
203
+ await runtime.shutdown(); // 幂等六步:abort → closer(backend.stop 出屏)→ flush → …
204
+ }
205
+ return exitCode;
206
+ }
207
+ /** 命令表 → 补全条目('/' 前缀过滤——query 已去斜杠,AutocompleteSources 契约) */
208
+ function commandItems(specs, query) {
209
+ return specs
210
+ .filter((spec) => spec.name.startsWith(query))
211
+ .map((spec) => ({
212
+ label: `/${spec.name}`,
213
+ ...(spec.description !== undefined ? { detail: spec.description } : {}),
214
+ replacement: `/${spec.name}`,
215
+ }));
216
+ }