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,654 @@
1
+ /**
2
+ * host/conversation-stack — 对话栈组合根(批 12e 装配序核心件;07 §1.1
3
+ * 表 #10 host 席「装配序」条的本体,channels/service.ts 头注装配序真源)。
4
+ *
5
+ * 一次成型装配五层:
6
+ * ① context 基座——Scope 根 + EventDispatch + ctx.agent 服务面(先于一切
7
+ * 驱动起跑——onRunSettled 订阅面的供给前提);
8
+ * ② llm 运行时——Models 宿主 + StreamFn(永不抛)+ complete 单发服务,
9
+ * 两出口共享同一 InFlightTracker(04 §3.6 同源计数);
10
+ * ③ compaction 服务——SummaryChannel 适配 LlmService(maxChars 预算入
11
+ * prompt 指令、输出防御性截断)+ 阈值触发器经 onRunSettled 接线
12
+ * (05 §2 装配挂账 + 判阈双源真值笔全兑现——run 终态从日志末条
13
+ * assistant 计量供笔〔lastUsageFactOf,末条不回溯〕,真 token 主判、
14
+ * 缺真值回落投影字符估算);
15
+ * ④ channels 通道核——fetchProjection/history 同源投影注入(07 §4.1 边表
16
+ * 执法:核零 session 依赖,数据源在此闭包注入);
17
+ * ⑤ SessionManager——DriverFactory 注入:open 域工具一次成型(assembleOpenTools
18
+ * + 审批桥)+ ConversationDriver 装配注入族全接线 + 活体事件信封汇入
19
+ * channels.emit(per-run sink 的归汇处)。
20
+ *
21
+ * 装配循环依赖解法:channels 的投影源要驱动登记、驱动的审批 ask 要 channels
22
+ * 队列——channels 创建收进本件内部(组合根自持),TUI 入口只 addBackend。
23
+ *
24
+ * memory 形降级(05 §6.6/07 §5):runtime.dataDir === null 时 open 域工具整面
25
+ * 缺席(守门恒排除位必填真 dataDir)——纯对话 run,工具面类型可选的诚实降级。
26
+ */
27
+ import { join } from 'node:path';
28
+ import { canonicalWorkspaceRoot, EventDispatch, Scope } from '../context/index.js';
29
+ import { createChannels } from '../channels/index.js';
30
+ import { getMessageRoleDefinition, isStandardMessage } from '../contracts/index.js';
31
+ import { createCompactionService, createCompactionSlots, createCcrRetrieveTool, BEFORE_COMPACT_ATTRIB, } from '../compaction/index.js';
32
+ import { assembleOpenTools, ConversationDriver, createSessionsControl, createControlTools, CONTROL_CROSS_CAPABILITY, DEFAULT_RETRY_POLICY, ensureTodoRole, provideAgentService, reseedTimeline, SessionManager, } from '../conversation/index.js';
33
+ import { classifyError, createLlmRuntime, createLlmService, createStreamFn, InFlightTracker, resolveDefaultModelSpec, usageBucketsOf, } from '../llm/index.js';
34
+ import { matchToolPolicy } from '../safety/index.js';
35
+ import { deriveMessages } from '../session/index.js';
36
+ import { TOOL_POLICY_BASENAME } from './tool-policy-store.js';
37
+ import { createSessionTools, createSessionView, OBSERVE_CROSS_CAPABILITY } from '../obs/index.js';
38
+ import { adjudicateCapabilityDoor } from '../contracts/api.js';
39
+ /**
40
+ * 组装对话栈。副作用注册:两 closer(manager 拆解 → compaction 排空)按注册
41
+ * 序进运行时退出序(abort 之后、write-behind flush 之前)。
42
+ */
43
+ export function createConversationStack(options) {
44
+ const warn = options.warn ?? ((message) => process.stderr.write(`${message}\n`));
45
+ const scope = options.scope ?? Scope.createRoot();
46
+ const dispatch = options.dispatch ?? new EventDispatch();
47
+ const model = options.model ?? resolveDefaultModelSpec(options.env ?? process.env);
48
+ // lane 帽(04 §4 宿主级 run 并发帽——channels 消息语义批 m-2):全宿主
49
+ // 单例信号量,driver 装配位 seam 注入(acquireRunSlot——kick 同步试位/
50
+ // 排队段两面消费;steer/inject 腿不经闸)。容量解析序:显式覆盖位 > env > 缺省 16。
51
+ const runLane = createRunLaneGate(resolveRunLaneCapacity(options.maxConcurrentRuns, options.env ?? process.env));
52
+ // 当日后台预算限额(04 §5 env 旋钮——2026-09-13 复盘修复 #44 F3):
53
+ // undefined = 缺省 4M(缺省值单源在 llm 件——本层只透传覆盖位)
54
+ const backgroundBudgetTokens = resolveBackgroundBudgetTokens(options.env ?? process.env);
55
+ const sandboxMode = options.sandboxMode ?? (() => 'workspace-write');
56
+ const workspaceAnchor = options.workspace ?? (() => canonicalWorkspaceRoot());
57
+ // ① ctx.agent 服务面先于一切驱动起跑(onRunSettled 订阅供给前提)
58
+ const agentService = provideAgentService(scope);
59
+ // todo 回看角色幂等注册(进程级单表——convertToLlm 消费前置)
60
+ ensureTodoRole();
61
+ // ② llm 运行时:两出口共享同一 InFlightTracker(04 §3.6 同源计数名实相符);
62
+ // 钩子派发段只读面同注双入口(03 §3.4——LLM_CALL_IN_HOOK 前置查,ca-3)。
63
+ // 预算读面同位接线(04 §5——2026-09-13 复盘修复 #44:修前 backgroundSpentToday
64
+ // 缺省 () => 0 空转,canAfford/预警三档/reserve 线三消费面生产恒判未超)。
65
+ const llmRuntime = createLlmRuntime(options.providers !== undefined ? { providers: options.providers } : {});
66
+ const tracker = new InFlightTracker();
67
+ const streamFn = createStreamFn(llmRuntime, {}, tracker, options.hookDispatchGuard);
68
+ // 当日后台已耗读面:日键缓存 + 桥接增量(05 §1.1 口径——SUM(input+output)、
69
+ // background 道过滤)。单写者进程(单活跃机收口)内首读聚合后只随本进程
70
+ // 桥接落账增量推进;日翻转重聚合(昨账不跨日)。write-behind 未落盘窗内
71
+ // 最近一笔偏松——软闸门既有语义(04 §5 定形注②)。
72
+ let spentDayStart = -1;
73
+ let spentCached = 0;
74
+ const backgroundSpentToday = () => {
75
+ const dayStart = startOfTodayMs();
76
+ if (dayStart !== spentDayStart) {
77
+ spentDayStart = dayStart;
78
+ try {
79
+ spentCached = aggregateBackgroundSpentToday(options.runtime.persistence.store, dayStart);
80
+ }
81
+ catch (err) {
82
+ // 读面失败 fail-open + warn:预算是软闸门,db 抖动不应反噬请求路(欠账
83
+ // 随下次日键重试/进程重启收口——丢弃的只是当次聚合精度)
84
+ warn(`当日后台已耗聚合失败(fail-open 归既知值 ${spentCached}):${err instanceof Error ? err.message : String(err)}`);
85
+ }
86
+ }
87
+ return spentCached;
88
+ };
89
+ const llm = createLlmService({
90
+ runtime: llmRuntime,
91
+ tracker,
92
+ defaultModel: () => model,
93
+ ...(options.hookDispatchGuard !== undefined ? { hookDispatch: options.hookDispatchGuard } : {}),
94
+ backgroundSpentToday,
95
+ ...(backgroundBudgetTokens !== undefined ? { backgroundBudgetTokens } : {}),
96
+ });
97
+ /**
98
+ * usage 桥接落账(04 §5 记账桥接单点——2026-09-13 复盘修复 #41/#44 定形注①):
99
+ * settled 回执窗扫 (seqFromLaunch, settle] 的 assistant/message 逐条转抄
100
+ * llm/usage(真源仍是 assistant 落账位——桥接投影零改写;05 §1.1 表注)。
101
+ * callId `run:<sid>:<seq>`(与 run CLI 旧桥接同形平移零迁移);priority 随
102
+ * run 级 backgroundLane(「前台花销照入账」就此收口——TUI/webui/issue/SDK
103
+ * /run CLI 五入口统一)。run-entry --background 块与 scheduler-tick
104
+ * recordBackgroundUsage 两处旧扫已退役(同窗同键防双计)。
105
+ */
106
+ const bridgeUsageLedger = (log, modelSpec, receipt) => {
107
+ for (const event of log.events()) {
108
+ if (event.seq <= receipt.seqFromLaunch || event.type !== 'assistant/message')
109
+ continue;
110
+ const usage = event.data.usage;
111
+ if (usage === undefined)
112
+ continue; // 无计量不造零账
113
+ log.append('llm/usage', {
114
+ callId: `run:${receipt.sessionId}:${event.seq}`,
115
+ model: modelSpec,
116
+ usage: usageBucketsOf(usage),
117
+ priority: receipt.backgroundLane ? 'background' : 'foreground',
118
+ });
119
+ // 当日缓存增量(仅后台道进闸门口径;先经读面确保日键已初始化——
120
+ // 未初始化时磁盘聚合随后自会收编本笔,双计不生)
121
+ if (receipt.backgroundLane) {
122
+ void backgroundSpentToday();
123
+ if (spentDayStart === startOfTodayMs())
124
+ spentCached += usage.input + usage.output;
125
+ }
126
+ }
127
+ };
128
+ // ③ compaction:SummaryChannel 适配(maxChars 由 prompt 指令承载——complete
129
+ // 单发面无 maxTokens 参数;输出防御性截断兜底)。阈值触发器接线:run 终态
130
+ // 订阅 → handleRunSettled(真 token 笔从日志末条 assistant 计量供笔——见
131
+ // lastUsageFactOf;注入位在则为测试替身/装配覆盖)。
132
+ // U4-3 三 seam 容器位:席位容器(getConfig/getProvider 晚绑定——plugin-boot
133
+ // fork 绑定装载面)+ 接管缝派发包装(waterfall + 归因箱种子/读末位)。
134
+ // 注:options.compaction 注入覆盖时席位容器仍外露(绑定面照常可达)——注入
135
+ // 替身不走 seam,容器位对其无效果(测试覆盖语义)。
136
+ const compactionSlots = createCompactionSlots();
137
+ const dispatchBeforeCompact = async (input) => {
138
+ // 零监听器直通(boot 未跑/词未注册形——waterfall 面词缺席即拒,守卫免炸)
139
+ if (!dispatch.isRegistered('session_before_compact'))
140
+ return { value: input };
141
+ // 归因箱种子:symbol 键随值链 spread 传播(改写必新建对象律)——plugin-context
142
+ // 钩子包装层逐跳记名(mark)/铸造(forge),出口读末位改写者
143
+ const box = {};
144
+ const seeded = Object.assign({}, input, { [BEFORE_COMPACT_ATTRIB]: box });
145
+ const value = await dispatch.waterfall('session_before_compact', seeded);
146
+ return box.lastAdjustedBy === undefined ? { value } : { value, lastAdjustedBy: box.lastAdjustedBy };
147
+ };
148
+ const compaction = options.compaction ??
149
+ createCompactionService({
150
+ channel: {
151
+ complete: async ({ prompt, maxChars }) => {
152
+ const result = await llm.complete({
153
+ messages: [{ role: 'user', content: prompt, timestamp: Date.now() }],
154
+ priority: 'foreground',
155
+ });
156
+ // 文本块拼接 + 预算截断(防御位——prompt 指令是主预算通道)
157
+ const text = result.message.content
158
+ .filter((block) => block.type === 'text')
159
+ .map((block) => block.text)
160
+ .join('');
161
+ return { text: text.length > maxChars ? text.slice(0, maxChars) : text };
162
+ },
163
+ },
164
+ getConfig: compactionSlots.getConfig,
165
+ getProvider: compactionSlots.getProvider,
166
+ onBeforeCompact: dispatchBeforeCompact,
167
+ warn,
168
+ });
169
+ // ④ channels 通道核(投影源在⑤驱动登记之后才被调用——闭包前向引用安全)
170
+ const channels = createChannels({
171
+ fetchProjection: (sessionId) => Promise.resolve(projectionOf(sessionId)),
172
+ history: (sessionId) => Promise.resolve(projectionOf(sessionId)),
173
+ // /memory 注册位(06 §7——mm 批):库座在位即注册(persistence 在场 ⇒
174
+ // memory 件将装载;件缺席形由命令 handler 的 notify 降级提示诚实兜底)
175
+ memory: true,
176
+ });
177
+ // ④½ 会话维视图(e-2 观测腿——SessionView 纯派生读面):数据三窄面全结构
178
+ // 兼容直传(store.queryEvents/getSessionRow + manager.listActive 投影——前向
179
+ // 闭包同 channels 律,首会话起跑时 manager 必已建);消费位两会话工具族
180
+ // (durable 形 per-session 闭包)+ 装配根 sessionLineage(订阅 tree 档过滤)
181
+ const sessionStore = options.runtime.persistence.store;
182
+ const sessionView = createSessionView({
183
+ events: sessionStore,
184
+ sessions: sessionStore,
185
+ liveSessions: { listActive: () => manager.listActive() },
186
+ workspaceRoot: workspaceAnchor,
187
+ });
188
+ // ④' 出口治理③ 值基腿活值 provider(04 §7 执行段 2026-09-08 落码定形⑤):
189
+ // credentials 库 live 读——管道链尾消毒步每次调用现取(工具执行期间新入库
190
+ // 凭证同受覆盖)。单行解密失败跳过不连坐(其余行照常参与——行级隔离);
191
+ // 库整体故障抛给管道侧 catch 降级纯模式腿(降级诚实)。长度 ≥8 过滤在
192
+ // redactKnownSecretValues 内执法(短值误伤普通文本的灾难面控制)。
193
+ const sensitiveValues = () => {
194
+ const values = [];
195
+ for (const row of options.runtime.persistence.store.listCredentialProviders()) {
196
+ try {
197
+ const entry = options.runtime.persistence.store.getCredential(row.namespace, row.provider);
198
+ if (entry !== undefined && entry.apiKey.length > 0)
199
+ values.push(entry.apiKey);
200
+ }
201
+ catch {
202
+ /* 单行坏(解密不匹配等)跳过——消毒面按行降级,不炸 provider */
203
+ }
204
+ }
205
+ return values;
206
+ };
207
+ // ⑤ SessionManager:DriverFactory 装配注入族全接线(model = per-fresh-session
208
+ // 覆盖 ?? 栈缺省——create init.model 透传位,触发器 starter 载体,C 批 C-3;
209
+ // systemPrompt/shapeTools/askApproval = 批 19c-1 per-session 装配覆盖通道——
210
+ // in-process 子代理工厂消费位:系统提示覆盖、派生面白名单整形、审批升父面;
211
+ // extraTools = 会话维追加工具面(成熟度缺口 #5——issue 起会腿消费位:件注册
212
+ // 的只读工具面经 open 域管道注册位并入,真三段管道零旁路)
213
+ const createDriver = ({ session, model: sessionModel, systemPrompt, shapeTools, askApproval, extraTools, }) => {
214
+ const sessionId = session.sessionId;
215
+ // 审批桥:driver 与 open 域工具共用同一 per-session ask 面(07 §4.3 提问队列);
216
+ // 工厂注入覆盖在场时胜出(委派边界①——子会话审批落父会话呈现面,04 §10)
217
+ const askFace = askApproval ??
218
+ ((request, opts) => channels.askApproval(sessionId, request, opts));
219
+ let tools;
220
+ let settleApprovals;
221
+ // 工具归因取值器(T9 案一批 t-1——tool/call 载荷 owner 位):assembly
222
+ // 在场时从会话注册表构造(listFor 两层并集 live 查询——覆盖装配后动态
223
+ // 注册);memory 形(无工具面)保持 undefined——纯对话 run 无 tool/call
224
+ let resolveToolOwner;
225
+ if (options.runtime.dataDir !== null) {
226
+ // goal 段换装(批 19c-3——03 §10.5):goal 件在场 + 锚注入在位 →
227
+ // per-session 扩展 todo 工具替换内置件(openTools todoTool 注入位——
228
+ // 词面独立律 conversation 域零 goal 知识;未装载/锚缺席 = 内置件)
229
+ const goalFace = scope.tryGet('goal');
230
+ const goalTodo = goalFace !== undefined && options.goalScopeFor !== undefined
231
+ ? goalFace.todoFactory({
232
+ append: (data) => session.append('todo/write', data),
233
+ getScope: () => options.goalScopeFor?.(sessionId) ?? null,
234
+ })
235
+ : undefined;
236
+ // 会话维工具族(e-2 观测腿——03 §10.8 恒挂载四件;per-session 闭包
237
+ // callerSessionId 注入〔todoTool 换装 seam 同构〕;跨树门检输入经
238
+ // observeCross seam——装配已接 doors 段活体真源 + capability/used
239
+ // 审计〔assembly;缺席形 ?? 空集 = 测试/未装配兜底〕)
240
+ const sessionTools = createSessionTools({
241
+ view: sessionView,
242
+ callerSessionId: sessionId,
243
+ getOpens: () => options.observeCross?.getOpens() ?? new Set(),
244
+ ...(options.observeCross?.onCapabilityUsed !== undefined
245
+ ? { onCapabilityUsed: options.observeCross.onCapabilityUsed }
246
+ : {}),
247
+ // e-3 环境自感窄面(03 §10.8 session_status 并入注):工具清单 =
248
+ // 本会话整形后面(shapeTools 白名单后的可见面——子代理派生面自省
249
+ // 即其子实面,04 §10「孙代委派以子实面为基准」同律;lazy 读,execute
250
+ // 时点装配已完成);门态 = 与门检同吃 caller 感知合成源(开门制扩展批
251
+ // ——本会话即模型道 caller:观测门 doors 段单独、操控门 getOpensFor
252
+ // session caller 形;reason 与执行时拒绝 message 同源——先查后用)
253
+ env: {
254
+ listTools: () => (tools ?? []).map((entry) => ({ name: entry.name })),
255
+ doorStates: () => {
256
+ const opens = options.observeCross?.getOpens() ?? new Set();
257
+ const verdict = adjudicateCapabilityDoor(opens, OBSERVE_CROSS_CAPABILITY);
258
+ const controlVerdict = adjudicateCapabilityDoor(options.controlCross?.getOpensFor({ kind: 'session', sessionId }) ?? new Set(), CONTROL_CROSS_CAPABILITY);
259
+ return [
260
+ {
261
+ capability: OBSERVE_CROSS_CAPABILITY,
262
+ open: verdict.ok,
263
+ ...(verdict.ok ? {} : { reason: verdict.message }),
264
+ scope: '跨树会话枚举与读取(session_list/session_read/session_trace 跨树目标)',
265
+ },
266
+ {
267
+ capability: CONTROL_CROSS_CAPABILITY,
268
+ open: controlVerdict.ok,
269
+ ...(controlVerdict.ok ? {} : { reason: controlVerdict.message }),
270
+ scope: '跨会话操控三动词(session_send/session_interrupt/session_withdraw——全域同门无树内豁免)',
271
+ },
272
+ ];
273
+ },
274
+ // ap-3 第四段数据源(03 §10.8 ap-3 定形注):装配期快照 + 整名族
275
+ // dryRun 闭包(matchToolPolicy 同源——obs 零 safety 依赖的桥位);
276
+ // 策略表功能缺席(options.toolPolicy undefined)= getter 返 undefined
277
+ // ——obs 段不呈现。dryRun 只喂整名族条目(write/edit/bash 条目命中
278
+ // 依赖调用实参,整名干跑恒 miss——闭包内过滤防假报)
279
+ ...(options.toolPolicy !== undefined
280
+ ? {
281
+ toolPolicy: () => {
282
+ // memory 形降级防御:dataDir 缺席(正常装配下不共现——
283
+ // assembly 只在 dataDir 非空时穿 toolPolicy)= 段不呈现
284
+ if (options.runtime.dataDir === null)
285
+ return undefined;
286
+ const wholeName = options.toolPolicy.filter((e) => e.tool !== 'write' && e.tool !== 'edit' && e.tool !== 'bash');
287
+ return {
288
+ entries: options.toolPolicy.map((entry, index) => ({
289
+ index,
290
+ tool: entry.tool,
291
+ ...(entry.pattern !== undefined ? { pattern: entry.pattern } : {}),
292
+ ...(entry.effect !== undefined ? { effect: entry.effect } : {}),
293
+ decision: entry.decision,
294
+ ...(entry.reason !== undefined ? { reason: entry.reason } : {}),
295
+ ...(entry.expiresAt !== undefined ? { expiresAt: entry.expiresAt } : {}),
296
+ })),
297
+ path: join(options.runtime.dataDir, TOOL_POLICY_BASENAME),
298
+ dryRun: (tool, effect) => {
299
+ const hit = matchToolPolicy(wholeName, { tool, effect }, Date.now());
300
+ return hit === undefined ? undefined : { decision: hit.entry.decision, index: hit.index };
301
+ },
302
+ };
303
+ },
304
+ }
305
+ : {}),
306
+ },
307
+ });
308
+ const assembly = assembleOpenTools({
309
+ sessionId,
310
+ dispatch,
311
+ session,
312
+ scope,
313
+ mode: sandboxMode,
314
+ dataDir: options.runtime.dataDir,
315
+ workspace: workspaceAnchor,
316
+ askApproval: askFace,
317
+ ...(options.toolPolicy !== undefined ? { toolPolicy: options.toolPolicy } : {}),
318
+ ...(options.persistToolPolicy !== undefined ? { persistToolPolicy: options.persistToolPolicy } : {}),
319
+ // 审批策略档穿线(04 §9 两旋钮——装配根四层解析胜者;缺省不注入 =
320
+ // approval 服务内缺省 'ask')
321
+ ...(options.approvalPolicy !== undefined ? { policy: options.approvalPolicy } : {}),
322
+ // 会话维工具族并入扩展位(bootTools 同位——模型可见清单恒在律);
323
+ // 操控三件同位并入(e-4——恒挂载,门检在受理器内执法);
324
+ // ccr_retrieve 同位并入(05 §2.1 压缩可逆性——恒挂载:压缩归档原文
325
+ // 回取面,turn 1 起在场永不摘除;memory 形随工具整面缺席);
326
+ // extraTools 会话维追加位(缺口 #5——issue 工具面经管道注册并入,
327
+ // boot 全局层之后、会话族之前:宿主全局面 → 本会话编排面 → 观测族)
328
+ extraTools: () => [
329
+ ...(options.bootTools?.() ?? []),
330
+ ...(extraTools?.() ?? []),
331
+ ...sessionTools,
332
+ ...createControlTools({ callerSessionId: sessionId, control: sessionsControl }),
333
+ ...createCcrRetrieveTool({ events: () => session.events() }),
334
+ ],
335
+ ...(goalTodo !== undefined ? { todoTool: goalTodo } : {}),
336
+ sensitiveValues, // 出口消毒值基腿(栈级单闭包——多会话装配共享,live 读)
337
+ });
338
+ options.runtime.registerDisposer(assembly.dispose); // LIFO 拆解进运行时退出序
339
+ // 整形钩子(批 19c-1):装配产物进驱动前整形(语义归调用方——子代理
340
+ // 派生面执法;fullTools 快照即整形后面——孙代委派以子实面为基准)
341
+ tools = shapeTools !== undefined ? [...shapeTools(assembly.tools)] : assembly.tools;
342
+ // owner 取数闭包:装配产物 registry 即会话注册表——宿主直构件已盖
343
+ // 'core:host'、extraTools 重放的插件定义携插件 id owner,listFor 全量
344
+ // 可查(live 形——named provider 程序化注册等后续注册天然覆盖)
345
+ resolveToolOwner = (name) => assembly.registry.listFor(sessionId).find((def) => def.name === name)?.owner;
346
+ settleApprovals = assembly.settlePending;
347
+ } // memory 形:工具整面缺席——纯对话 run(件头注降级语义)
348
+ const driver = new ConversationDriver({
349
+ session,
350
+ scope,
351
+ dispatch,
352
+ streamFn,
353
+ convertToLlm: (message) => isStandardMessage(message) ? message : (getMessageRoleDefinition(message.role)?.toLlm?.(message) ?? null),
354
+ model: sessionModel ?? model,
355
+ ...(tools !== undefined ? { tools } : {}),
356
+ // tool/call 载荷 owner 位取数(T9 案一批 t-1——memory 形 undefined 不带)
357
+ ...(resolveToolOwner !== undefined ? { resolveToolOwner } : {}),
358
+ // lane 帽取位器(04 §4——channels 消息语义批 m-2):followUp 起跑前
359
+ // 取位、run 终态释放;排队不计在飞(冷读闸 M1 裁决)。栈级单 gate
360
+ // 全会话共享——「宿主级」并发数的真源。
361
+ acquireRunSlot: runLane,
362
+ ...(options.thinkingLevel !== undefined ? { thinkingLevel: options.thinkingLevel } : {}),
363
+ // per-session 覆盖 ?? 栈基线(open/resume 不携带——回落基线同 model 律)
364
+ ...((systemPrompt ?? options.systemPrompt) !== undefined
365
+ ? { systemPrompt: systemPrompt ?? options.systemPrompt }
366
+ : {}),
367
+ ...(options.pluginSections !== undefined ? { pluginSections: options.pluginSections } : {}),
368
+ // goal 段升格锚穿线(批 19c-3——驱动 fold 升格消费位 driver.ts)
369
+ ...(options.goalScopeFor !== undefined ? { goalScopeFor: options.goalScopeFor } : {}),
370
+ // goal 轮间沉淀 + 记账回执穿线(批 #99——sessionId 位在此落格绑定)
371
+ ...(options.goalDeposit !== undefined ? { goalDeposit: () => options.goalDeposit(sessionId) } : {}),
372
+ // 预算预警穿线(批 H——04 §5 软着陆层 + 修复批 run 车道透传):sessionId
373
+ // 落格绑定同 goalDeposit 形;backgroundLane = 驱动侧当前 run 声明位
374
+ ...(options.budgetAdvisory !== undefined
375
+ ? { budgetAdvisory: (backgroundLane) => options.budgetAdvisory(sessionId, backgroundLane) }
376
+ : {}),
377
+ // run 结算回执链(无条件装——2026-09-13 复盘修复 #41/#44 记账桥接单点):
378
+ // 桥接落账先于消费侧钩(usage 底账近源先行;goal recordTurn 等
379
+ // options.onRunSettled 消费者随后——批 #99 既有链不破)。session 直接
380
+ // 取闭包本尊(settle 时点即本驱动日志——不经 manager 回查)
381
+ onRunSettled: (receipt) => {
382
+ bridgeUsageLedger(session, sessionModel ?? model, receipt);
383
+ options.onRunSettled?.(sessionId, receipt);
384
+ },
385
+ classifyError,
386
+ compactForOverflow: (log) => compaction.compactForOverflow(log),
387
+ environmentDisclosure: options.runtime.disclosure,
388
+ askApproval: askFace,
389
+ ...(settleApprovals !== undefined ? { settleApprovals } : {}),
390
+ warn,
391
+ onEvent: (event) => channels.emit({ sessionId, event }),
392
+ retry: DEFAULT_RETRY_POLICY,
393
+ });
394
+ return driver;
395
+ };
396
+ const manager = new SessionManager({
397
+ persistence: options.runtime.persistence,
398
+ dispatch,
399
+ createDriver,
400
+ // 单会话收口观察穿线(发现 ⑯——缺席形不设位保持测试替身零观察)
401
+ ...(options.onSessionRetired !== undefined ? { onRetired: options.onSessionRetired } : {}),
402
+ });
403
+ // 操控受理器(e-4——03 §2.2 第十一面双面同源单源实现位):栈级单例——
404
+ // 工具族(模型道 per-session 闭包)与 plugin-boot fork 绑定(插件道)消费
405
+ // 同一实例。门检 caller 感知合成 + 审计经 controlCross seam(开门制扩展批
406
+ // ——装配根接 doors 段 ∪ 行 opens 双源,模型道 doors 段单独)
407
+ const sessionsControl = createSessionsControl({
408
+ manager,
409
+ getOpensFor: (caller) => options.controlCross?.getOpensFor(caller) ?? new Set(),
410
+ ...(options.controlCross?.onCapabilityUsed !== undefined
411
+ ? { onCapabilityUsed: options.controlCross.onCapabilityUsed }
412
+ : {}),
413
+ });
414
+ // 阈值触发器:run 终态 → 该会话日志入阈值判定(fire-and-forget)。判阈双源
415
+ // 的真 token 主判在此供笔(lastUsageFactOf——日志末条 assistant 计量);零计量
416
+ // 不携带 → 服务侧回落投影字符估算(estimate 兜底档)
417
+ agentService.onRunSettled((event) => {
418
+ const driver = manager.driverOf(event.sessionId);
419
+ if (driver !== undefined)
420
+ compaction.handleRunSettled({ log: driver.session, ...lastUsageFactOf(driver.session) });
421
+ });
422
+ /** 投影拉取:驱动活体优先(内存最新鲜),未开回库装载(双事实源纪律同律) */
423
+ async function projectionOf(sessionId) {
424
+ const log = manager.driverOf(sessionId)?.session ?? options.runtime.persistence.loadSession(sessionId).log;
425
+ const events = log.events();
426
+ return reseedTimeline(deriveMessages(events), (seq) => events[seq]?.time ?? 0);
427
+ }
428
+ // 退出序接线:closer 注册序即 drain 序——先拆驱动(打断在飞 run)再排空压缩链
429
+ options.runtime.registerCloser({ label: 'conversation-manager', fn: () => manager.dispose() });
430
+ options.runtime.registerCloser({
431
+ label: 'compaction-drain',
432
+ fn: () => compaction.drain(),
433
+ });
434
+ return {
435
+ manager,
436
+ channels,
437
+ llm,
438
+ llmRuntime,
439
+ scope,
440
+ dispatch,
441
+ model,
442
+ sessionView,
443
+ sessionsControl,
444
+ compactionSlots,
445
+ projectionOf,
446
+ driverOf: (sessionId) => manager.driverOf(sessionId),
447
+ submitText(sessionId, text, submitOptions) {
448
+ const driver = manager.driverOf(sessionId);
449
+ if (driver === undefined)
450
+ return undefined; // 未开会话——上层提交序不达(理论不达防御位)
451
+ const run = driver.submit(text, submitOptions);
452
+ // 回执面错误经 notify 回流呈现面(fire-and-forget 无未处理拒绝;await 方仍得真回执)
453
+ void run.catch((err) => {
454
+ channels.notify(sessionId, `提交失败:${err instanceof Error ? err.message : String(err)}`, {
455
+ level: 'error',
456
+ });
457
+ });
458
+ return run;
459
+ },
460
+ interrupt(sessionId) {
461
+ manager.driverOf(sessionId)?.abort();
462
+ },
463
+ openStartupSession(cwd) {
464
+ const workspaceRoot = canonicalWorkspaceRoot(cwd);
465
+ // 按 cwd 归一根取最新会话(会话表 workspace_root 选取键——07 §5 策略真源)
466
+ const [latest] = manager.list({ workspaceRoot, limit: 1 });
467
+ if (latest !== undefined) {
468
+ const opened = manager.open(latest.id);
469
+ return { ...opened, resumed: true, workspaceRoot };
470
+ }
471
+ const created = manager.create({ workspaceRoot });
472
+ return { ...created, resumed: false, workspaceRoot };
473
+ },
474
+ };
475
+ }
476
+ /**
477
+ * 末次真计量笔(05 §2.1 判阈双源「真 token 主判」的供笔侧):取日志**末条**
478
+ * assistant/message 事件的计量快照(provider 报数随落账原样在场——05 §1.1)。
479
+ *
480
+ * 取值律:
481
+ * - 只看末条不回溯——上一轮的 input 是上一形态的真值、不是本轮的,回溯即拿
482
+ * 旧值冒充新真值(真值可用时不猜,真值缺席就明说缺席);
483
+ * - 零值/坏形(中止早退、脚本零报形)= 无真值:返回不携带,调用方回落投影
484
+ * 字符估算(chars/4——estimate 兜底档语义在此保底而非在服务侧猜测);
485
+ * - contextWindow 不供(栈面只有模型 id 无目录查询)——分母归服务侧
486
+ * fallbackWindowTokens 缺省,与估算档同分母。
487
+ */
488
+ export function lastUsageFactOf(log) {
489
+ const events = log.events();
490
+ for (let i = events.length - 1; i >= 0; i--) {
491
+ const event = events[i];
492
+ if (event.type !== 'assistant/message')
493
+ continue;
494
+ const data = event.data;
495
+ const usage = typeof data === 'object' && data !== null ? data.usage : undefined;
496
+ if (typeof usage === 'object' &&
497
+ usage !== null &&
498
+ typeof usage.input === 'number' &&
499
+ usage.input > 0) {
500
+ // cache 两桶同笔带出(cache 经济批 RP4——basis 五件的数据源):与 input
501
+ // 同一事件同一读笔,缺桶/非数不猜(透传面收敛 number 判)
502
+ const record = usage;
503
+ return {
504
+ usage: {
505
+ input: record.input,
506
+ ...(typeof record.cacheRead === 'number' ? { cacheRead: record.cacheRead } : {}),
507
+ ...(typeof record.cacheWrite === 'number' ? { cacheWrite: record.cacheWrite } : {}),
508
+ },
509
+ };
510
+ }
511
+ return {}; // 末条已见而无可信计量——无真值不猜(不回溯)
512
+ }
513
+ return {}; // 无 assistant 事件(空 run/纯消费防御路径)
514
+ }
515
+ /**
516
+ * lane 帽缺省容量(04 §4——码面缺省参数非契约常数,观测证据可再裁;
517
+ * 对齐常见 LLM 供应商并发档量级)。
518
+ */
519
+ export const DEFAULT_RUN_LANE_CAPACITY = 16;
520
+ /**
521
+ * lane 帽容量解析(04 §4 宿主级 run 并发帽——channels 消息语义批 m-2):
522
+ * 解析序 = 显式覆盖位 > env `BERRY_AGENT_MAX_CONCURRENT_RUNS` > 缺省 16。
523
+ * 非正整数 fail-loud 拒(RangeError)——空帽/坏帽是死配置(queue capacity
524
+ * 同律)。
525
+ */
526
+ export function resolveRunLaneCapacity(override, env) {
527
+ const raw = override ?? env['BERRY_AGENT_MAX_CONCURRENT_RUNS'];
528
+ if (raw === undefined)
529
+ return DEFAULT_RUN_LANE_CAPACITY;
530
+ // 字串形全串 /^\d+$/ 判——parseInt 截停会把 '16x'/'16.5'/'0x10'/'+16'/' 16' 类
531
+ // 尾随垃圾静默放行成 16,与「空帽/坏帽是死配置」的 fail-loud 自述相悖
532
+ let value;
533
+ if (typeof raw === 'number') {
534
+ value = raw;
535
+ }
536
+ else if (/^\d+$/.test(raw)) {
537
+ value = Number.parseInt(raw, 10);
538
+ }
539
+ else {
540
+ value = Number.NaN;
541
+ }
542
+ if (!Number.isInteger(value) || value < 1) {
543
+ throw new RangeError(`lane 帽容量须为正整数,收到 ${String(raw)}——空帽/坏帽是死配置(BERRY_AGENT_MAX_CONCURRENT_RUNS / maxConcurrentRuns)`);
544
+ }
545
+ return value;
546
+ }
547
+ /** env 名(04 §5——2026-09-13 复盘修复 #44 F3;07 环境变量 BERRY_AGENT_* 前缀族) */
548
+ export const BACKGROUND_BUDGET_TOKENS_ENV = 'BERRY_AGENT_BACKGROUND_BUDGET_TOKENS';
549
+ /**
550
+ * 当日后台预算限额解析(04 §5 env 旋钮):env 缺席 = undefined(缺省 4M 由
551
+ * llm 件单源持有——本函数只产覆盖位)。坏形 fail-loud 启动当场红(与 lane
552
+ * 帽/静置窗两旋钮同律:坏预算值是死配置,静默回落缺省会吞掉用户显式
553
+ * 降额/提额意图)。零值合法(显式关池——canAfford 恒 false,停靠腿承接)。
554
+ */
555
+ export function resolveBackgroundBudgetTokens(env) {
556
+ const raw = env[BACKGROUND_BUDGET_TOKENS_ENV];
557
+ if (raw === undefined || raw === '')
558
+ return undefined;
559
+ // 字串形全串 /^\d+$/ 判(同 resolveRunLaneCapacity——parseInt 截停防)
560
+ if (!/^\d+$/.test(raw)) {
561
+ throw new RangeError(`当日后台预算限额须为非负整数字串,收到 "${raw}"——坏形是死配置(${BACKGROUND_BUDGET_TOKENS_ENV})`);
562
+ }
563
+ return Number.parseInt(raw, 10);
564
+ }
565
+ /** 当日零点毫秒(本地时区——「当日」语义随用户挂钟;04 §5 日池窗) */
566
+ export function startOfTodayMs(now = Date.now()) {
567
+ const d = new Date(now);
568
+ d.setHours(0, 0, 0, 0);
569
+ return d.getTime();
570
+ }
571
+ /**
572
+ * 当日后台已耗聚合(04 §5 #44 聚合读面——05 §1.1 口径单源):queryEvents
573
+ * 当日窗 llm/usage · `priority === 'background'` 过滤 · SUM(input+output)
574
+ * 主计费桶(cache 桶进观察面板不进闸门)。分页游标走满(页帽顶格 10000——
575
+ * 当日调用密度远不及帽,走满是完整性防御非热路径)。读失败上抛由调用方
576
+ * 定姿态(装配位 fail-open + warn——预算软闸门不反噬请求路)。
577
+ */
578
+ export function aggregateBackgroundSpentToday(store, sinceMs) {
579
+ let sum = 0;
580
+ let cursor = undefined;
581
+ do {
582
+ const page = store.queryEvents({
583
+ types: ['llm/usage'],
584
+ sinceMs,
585
+ limit: 10_000,
586
+ ...(cursor !== undefined ? { cursor } : {}),
587
+ });
588
+ for (const event of page.events) {
589
+ const data = event.data;
590
+ if (data.priority !== 'background')
591
+ continue; // 前台照入账不进闸门
592
+ sum += (data.usage?.input ?? 0) + (data.usage?.output ?? 0);
593
+ }
594
+ cursor = page.nextCursor ?? undefined;
595
+ } while (cursor !== undefined);
596
+ return sum;
597
+ }
598
+ /**
599
+ * 造宿主级 run 并发闸(04 §4——排队非拒收:帽满排队 FIFO、释放依序续跑;
600
+ * openclaw CommandLane 先例的宿主级对位)。lane 队列是内存态:退出 drain
601
+ * 不等待排队件(与 PendingMessageQueue 崩溃即丢同语义——04 §1)。释放器
602
+ * 幂等(双调安全——driver kick 的 finally 腿防御)。
603
+ */
604
+ export function createRunLaneGate(capacity) {
605
+ if (!Number.isInteger(capacity) || capacity < 1) {
606
+ throw new RangeError(`lane 帽容量须为正整数,收到 ${capacity}——空帽是死配置`);
607
+ }
608
+ let inFlight = 0;
609
+ /** FIFO 等位队列(帽满时的挂起取位——resolve 载释放器) */
610
+ const waiting = [];
611
+ /** 释放在飞位;有等位者则依 FIFO 直接移交(等位者即刻在飞——无缝续跑) */
612
+ const releaseSlot = () => {
613
+ inFlight -= 1;
614
+ const next = waiting.shift();
615
+ if (next === undefined)
616
+ return;
617
+ inFlight += 1;
618
+ next(makeRelease());
619
+ };
620
+ /** 造幂等释放器(每次取位独立一枚——双调只是无操作,不双扣在飞位) */
621
+ const makeRelease = () => {
622
+ let used = false;
623
+ return () => {
624
+ if (used)
625
+ return;
626
+ used = true;
627
+ releaseSlot();
628
+ };
629
+ };
630
+ return {
631
+ tryAcquire() {
632
+ if (inFlight >= capacity)
633
+ return undefined;
634
+ inFlight += 1;
635
+ return makeRelease();
636
+ },
637
+ acquire() {
638
+ return new Promise((resolve) => {
639
+ if (inFlight < capacity) {
640
+ inFlight += 1;
641
+ resolve(makeRelease());
642
+ return;
643
+ }
644
+ waiting.push(resolve);
645
+ });
646
+ },
647
+ get inFlight() {
648
+ return inFlight;
649
+ },
650
+ get queued() {
651
+ return waiting.length;
652
+ },
653
+ };
654
+ }