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,12 @@
1
+ export { AGENT_PRE_STEP_EVENT, CONTEXT_TRANSFORM_EVENT, DEFAULT_RETRY_POLICY, SESSION_LIFECYCLE_EVENT, } from './types.js';
2
+ export { reseedTimeline } from './reseed.js';
3
+ export { ConversationDriver } from './driver.js';
4
+ // 操控面码注册(02 §5.3 SESSION_ 族操控五码——e-4 落码批;import 发生才注册)
5
+ import './codes.js';
6
+ export { A2A_ROUND_LIMIT_DEFAULT, bindControlForPlugin, CONTROL_CROSS_CAPABILITY, controlSourceOf, createSessionsControl, SESSIONS_CONTROL_SERVICE, } from './control.js';
7
+ export { createControlTools } from './control-tools.js';
8
+ export { TODO_ROLE, createTodoTool, ensureTodoRole, foldTodoTable, renderTodoTable, todoSnapshotMessage, } from './todo.js';
9
+ export { wireSessionApproval } from './approval-wiring.js';
10
+ export { assembleOpenTools } from './open-tools.js';
11
+ export { AGENT_SERVICE_NAME, notifyRunSettled, provideAgentService } from './agent-service.js';
12
+ export { SESSION_HOOK_NAMES, SessionManager } from './sessions.js';
@@ -0,0 +1,169 @@
1
+ import { isStandardMessage } from '../contracts/index.js';
2
+ import { normalizeUsage } from './reseed.js';
3
+ /** 预算刀降级标记核心串(session/budget.ts 真源标记的稳定子串) */
4
+ const DEGRADATION_MARKERS = ['…[truncated ', 'image-blob-dropped'];
5
+ /** 诊断摘录帽(红消息里的规范形截断长度——诊断可读性,非语义面) */
6
+ const SNIPPET_LIMIT = 240;
7
+ /**
8
+ * 规范序列化:对象键递归排序的确定性 JSON(键序不敏感对拍的前提——两侧
9
+ * 构造路径独立,键插入序天然可异)。undefined 值键随 stringify 丢弃——
10
+ * 「在场 undefined ≡ 缺席」正是白名单外的兜底语义。
11
+ */
12
+ function canonicalJson(value) {
13
+ return JSON.stringify(value, (_key, val) => {
14
+ if (val !== null && typeof val === 'object' && !Array.isArray(val)) {
15
+ const record = val;
16
+ const sorted = {};
17
+ for (const key of Object.keys(record).sort())
18
+ sorted[key] = record[key];
19
+ return sorted;
20
+ }
21
+ return val;
22
+ });
23
+ }
24
+ /** 诊断摘录(超帽截断加省略号——红消息不吞日志) */
25
+ function snippet(text) {
26
+ return text.length <= SNIPPET_LIMIT ? text : `${text.slice(0, SNIPPET_LIMIT)}…`;
27
+ }
28
+ /**
29
+ * assistant 内容块规范形:toolCall 块归尾(与 reseedTimeline「装回尾部」
30
+ * 同律——durable 分立事件下交错序无账,两侧都归一到尾部序再对拍)。
31
+ */
32
+ function canonicalAssistantContent(blocks) {
33
+ const plain = [];
34
+ const calls = [];
35
+ for (const block of blocks) {
36
+ if (block.type === 'toolCall')
37
+ calls.push({ type: 'toolCall', id: block.id, name: block.name, arguments: block.arguments });
38
+ else
39
+ plain.push(block);
40
+ }
41
+ return [...plain, ...calls];
42
+ }
43
+ /** 单消息规范形(model-visible 白名单——见件头注) */
44
+ function canonicalMessageForm(message) {
45
+ if (!isStandardMessage(message)) {
46
+ // 自定义角色:role + content 原样对拍(对拍红素材——wiring 零 durable
47
+ // 写点位,活侧出现即暗通道候选,规范形不吞)
48
+ return { kind: 'custom', role: message.role, content: message.content };
49
+ }
50
+ switch (message.role) {
51
+ case 'user':
52
+ return { kind: 'user', content: message.content };
53
+ case 'assistant':
54
+ return {
55
+ kind: 'assistant',
56
+ content: canonicalAssistantContent(message.content),
57
+ usage: normalizeUsage(message.usage),
58
+ stopReason: message.stopReason,
59
+ ...(message.errorMessage !== undefined ? { errorMessage: message.errorMessage } : {}),
60
+ };
61
+ case 'toolResult':
62
+ return {
63
+ kind: 'toolResult',
64
+ toolCallId: message.toolCallId,
65
+ toolName: message.toolName,
66
+ content: message.content,
67
+ isError: message.isError,
68
+ };
69
+ }
70
+ }
71
+ /**
72
+ * 单消息结构形(预算刀豁免位的内容降级对拍):角色 / toolCall id·name
73
+ * 清单(arguments 豁免——被截后解析兜底 {},与真值永假)/ stopReason /
74
+ * isError。内容文本腿豁免(刀后字节级对拍永假)。
75
+ */
76
+ function structuralMessageForm(message) {
77
+ if (!isStandardMessage(message))
78
+ return { kind: 'custom', role: message.role };
79
+ switch (message.role) {
80
+ case 'user':
81
+ return { kind: 'user' };
82
+ case 'assistant':
83
+ return {
84
+ kind: 'assistant',
85
+ plainBlockTypes: message.content.filter((block) => block.type !== 'toolCall').map((block) => block.type),
86
+ calls: message.content
87
+ .filter((block) => block.type === 'toolCall')
88
+ .map((block) => ({ id: block.id, name: block.name })),
89
+ stopReason: message.stopReason,
90
+ };
91
+ case 'toolResult':
92
+ return {
93
+ kind: 'toolResult',
94
+ toolCallId: message.toolCallId,
95
+ toolName: message.toolName,
96
+ isError: message.isError,
97
+ };
98
+ }
99
+ }
100
+ /** 投影原始形的消息级降级扫描(canonicalJson 全文含任一标记即降级位) */
101
+ function projectedIsDegraded(projected) {
102
+ const raw = projected.type === 'user'
103
+ ? projected.content
104
+ : projected.type === 'assistant'
105
+ ? {
106
+ content: projected.content,
107
+ toolCalls: projected.toolCalls,
108
+ ...(projected.errorMessage !== undefined ? { errorMessage: projected.errorMessage } : {}),
109
+ }
110
+ : { output: projected.output, arguments: projected.arguments };
111
+ const text = canonicalJson(raw);
112
+ return DEGRADATION_MARKERS.some((marker) => text.includes(marker));
113
+ }
114
+ /**
115
+ * 降级掩码(预算刀豁免位清单):投影逐消息判降级——掩码[i] = true 表示
116
+ * 第 i 条重建消息源自被截事件,对拍降为结构级。掩码从投影原始形判而非
117
+ * 重建形:arguments 被截后 reseedTimeline 解析兜底 {},标记在重建形已蒸发,
118
+ * 唯投影形(事件 data 原值)保真。
119
+ */
120
+ export function degradationMask(projection) {
121
+ return projection.map(projectedIsDegraded);
122
+ }
123
+ /** 角色名(诊断用——缺位形明示) */
124
+ function roleOf(message) {
125
+ return message === undefined ? '(缺位)' : message.role;
126
+ }
127
+ /** 红消息构造(首分歧位 + 双形摘录 + 病灶指认——fail-loud 诊断面) */
128
+ function driftError(index, live, expected, reason, liveForm, expectedForm) {
129
+ return new Error(`模型可见即已记录对拍红(05 §1.2 总拍):timeline 活数组与日志重建漂移——${reason}。` +
130
+ `首分歧位 ${index}:live ${roleOf(live[index])} vs 重建 ${roleOf(expected[index])}。` +
131
+ `live 形 ${snippet(canonicalJson(liveForm))};重建形 ${snippet(canonicalJson(expectedForm))}。` +
132
+ `驱动 bug——存在绕过 durable 落账的模型可见写入(loop「入列↔emit」对偶被绕过)或落账翻译腿失真(wiring 翻译丢字段/变形)`);
133
+ }
134
+ /**
135
+ * 请求关口总拍断言(05 §1.2——恒开执法位唯一消费面是 driver.onTransformContext
136
+ * 入口)。逐条规范形比对;degraded[i] = true 的位置降为结构形比对(预算刀
137
+ * 豁免);长度差单列诊断。等价零动作,漂移 throw(驱动 bug 语义——与
138
+ * occludeFailedTail 锚缺席同律,不占公开错误码面)。
139
+ *
140
+ * @param live 驱动 timeline 活数组(请求组装时点的模型可见持久消息序列)
141
+ * @param expected 日志独立重建(reseedTimeline 产物——与 live 同源不同路)
142
+ * @param degraded 降级掩码(degradationMask 产物;缺省全 false)
143
+ */
144
+ export function assertModelVisibleTimeline(live, expected, degraded) {
145
+ const n = Math.min(live.length, expected.length);
146
+ for (let i = 0; i < n; i += 1) {
147
+ if (degraded?.[i] === true) {
148
+ // 预算刀豁免位:结构形比对(内容腿降级——刀后字节级对拍永假)
149
+ const liveForm = structuralMessageForm(live[i]);
150
+ const expectedForm = structuralMessageForm(expected[i]);
151
+ if (canonicalJson(liveForm) !== canonicalJson(expectedForm)) {
152
+ throw driftError(i, live, expected, '结构异(预算刀豁免位——内容腿已降级,结构仍不等价)', liveForm, expectedForm);
153
+ }
154
+ continue;
155
+ }
156
+ const liveForm = canonicalMessageForm(live[i]);
157
+ const expectedForm = canonicalMessageForm(expected[i]);
158
+ if (canonicalJson(liveForm) !== canonicalJson(expectedForm)) {
159
+ throw driftError(i, live, expected, '内容异(规范形不等价)', liveForm, expectedForm);
160
+ }
161
+ }
162
+ if (live.length !== expected.length) {
163
+ // 长度差诊断(前 n 条已逐条过拍——本红专指尾部多出/缺失)
164
+ const surplus = live.length > expected.length
165
+ ? 'live 多出——存在绕过 durable 落账的模型可见写入(暗通道)'
166
+ : '重建多出——存在落账后未入 timeline 的 surplus(翻译腿多写/入列腿漏推)';
167
+ throw new Error(`模型可见即已记录对拍红(05 §1.2 总拍):timeline 活数组与日志重建漂移——长度差:live ${live.length} 条 vs 重建 ${expected.length} 条(前 ${n} 条规范形等价;${surplus})。驱动 bug fail-loud`);
168
+ }
169
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * fresh 作用域审批三件之③(04 §9 守门安装)+ open 域工具装配——会话级
3
+ * 工具面的组装面(04 §7 三段管道唯一合法路径执法位 / 03 §2.3 defineTool 形)。
4
+ *
5
+ * 装配序(一次成型,host 装配根 / 驱动测试面注入):
6
+ * ① 词汇接线——工具族事件词注册(contracts TOOL_EVENT_NAMES 的装配消费面);
7
+ * ② 审批三件前两件(wireSessionApproval:服务 + answerer + settlePending);
8
+ * ③ 守门安装(installSafetyGate **先装本行**——waterfall 注册序即执行序,
9
+ * carve-out 硬拒 / 策略表 allow 免问 / write-effect 审批对最先执法;后续
10
+ * 守门者(插件拦截族)装在其后);
11
+ * ④ 管道 + 注册表(gate/decision durable 落账接线——守门不可绕不变式的
12
+ * 断言对象);
13
+ * ⑤ 工具族:fs 四件(read/write/edit/ls——fence 数据源 createRootsProvider
14
+ * 与守门行同档位单源)+ 检索两件(find/grep)+ bash(exec 服务面
15
+ * scope.tryGet 诚实缺席——exec 禁用 = coding 降级对话本体仍通)+
16
+ * todo 一件(全量快照 durable 落 todo/write)。
17
+ *
18
+ * 工具注册走注册表驱动层({driver: sessionId}——per-session 工具面);
19
+ * agentToolsFor 快照即驱动 tools 面直用形。dispose 按 LIFO 拆解(工具注册 →
20
+ * 守门 → answerer)。
21
+ */
22
+ import { canonicalWorkspaceRoot } from '../context/index.js';
23
+ import { TOOL_EVENT_NAMES } from '../contracts/index.js';
24
+ import { createRootsProvider, installSafetyGate, sensitiveReadFiles } from '../safety/index.js';
25
+ import { createFsTools, createSearchTools, createToolPipeline, createToolRegistry } from '../tools/index.js';
26
+ import { wireSessionApproval } from './approval-wiring.js';
27
+ import { createTodoTool } from './todo.js';
28
+ /**
29
+ * 组装 open 域工具面 + 审批守门。**一次成型**:返回的 tools 快照经真三段
30
+ * 管道执行(schema → 守门 → 执行——04 §7 唯一合法路径),驱动侧零旁路。
31
+ */
32
+ export function assembleOpenTools(opts) {
33
+ const workspace = opts.workspace ?? (() => canonicalWorkspaceRoot());
34
+ const workspaceRoot = workspace();
35
+ // ① 工具族事件词接线(contracts TOOL_EVENT_NAMES 装配消费面)。一词两册
36
+ // 幂等跳过(03 §2.4 装配序律——装载批预注册主表镜像在前,已注册词共享
37
+ // 登记非撞名覆盖;未注册词自举注册保单测独立装配)。装配哨兵词同走幂等
38
+ // 跳过(批 19c-1 修正:「同 dispatch 二次装配 = bug」前提随多会话装配废止
39
+ // ——in-process 子代理真工厂首例〔同栈父子两会话各装配一次〕;「同一会话
40
+ // 重复装配」检测由 SessionManager records 幂等守卫承担——open 幂等回
41
+ // 活体驱动不二造)
42
+ opts.dispatch.registerEventNames(['conversation/open-tools-mounted', ...TOOL_EVENT_NAMES].filter((name) => !opts.dispatch.isRegistered(name)));
43
+ // ② 审批三件前两件(服务 + answerer + 审批对 durable 落账)
44
+ const approvalWiring = wireSessionApproval({
45
+ sessionId: opts.sessionId,
46
+ dispatch: opts.dispatch,
47
+ session: opts.session,
48
+ ...(opts.askApproval !== undefined ? { askApproval: opts.askApproval } : {}),
49
+ ...(opts.policy !== undefined ? { policy: opts.policy } : {}),
50
+ ...(opts.persistToolPolicy !== undefined ? { persistToolPolicy: opts.persistToolPolicy } : {}),
51
+ });
52
+ // ③ 守门安装(先装本行——waterfall 注册序即执行序,本行最先执法;
53
+ // sessionId 归属位 = 会话归属过滤——他会话〔in-process 子代理等〕的工具
54
+ // 调用本行让棒,防止同栈多会话装配时同一 toolCall 被多行各问一次审批)
55
+ const uninstallGate = installSafetyGate(opts.dispatch, {
56
+ approval: approvalWiring.approval,
57
+ sessionId: opts.sessionId,
58
+ workspace: workspaceRoot,
59
+ mode: opts.mode,
60
+ dataDir: opts.dataDir,
61
+ ...(opts.toolPolicy !== undefined ? { toolPolicy: opts.toolPolicy } : {}),
62
+ ...(opts.entries !== undefined ? { entries: opts.entries } : {}),
63
+ });
64
+ // ④ 管道 + 注册表(gate/decision durable 落账——守门不可绕不变式的载体;
65
+ // sensitiveValues 透传 = 出口治理③ 值基腿接线,管道链尾消毒步消费)
66
+ const pipeline = createToolPipeline(opts.dispatch, {
67
+ onGateDecision: (record) => opts.session.append('gate/decision', record),
68
+ ...(opts.sensitiveValues !== undefined ? { sensitiveValues: opts.sensitiveValues } : {}),
69
+ });
70
+ const registry = createToolRegistry(opts.dispatch, { pipeline });
71
+ // ⑤ 工具族装配(fs fence 数据源与守门行同档位单源——createRootsProvider;
72
+ // fs/search 两族读侧 carve-out 同注入位——sensitiveReadFiles 单源派生,与
73
+ // 沙箱 profile 读 deny 行同数据〔2026-09-08 P0① 两腿同源〕)
74
+ const fsTools = createFsTools({
75
+ workspace,
76
+ writableRoots: createRootsProvider({ workspace: workspaceRoot, mode: opts.mode }),
77
+ protectedReadFiles: () => sensitiveReadFiles(opts.dataDir),
78
+ });
79
+ const searchTools = createSearchTools({
80
+ workspace,
81
+ protectedReadFiles: () => sensitiveReadFiles(opts.dataDir),
82
+ });
83
+ // exec 服务面诚实缺席(02 §4.1 #16:tryGet——exec 禁用 = bash 静默缺席);
84
+ // 在场则经会话装配期工厂求值 bash 工具(批 19a 定形:档位/审批/工作区
85
+ // 会话 deps 注入——装载期固定构造会丢会话面)
86
+ const execService = opts.scope.tryGet('exec');
87
+ const bashTool = execService !== undefined
88
+ ? execService.createBashTool({
89
+ workspaceRoot: workspace,
90
+ currentMode: opts.mode,
91
+ // 升权审批面绑本会话审批服务(结构窄面 {ask}——ApprovalService 满足)
92
+ approval: { ask: (req) => approvalWiring.approval.ask(req) },
93
+ })
94
+ : undefined;
95
+ // todo 工具:goal 换装注入位胜出(03 §10.5),缺省本域内置件
96
+ const todoTool = opts.todoTool ?? createTodoTool((data) => opts.session.append('todo/write', data));
97
+ const definitions = [
98
+ ...fsTools.tools,
99
+ ...searchTools.tools,
100
+ ...(bashTool !== undefined ? [bashTool] : []),
101
+ todoTool,
102
+ // 装载工具重放(批 19a 消费腿:boot 全局层定义经驱动层注册走真三段
103
+ // 管道——04 §7 插件工具同管线执法;每会话重放一次,快照在装配时点取)
104
+ ...(opts.extraTools !== undefined ? [...opts.extraTools()] : []),
105
+ ];
106
+ // 驱动层注册({driver: sessionId}——per-session 工具面;批 12 前插件的
107
+ // beforeToolCall 钩子同 dispatch 挂后续守门位);owner 缺省盖章
108
+ // 'core:host'(03 §2.3 尾注族谱——T9 案一批 t-1):本注册点是宿主装配
109
+ // 工具的唯一入口,未带 owner 的定义即宿主直构件(fs/search/bash/todo +
110
+ // obs/control extraTools 族);extraTools 重放腿携带的插件定义已被受理壳
111
+ // 铸得插件 id owner——`??` 缺省式不覆盖,插件归因原样存活到会话层
112
+ const disposers = definitions.map((definition) => registry.register({ ...definition, owner: definition.owner ?? 'core:host' }, { driver: opts.sessionId }));
113
+ return {
114
+ tools: registry.agentToolsFor(opts.sessionId),
115
+ registry,
116
+ approval: approvalWiring.approval,
117
+ settlePending: approvalWiring.settlePending,
118
+ dispose() {
119
+ // LIFO 拆解:先摘工具注册(含执行面)→ 守门 → answerer
120
+ for (const dispose of disposers.reverse())
121
+ dispose();
122
+ uninstallGate();
123
+ approvalWiring.dispose();
124
+ },
125
+ };
126
+ }
@@ -0,0 +1,132 @@
1
+ /** StopReason 闭集(成员校验用——contracts 联合的运行时镜像) */
2
+ const STOP_REASONS = new Set([
3
+ 'pending',
4
+ 'stop',
5
+ 'length',
6
+ 'toolUse',
7
+ 'error',
8
+ 'aborted',
9
+ 'deferred',
10
+ ]);
11
+ /** 零用量兜底形(usage 未知/损坏时——0 值不冒充计量,底账在 llm/usage 事件) */
12
+ const ZERO_USAGE = {
13
+ input: 0,
14
+ output: 0,
15
+ cacheRead: 0,
16
+ cacheWrite: 0,
17
+ totalTokens: 0,
18
+ };
19
+ /**
20
+ * usage 归一(unknown → Usage 必填形):数值字段 finite 才取,任一主字段
21
+ * 缺损即整笔退零用量(半拼凑的 usage 比零用量更有害——计量面要么完整
22
+ * 要么明示没有)。导出面:模型可见总拍(model-visible.ts)两侧同归一复用
23
+ * ——对拍等价判据以本归一为准(undefined ≡ 零用量兜底),单一归一源。
24
+ */
25
+ export function normalizeUsage(usage) {
26
+ if (usage === null || typeof usage !== 'object')
27
+ return ZERO_USAGE;
28
+ const u = usage;
29
+ const num = (key) => {
30
+ const v = u[key];
31
+ return typeof v === 'number' && Number.isFinite(v) ? v : undefined;
32
+ };
33
+ const input = num('input');
34
+ const output = num('output');
35
+ const cacheRead = num('cacheRead');
36
+ const cacheWrite = num('cacheWrite');
37
+ if (input === undefined || output === undefined || cacheRead === undefined || cacheWrite === undefined) {
38
+ return ZERO_USAGE;
39
+ }
40
+ return {
41
+ input,
42
+ output,
43
+ cacheRead,
44
+ cacheWrite,
45
+ ...(num('cacheWrite1h') !== undefined ? { cacheWrite1h: num('cacheWrite1h') } : {}),
46
+ ...(num('reasoning') !== undefined ? { reasoning: num('reasoning') } : {}),
47
+ totalTokens: num('totalTokens') ?? input + output + cacheRead + cacheWrite,
48
+ };
49
+ }
50
+ /** stopReason 闭集校验:undefined(历史/残缺)→ 'stop' 最小偏见;非成员 → 'error' 保守 */
51
+ function normalizeStopReason(reason) {
52
+ if (reason === undefined)
53
+ return 'stop';
54
+ return STOP_REASONS.has(reason) ? reason : 'error';
55
+ }
56
+ /** durable 原始参数串 → 已解析对象:解析失败兜底空对象(损坏不炸重播种) */
57
+ function parseArguments(raw) {
58
+ try {
59
+ const parsed = JSON.parse(raw);
60
+ if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
61
+ return parsed;
62
+ }
63
+ }
64
+ catch {
65
+ // 落空兜底——真源串在 durable 事件里永不丢
66
+ }
67
+ return {};
68
+ }
69
+ /** 投影 toolCall 块 → contracts ToolCallBlock(id/name 字段名对齐) */
70
+ function toToolCallBlock(call) {
71
+ return {
72
+ type: 'toolCall',
73
+ id: call.toolCallId,
74
+ name: call.toolName,
75
+ arguments: parseArguments(call.arguments),
76
+ };
77
+ }
78
+ /**
79
+ * 重播种:投影消息序列 → 标准 Message 数组(timeline 活数组种子)。
80
+ * 纯函数(timeOf 词典注入保确定性);toolResult 侧 arguments 丢弃
81
+ * (请求面不需要——assistant 内联块已带)。
82
+ * @param projection 投影(SessionLog.projection() 或 deriveMessages 产物——两路同源)
83
+ * @param timeOf 锚事件 seq → 毫秒时间戳解析器(确定性时间源——不用墙钟)
84
+ */
85
+ export function reseedTimeline(projection, timeOf) {
86
+ const messages = [];
87
+ for (const projected of projection) {
88
+ switch (projected.type) {
89
+ case 'user': {
90
+ const user = {
91
+ role: 'user',
92
+ // content 双形直通(string | 块数组——session ContentBlock 与 contracts
93
+ // 块结构对齐,derive 头注同裁;只读→可写经结构断言收口)
94
+ content: projected.content,
95
+ timestamp: timeOf(projected.seq),
96
+ // source 闭集由写者闭环保证(durable 原值即 UserMessage.source 落账回读)
97
+ ...(projected.source !== undefined ? { source: projected.source } : {}),
98
+ };
99
+ messages.push(user);
100
+ break;
101
+ }
102
+ case 'assistant': {
103
+ const assistant = {
104
+ role: 'assistant',
105
+ // toolCalls 装回尾部(投影序 = 尾部序——交错序在 durable 分立事件下无账)
106
+ content: [...projected.content, ...projected.toolCalls.map(toToolCallBlock)],
107
+ usage: normalizeUsage(projected.usage),
108
+ stopReason: normalizeStopReason(projected.stopReason),
109
+ timestamp: timeOf(projected.seq),
110
+ ...(projected.errorMessage !== undefined ? { errorMessage: projected.errorMessage } : {}),
111
+ };
112
+ messages.push(assistant);
113
+ break;
114
+ }
115
+ case 'toolResult': {
116
+ messages.push({
117
+ role: 'toolResult',
118
+ toolCallId: projected.toolCallId,
119
+ toolName: projected.toolName,
120
+ // string 输出包裹文本块(请求面 content 恒块数组);块数组直通
121
+ content: typeof projected.output === 'string'
122
+ ? [{ type: 'text', text: projected.output }]
123
+ : projected.output,
124
+ isError: projected.isError,
125
+ timestamp: timeOf(projected.seq),
126
+ });
127
+ break;
128
+ }
129
+ }
130
+ }
131
+ return messages;
132
+ }
@@ -0,0 +1,215 @@
1
+ import { forkPrefix, isSeededPrefix, recoverClosers } from '../session/index.js';
2
+ /** 会话编排钩子词汇(03 主表在册——本面消费的 waterfall 事件名单源) */
3
+ export const SESSION_HOOK_NAMES = ['session_before_fork'];
4
+ /**
5
+ * 多会话管理器(进程级单例——Persistence 同生命周期;02 §2.3 多会话单焦点:
6
+ * 并存多枚驱动、焦点唯 caller 视角,本件不持焦点态)。
7
+ */
8
+ export class SessionManager {
9
+ persistence;
10
+ dispatch;
11
+ createDriver;
12
+ /** 单会话收口观察位(构造面注入——缺省无观察) */
13
+ onRetired;
14
+ /** 已开会话登记(sessionId → 驱动 + 血缘形态——幂等 open 的判据面) */
15
+ records = new Map();
16
+ constructor(options) {
17
+ this.persistence = options.persistence;
18
+ this.dispatch = options.dispatch;
19
+ this.createDriver = options.createDriver;
20
+ this.onRetired = options.onRetired;
21
+ // 钩子词汇接线(一词两册幂等跳过——03 §2.4 装配序律:装载批预注册主表
22
+ // 镜像在前,session_before_fork 已注册即共享登记;未注册自举保独立装配);
23
+ // 重复建管理器检测改经装配哨兵(永不 emit 的占位词——二次装配撞哨兵红)
24
+ this.dispatch.registerEventNames([
25
+ 'conversation/session-manager-mounted',
26
+ ...SESSION_HOOK_NAMES.filter((name) => !this.dispatch.isRegistered(name)),
27
+ ]);
28
+ }
29
+ /** 会话列表(「按 cwd 取最新」选取面透传) */
30
+ list(options = {}) {
31
+ return this.persistence.listSessions(options);
32
+ }
33
+ /** 新开会话(origin 缺省普通对话;model/systemPrompt/shapeTools/askApproval/extraTools 为本会话装配覆盖——纯内存载体,批 19c-1 子代理通道同 model 律) */
34
+ create(init = {}) {
35
+ const origin = init.origin ?? 'conversation';
36
+ const log = this.persistence.createSession({
37
+ origin,
38
+ ...(init.workspaceRoot !== undefined ? { workspaceRoot: init.workspaceRoot } : {}),
39
+ ...(init.title !== undefined ? { title: init.title } : {}),
40
+ });
41
+ return this.adopt(log, origin, false, init);
42
+ }
43
+ /**
44
+ * 打开(resume)既有会话:幂等——已 open 直接回活体驱动(单焦点,不造第二
45
+ * 附着)。未 open 走 loadSession → closer 合成(崩溃收形——合成物复用最后
46
+ * 真实 time 可审计辨)→ 驱动构造(resumed 形)。
47
+ * @throws 会话不存在(fail-loud——读未落库 id 属调用方 bug)
48
+ */
49
+ open(sessionId) {
50
+ const existing = this.records.get(sessionId);
51
+ if (existing !== undefined) {
52
+ return { sessionId, driver: existing.driver, origin: existing.origin };
53
+ }
54
+ const loaded = this.persistence.loadSession(sessionId);
55
+ // closer 合成(05 §4——孤儿 tool/未闭合 turn 等补形;全日志扫描不设窗口)
56
+ for (const draft of recoverClosers(loaded.log.events())) {
57
+ loaded.log.appendSynthetic(draft);
58
+ }
59
+ return this.adopt(loaded.log, loaded.row.origin, true);
60
+ }
61
+ /**
62
+ * 分叉(05 §5.0 边界快照):前缀种子 + 复制。边界缺省 lastClosedBoundary
63
+ * (不在进行中 turn 中间切);内存种子组装——end-seed 只随种子走(time 复用
64
+ * 前缀尾事件时间,§4 合成确定性同律),源日志零污染。session_before_fork
65
+ * 钩子可否决(联合回执)。源会话已 open 取活体事件流(最新鲜)。
66
+ * @throws 源会话不存在 / upToSeq 越界(fail-loud 调用方 bug)
67
+ */
68
+ async fork(sourceSessionId, options = {}) {
69
+ // —— 事实源选择:活体优先(双事实源纪律——未 open 才 loadSession)——
70
+ const live = this.records.get(sourceSessionId);
71
+ let sourceLog;
72
+ let workspaceRoot;
73
+ if (live !== undefined) {
74
+ sourceLog = live.driver.session;
75
+ // 活体驱动不携带行信息——workspaceRoot 经公开列表面反查(不为内部
76
+ // 取值开新 persistence 读口)
77
+ workspaceRoot = this.persistence.listSessions().find((row) => row.id === sourceSessionId)?.workspaceRoot;
78
+ }
79
+ else {
80
+ const loaded = this.persistence.loadSession(sourceSessionId);
81
+ sourceLog = loaded.log;
82
+ workspaceRoot = loaded.row.workspaceRoot;
83
+ }
84
+ const sourceEvents = sourceLog.events();
85
+ // —— 边界解析 + 合法性(缺省最后闭合边界;显式值须落在日志内)——
86
+ const boundary = options.upToSeq ?? sourceLog.lastClosedBoundary();
87
+ if (options.upToSeq !== undefined && (options.upToSeq < -1 || options.upToSeq >= sourceEvents.length)) {
88
+ throw new Error(`fork 边界越界:upToSeq=${options.upToSeq} 日志长 ${sourceEvents.length}(调用方 bug)`);
89
+ }
90
+ // —— 钩子(waterfall 可否决——只认 veto 位;空链直通)——
91
+ const hookInput = { sourceSessionId, upToSeq: boundary };
92
+ const hookOutput = await this.dispatch.waterfall('session_before_fork', hookInput);
93
+ if (hookOutput.veto !== undefined) {
94
+ return { status: 'vetoed', reason: hookOutput.veto.reason };
95
+ }
96
+ // —— 种子组装:前缀拷贝 + end-seed 字面尾事件(data 空对象——§1.1)——
97
+ const seed = forkPrefix(sourceEvents, boundary);
98
+ seed.push({
99
+ type: 'session/end-seed',
100
+ seq: seed.length,
101
+ time: seed.length > 0 ? seed[seed.length - 1].time : 0,
102
+ data: {},
103
+ });
104
+ // 合法种子断言(防御位——forkPrefix + 字面尾事件在构造上恒真,触达即实现回归)
105
+ if (!isSeededPrefix(seed, seed.length, true)) {
106
+ throw new Error('fork 种子不合法(isSeededPrefix 断言失败——实现回归,不应触达)');
107
+ }
108
+ // —— 物理腿:同步落库(返回 id 必可读——不返回幻影 id)+ 驱动构造 ——
109
+ const forkedLog = this.persistence.createSeededSession(seed, {
110
+ origin: 'fork',
111
+ parentId: sourceSessionId,
112
+ ...(workspaceRoot !== undefined ? { workspaceRoot } : {}),
113
+ ...(options.title !== undefined ? { title: options.title } : {}),
114
+ });
115
+ const opened = this.adopt(forkedLog, 'fork', false);
116
+ return {
117
+ status: 'forked',
118
+ ...opened,
119
+ lineage: { parentId: sourceSessionId, seedLength: seed.length, origin: 'fork' },
120
+ firstAppendSeq: seed.length,
121
+ };
122
+ }
123
+ /**
124
+ * 会话内全文检索(05 §9 首发口径):flush 屏障先行(write-behind 在飞事件
125
+ * 不进 FTS 索引——检索前先落定)→ Store.searchSessionFts。
126
+ * @returns 命中事件 seq 升序列表(limit 缺省 50)
127
+ */
128
+ async search(sessionId, pattern, limit) {
129
+ await this.persistence.flush();
130
+ return this.persistence.searchSessionFts(sessionId, pattern, limit);
131
+ }
132
+ /** 已开判据(幂等 open 的外部可读面) */
133
+ isOpen(sessionId) {
134
+ return this.records.has(sessionId);
135
+ }
136
+ /**
137
+ * 目标会话在场判定(进程内在管 ∪ durable 行——e-4 操控轴幽灵守卫判据位,
138
+ * 03 §2.2 第十一面:三动词共同前置「目标 id 无对应行拒
139
+ * SESSION_TARGET_NOT_FOUND」)。零副作用:不 attach、不合成 closer、
140
+ * 不构造驱动。
141
+ */
142
+ exists(sessionId) {
143
+ return this.records.has(sessionId) || this.persistence.hasSession(sessionId);
144
+ }
145
+ /** 已开驱动读取(缺席 undefined——焦点编排归调用方) */
146
+ driverOf(sessionId) {
147
+ return this.records.get(sessionId)?.driver;
148
+ }
149
+ /** 任一已开驱动在飞(宿主 busy 判据读面——scheduler GateFacts agentBusy 源,批 20c) */
150
+ anyRunning() {
151
+ for (const { driver } of this.records.values()) {
152
+ if (driver.running)
153
+ return true;
154
+ }
155
+ return false;
156
+ }
157
+ /**
158
+ * 已开会话清单投影(e-2 观测腿——宿主 SessionView deps.liveSessions 消费位;
159
+ * 结构兼容 obs ObsLiveSessionsFace)。只含进程内在管(durable 未开不在场——
160
+ * 会话维清单语义 = 进程内活体会话,历史会话归持久层 list)。
161
+ */
162
+ listActive() {
163
+ return [...this.records.entries()].map(([sessionId, { origin }]) => ({ sessionId, origin }));
164
+ }
165
+ /** 全量拆解(进程收尾序):逐驱动 dismantle(打断在飞 run)+ 清登记 */
166
+ dispose() {
167
+ for (const { driver } of this.records.values()) {
168
+ driver.dismantle();
169
+ }
170
+ this.records.clear();
171
+ }
172
+ /**
173
+ * 单会话收口(run 生命周期收口——05 §7 retire 律 / 04 §12 第 5 律):驱动
174
+ * dismantle(终态停摆——打断在飞 run,dispose 同律)+ 摘活体登记。幂等——
175
+ * 不在册回 false 零副作用(dispose 后再 retire / 重复收口均无害)。
176
+ *
177
+ * 与 dispose 分立:彼系进程收尾序全量拆解,此系单条无头编排会话的终态
178
+ * 收口(tick 用户行 / subagent 子会话 / issue headless 会话三消费位——
179
+ * 19c-1 头注「活体登记回收挂账」的销账动词)。durable 面不受影响:日志
180
+ * 仍可查、open 可复续(幂等 open 对已摘行重造活体)。会话复用形(goal
181
+ * 绑定会话——广播唤醒依赖活体登记)不走此收口。
182
+ */
183
+ retire(sessionId) {
184
+ const record = this.records.get(sessionId);
185
+ if (record === undefined)
186
+ return false;
187
+ record.driver.dismantle();
188
+ this.records.delete(sessionId);
189
+ // 收口观察 seam:主流程(dismantle + 摘登记)已成——观察者异常吞隔离
190
+ // 不回卷收口序(观察位当前 = 冻结缓存摘除〔Map.delete 不抛〕,防御位
191
+ // 留给未来观察者)
192
+ try {
193
+ this.onRetired?.(sessionId);
194
+ }
195
+ catch {
196
+ /* 观察者异常不回卷收口序(发射序末位) */
197
+ }
198
+ return true;
199
+ }
200
+ /** 登记 + 驱动构造 + 入册(create/open/fork 共尾;装配覆盖位仅 create 腿携带——resume 不回放) */
201
+ adopt(log, origin, resumed, overrides = {}) {
202
+ const driver = this.createDriver({
203
+ session: log,
204
+ origin,
205
+ resumed,
206
+ ...(overrides.model !== undefined ? { model: overrides.model } : {}),
207
+ ...(overrides.systemPrompt !== undefined ? { systemPrompt: overrides.systemPrompt } : {}),
208
+ ...(overrides.shapeTools !== undefined ? { shapeTools: overrides.shapeTools } : {}),
209
+ ...(overrides.askApproval !== undefined ? { askApproval: overrides.askApproval } : {}),
210
+ ...(overrides.extraTools !== undefined ? { extraTools: overrides.extraTools } : {}),
211
+ });
212
+ this.records.set(log.sessionId, { driver, origin });
213
+ return { sessionId: log.sessionId, driver, origin };
214
+ }
215
+ }