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,422 @@
1
+ import { DEFAULT_COMPACTION_CONFIG } from './types.js';
2
+ import { buildSummaryPrompt, evaluateThreshold, planFromRange, planSegment, previousSummaryText, SUMMARY_PREFIX, summaryBudgetFor, validateAdjustedRange, } from './policy.js';
3
+ import { ccrDirectoryOf, ccrHashOf, withCcrSection } from './ccr.js';
4
+ /** 空闲态逐出帽(在飞/排队态不逐——只逐空闲) */
5
+ const IDLE_STATES_CAP = 256;
6
+ /** 三振阈值(05 §2.1 回落三律第 3 律——同一 pluginId 进程级连续 3 次失败停用) */
7
+ const STRIKE_LIMIT = 3;
8
+ /** fallback 词错误摘要截断长度(防单条爆账——审计面够诊断即可) */
9
+ const FALLBACK_ERROR_MAX = 500;
10
+ /** 算法执行段超时标记(fallback stage='timeout' 判型用) */
11
+ class AlgoTimeoutError extends Error {
12
+ }
13
+ /** 插件路空文本产物标记(fallback stage='rejected' 判型用——同宿主通道失败律计入三振) */
14
+ class AlgoEmptyTextError extends Error {
15
+ }
16
+ /** fallback 词统一落账形(05 §1.1——回落三律第 3 律响亮记账;circuit 位末次记三振停用) */
17
+ function appendFallback(log, source, stage, error, circuit = false) {
18
+ log.append('compaction/fallback', {
19
+ source,
20
+ stage,
21
+ error: String(error).slice(0, FALLBACK_ERROR_MAX),
22
+ ...(circuit ? { circuit: true } : {}),
23
+ });
24
+ }
25
+ /**
26
+ * obs-b 五门 skip 统一落账形(05 §1.1 compaction/skip + §2.1 判序定形注):
27
+ * fire 而被门挡才落(below 恒不落);basis 五件 = fire 判据快照(与 start
28
+ * 同律——触发时刻判据素材);remainMs 仅 cooldown 门随行(窗余值);五门
29
+ * 均不动冷却锚(skip 非 completed——下轮触发照常参与判阈)。
30
+ */
31
+ function appendSkip(log, gate, basis, remainMs) {
32
+ log.append('compaction/skip', {
33
+ gate,
34
+ ...basis,
35
+ ...(remainMs !== undefined ? { remainMs } : {}),
36
+ });
37
+ }
38
+ /**
39
+ * 组装压缩服务。五步骨架两事件形(05 §2.1 步 3 勘正):摘要 user/message
40
+ * 普通 append(不带 surfaceOp——孤儿摘要处置条款依赖此形);遮蔽指令
41
+ * compaction/surface 经 appendWithSurfaceOp 正门独携信封。
42
+ */
43
+ export function createCompactionService(options = {}) {
44
+ const channel = options.channel;
45
+ // 配置晚绑定:getConfig seam 优先(U4 配置槽);缺省 = 装配时常量合并
46
+ const defaultConfig = { ...DEFAULT_COMPACTION_CONFIG, ...options.config };
47
+ const getConfig = options.getConfig ?? (() => defaultConfig);
48
+ const getProvider = options.getProvider;
49
+ const onBeforeCompact = options.onBeforeCompact;
50
+ const algoTimeoutMs = options.algoTimeoutMs ?? 60_000;
51
+ const now = options.now ?? Date.now;
52
+ const warn = options.warn ?? ((message) => process.stderr.write(`${message}\n`));
53
+ /** per-session 防抖分账(插入序即逐出序——Map 迭代序保真) */
54
+ const states = new Map();
55
+ /** 全局串行链尾(并发上限 1 的单点;链身永不 reject——尾接吞异常) */
56
+ let chain = Promise.resolve();
57
+ /** 通道缺席告警旗(只告警一次——缺配是装配错误不是运行抖动) */
58
+ let warnedNoChannel = false;
59
+ /**
60
+ * 三振表(进程级):按 pluginId 合并计数——接管缝声明者与 provider 槽注册
61
+ * 者同账;复位 = 同 pluginId 任一路成功即清(「连续」的合并计数语义)。
62
+ * 熔断复位走进程重启(05 §2.1 回落三律第 3 律)。
63
+ */
64
+ const strikes = new Map();
65
+ const isTripped = (pluginId) => (strikes.get(pluginId) ?? 0) >= STRIKE_LIMIT;
66
+ /** 全局串行入队:前序成败不阻断后继;result 即刻挂 handler 防 unhandledRejection */
67
+ function enqueue(body) {
68
+ const result = chain.then(body, body);
69
+ chain = result.then(() => undefined, () => undefined);
70
+ return result;
71
+ }
72
+ /** 取(或建)per-session 状态;建前按帽逐出空闲态 */
73
+ function stateOf(sessionId) {
74
+ let state = states.get(sessionId);
75
+ if (state === undefined) {
76
+ // 帽前逐出:只逐空闲态(在飞/排队的删了会破防重入账);全在飞时暂超帽
77
+ // 可接受——帽执法不破在飞正确性
78
+ if (states.size >= IDLE_STATES_CAP) {
79
+ for (const [key, value] of states) {
80
+ if (states.size < IDLE_STATES_CAP)
81
+ break;
82
+ if (!value.pending)
83
+ states.delete(key);
84
+ }
85
+ }
86
+ state = { lastCompactAt: null, pending: false };
87
+ states.set(sessionId, state);
88
+ }
89
+ return state;
90
+ }
91
+ /** 末条 compaction/end 的 seq(-1 = 从未压缩)——溢出路「他路已缩量」对账锚 */
92
+ function lastEndSeq(log) {
93
+ const ends = log.eventsOfType('compaction/end');
94
+ return ends.length > 0 ? ends[ends.length - 1].seq : -1;
95
+ }
96
+ /** 宿主通道执行(直选路与回落位共用):五段结构 prompt + 迭代链前次摘要 + 字符制预算 */
97
+ async function runHost(log, plan) {
98
+ const response = await channel.complete({
99
+ prompt: buildSummaryPrompt({
100
+ occluded: plan.occluded,
101
+ previousSummary: previousSummaryText(log.events()),
102
+ maxChars: summaryBudgetFor(plan.occludedChars, getConfig()),
103
+ }),
104
+ maxChars: summaryBudgetFor(plan.occludedChars, getConfig()),
105
+ });
106
+ // 空摘要 = 通道质量异常——按通道失败收口(把内容遮在空摘要后是静默信息丢失)
107
+ if (response.text.trim().length === 0)
108
+ throw new Error('摘要通道返回空文本');
109
+ return response.text;
110
+ }
111
+ /** 算法执行段预算帽(超时后原 promise 仍在跑——SummarizerFn 无 abort 面,产物弃用不采信) */
112
+ function withTimeout(p, ms) {
113
+ return new Promise((resolve, reject) => {
114
+ const timer = setTimeout(() => reject(new AlgoTimeoutError(`算法执行段超预算 ${ms}ms`)), ms);
115
+ p.then((r) => {
116
+ clearTimeout(timer);
117
+ resolve(r);
118
+ }, (e) => {
119
+ clearTimeout(timer);
120
+ reject(e);
121
+ });
122
+ });
123
+ }
124
+ /**
125
+ * 算法执行(U4):host 直选失败原样上抛;插件路三段执法——60s 预算 → 失败
126
+ * fallback 记账 + 三振推进 → 回落直达宿主(不串联试下一层)。回落成功记
127
+ * summarizer='host'(谁失败由 fallback 词承载——05 §1.1 词表);回落失败
128
+ * 上抛(失败终局恒宿主通道失败)。
129
+ */
130
+ async function runAlgo(log, plan, algo) {
131
+ if (algo.kind === 'host') {
132
+ return { text: await runHost(log, plan), summarizer: 'host' };
133
+ }
134
+ try {
135
+ const response = await withTimeout(algo.fn({
136
+ sessionId: log.sessionId,
137
+ occluded: plan.occluded,
138
+ previousSummary: previousSummaryText(log.events()) ?? undefined,
139
+ maxChars: summaryBudgetFor(plan.occludedChars, getConfig()),
140
+ plan,
141
+ }), algoTimeoutMs);
142
+ // 空文本产物同宿主通道失败律(fiveStep 既有)——计入三振
143
+ if (response.text.trim().length === 0)
144
+ throw new AlgoEmptyTextError('插件算法返回空文本');
145
+ strikes.delete(algo.pluginId); // 复位:同 pluginId 任一路成功即清
146
+ return { text: response.text, summarizer: `plugin:${algo.pluginId}` };
147
+ }
148
+ catch (err) {
149
+ // 失败三形态判型:超预算 / 空文本产物 / 抛错(stage 词面 05 §1.1)
150
+ const stage = err instanceof AlgoTimeoutError ? 'timeout' : err instanceof AlgoEmptyTextError ? 'rejected' : 'throw';
151
+ const count = (strikes.get(algo.pluginId) ?? 0) + 1;
152
+ const circuit = count >= STRIKE_LIMIT;
153
+ strikes.set(algo.pluginId, count);
154
+ appendFallback(log, `plugin:${algo.pluginId}`, stage, err, circuit);
155
+ // 回落不失败:当轮直达宿主通道继续完成(策略可崩、机制不塌——三律第 1 律)
156
+ return { text: await runHost(log, plan), summarizer: 'host' };
157
+ }
158
+ }
159
+ /**
160
+ * 五步骨架(05 §2.1):算法解析先行 → start(携 summarizer 归因终局值)→
161
+ * 摘要普通 append → compaction/surface 经正门携信封 → end。失败即 end-failed
162
+ * 闭段(防孤 start 悬挂)后原样上抛——上层按路收口(阈值路 warn、溢出路
163
+ * 'failed')。start 后置于算法解析:归因位随执行终局定形(插件路成功记
164
+ * plugin:<id>、回落后记 'host';失败闭段归因恒 'host'——一切失败终局都是
165
+ * 宿主通道失败,插件失败已由 fallback 词承载)。
166
+ */
167
+ async function fiveStep(log, reason, plan, basis, algo = { kind: 'host' }) {
168
+ // 步 2 前置(算法解析——U4):willRetry 语义不变(阈值路失败后下轮触发自然
169
+ // 重做,溢出路一次性)
170
+ let text;
171
+ let summarizer;
172
+ try {
173
+ ({ text, summarizer } = await runAlgo(log, plan, algo));
174
+ }
175
+ catch (err) {
176
+ // 失败即时闭段(防孤 start 悬挂——§4 恢复协议按未完成压缩重做)
177
+ log.append('compaction/start', {
178
+ reason,
179
+ willRetry: reason === 'threshold',
180
+ ...(basis ?? {}),
181
+ summarizer: 'host',
182
+ });
183
+ log.append('compaction/end', { reason: 'failed', error: String(err) });
184
+ throw err;
185
+ }
186
+ // 步 1:压缩意图与判据快照(basis 五件仅阈值路落账——RP4 扩值含 cache 两桶)+ 归因位
187
+ log.append('compaction/start', {
188
+ reason,
189
+ willRetry: reason === 'threshold',
190
+ ...(basis ?? {}),
191
+ summarizer,
192
+ });
193
+ // 步 3:摘要落账——两事件形:普通 append、不带 surfaceOp(孤儿摘要按普通
194
+ // user/message 同视的处置条款依赖此形);载体前缀防注入。
195
+ // CCR 标记段(05 §2.1 压缩可逆性):宿主追加非算法产物——当次标记行 +
196
+ // 目录恒链(此前历次 surface 事件全量:旧载体被遮蔽后历史哈希仍可达)
197
+ const hash = ccrHashOf(plan.occluded);
198
+ const directory = [
199
+ ...ccrDirectoryOf(log.events()),
200
+ { hash, messages: plan.occludedMessages, chars: plan.occludedChars },
201
+ ];
202
+ const summaryEvent = log.append('user/message', {
203
+ content: `${SUMMARY_PREFIX} ${withCcrSection(text, directory)}`,
204
+ source: 'compaction',
205
+ });
206
+ // 步 4:遮蔽指令——正门独携信封;溯源 = 区间全部 seq + 摘要事件 seq;
207
+ // ccrHash = 归档映射位(本事件即「哈希 → 区间原文」映射记录——events 表
208
+ // 即归档 store,检索面 ccr_retrieve 的匹配键)
209
+ const sourceEventSeqs = [];
210
+ for (let seq = plan.start; seq <= plan.end; seq++)
211
+ sourceEventSeqs.push(seq);
212
+ sourceEventSeqs.push(summaryEvent.seq);
213
+ log.appendWithSurfaceOp('compaction/surface', {
214
+ summarySeq: summaryEvent.seq,
215
+ occludedMessages: plan.occludedMessages,
216
+ occludedChars: plan.occludedChars,
217
+ ccrHash: hash,
218
+ }, { op: 'replace', start: plan.start, end: plan.end }, sourceEventSeqs);
219
+ // 步 5:完成收尾(载荷 = 规模审计对账面)
220
+ log.append('compaction/end', {
221
+ reason: 'completed',
222
+ occludedMessages: plan.occludedMessages,
223
+ occludedChars: plan.occludedChars,
224
+ });
225
+ }
226
+ /**
227
+ * 接管缝裁决(U4——宿主检查序,05 §2.1 位间裁决):veto 先检(在场即否决
228
+ * 收场,余位不问)→ takeover 次之(在场即接管;接管输入 = 管线终值 plan,
229
+ * 先行监听者的有效调整自然随值链传入;熔断者忽略 = 资格除名后重选)→
230
+ * 调整位(引用变即校验 + 素材重算;非法调整 = 忽略 + fallback 记账 +
231
+ * 宿主原案续跑)。返回当轮算法与生效区间;veto 收场返回 null。
232
+ */
233
+ async function dispatchBeforeCompact(log, plan, basis, messages) {
234
+ const { value, lastAdjustedBy } = await onBeforeCompact({
235
+ sessionId: log.sessionId,
236
+ reason: 'threshold',
237
+ plan,
238
+ basis,
239
+ });
240
+ // veto 先检:一票否决收场(判阈触发过是审计面事实——落 start+end 对可查、
241
+ // 无声取消不可观测;意见位无三振)
242
+ if (value.veto !== undefined) {
243
+ log.append('compaction/start', { reason: 'threshold', willRetry: true, ...basis });
244
+ log.append('compaction/end', { reason: 'vetoed' });
245
+ return null;
246
+ }
247
+ let algo = { kind: 'host' };
248
+ // takeover 次之:熔断者忽略(资格除名——当轮按生效序落 provider/host,
249
+ // 非失败回落不走「直达宿主」条款;钩子本身不停派——veto/调整仍有效)
250
+ if (value.takeover !== undefined) {
251
+ const pid = value.takeover.pluginId ?? 'unknown';
252
+ if (isTripped(pid))
253
+ warn(`[COMPACTION_CIRCUIT_IGNORE] takeover 位被熔断忽略: ${pid}`);
254
+ else
255
+ algo = { kind: 'plugin', pluginId: pid, fn: value.takeover.summarize };
256
+ }
257
+ // 调整位:值链改写必新建对象(引用比较);生效区间素材三件宿主重算(区间
258
+ // 主权 = 唯一采用面,不信载荷);越宿主原案界 / 区间内无投影消息 = 非法调整
259
+ if (value.plan !== plan) {
260
+ const accepted = validateAdjustedRange(value.plan, plan) &&
261
+ planFromRange(value.plan.start, value.plan.end, messages).occludedMessages >= 1;
262
+ if (accepted) {
263
+ plan = planFromRange(value.plan.start, value.plan.end, messages);
264
+ }
265
+ else {
266
+ appendFallback(log, lastAdjustedBy !== undefined ? `plugin:${lastAdjustedBy}` : 'host', 'rejected', `非法调整区间 [${value.plan.start}, ${value.plan.end}](越宿主原案界或区间内无投影消息)`);
267
+ // 宿主原案续跑(忽略该调整)
268
+ }
269
+ }
270
+ return { algo, plan };
271
+ }
272
+ return {
273
+ handleRunSettled(input) {
274
+ // —— obs-b 判序定形(05 §2.1):阈值评估先行(纯函数零副作用)——
275
+ // below 不落任何账(判据素材可后算,不为 below 防 durable 膨胀落
276
+ // skip);fire 而被门挡才落 compaction/skip(五门词 05 §1.1)。
277
+ // 判阈双源:真 token 主判(usage.input)、投影字符兜底(chars/4)
278
+ const cfg = getConfig();
279
+ const verdict = evaluateThreshold({
280
+ usageInput: input.usage?.input ?? null,
281
+ contextWindow: input.usage?.contextWindow,
282
+ projectedChars: input.log.projectedChars(),
283
+ config: cfg,
284
+ });
285
+ if (verdict === null || !verdict.fire)
286
+ return;
287
+ // basis 五件(RP4 扩值):fire 判据快照——触发时刻判据素材(与 start
288
+ // 同律;cache 两桶从主 loop 真值笔同笔透传,estimate 兜底路恒缺省不落)
289
+ const basis = {
290
+ basis: verdict.basis,
291
+ estTokens: verdict.estTokens,
292
+ effectiveWindow: verdict.effectiveWindow,
293
+ ...(input.usage?.cacheRead !== undefined ? { cacheRead: input.usage.cacheRead } : {}),
294
+ ...(input.usage?.cacheWrite !== undefined ? { cacheWrite: input.usage.cacheWrite } : {}),
295
+ };
296
+ // 门① no-channel(装配级永久门最先呈报最诊断):通道缺席——首触落
297
+ // 一条 skip 后静默(与 warn-once 同锚——缺配是装配错误不是运行抖动)
298
+ if (channel === undefined) {
299
+ if (!warnedNoChannel) {
300
+ warnedNoChannel = true;
301
+ warn('[COMPACTION_NO_CHANNEL] 摘要通道缺席——阈值压缩停用(溢出面将报 failed)');
302
+ appendSkip(input.log, 'no-channel', basis);
303
+ }
304
+ return;
305
+ }
306
+ const state = stateOf(input.log.sessionId);
307
+ // 门② pending:防重入挡(进行中/已排队期重复 fire 可观测)
308
+ if (state.pending) {
309
+ appendSkip(input.log, 'pending', basis);
310
+ return;
311
+ }
312
+ // 门③ cooldown:冷却窗挡(判据与 policy.inCooldown 同式就地展开——
313
+ // remainMs 窗余值需要差值,单一 now() 采样点保证两值同账。2026-09-13
314
+ // 复盘发现 ⑰:修前判据与余值各采一次——挂钟跨采样推进可出负余值
315
+ // (判据说「窗内」而回执窗余为负,不同账),就此单采样)
316
+ const nowMs = now();
317
+ if (state.lastCompactAt !== null && nowMs - state.lastCompactAt < cfg.cooldownMs) {
318
+ appendSkip(input.log, 'cooldown', basis, cfg.cooldownMs - (nowMs - state.lastCompactAt));
319
+ return;
320
+ }
321
+ state.pending = true;
322
+ // fire-and-forget:排队体内部全收口(不外抛);冷却锚只在成功时推进
323
+ void enqueue(async () => {
324
+ try {
325
+ // 锁内复评(幻影触发闸):全局串行链排队期间判据面可能已变(投影
326
+ // 被他路压缩改写 / 配置槽翻值);原 usage 笔不重放——真 token 计量
327
+ // 是请求事实非投影派生,token-basis 复评不受投影影响
328
+ const freshVerdict = evaluateThreshold({
329
+ usageInput: input.usage?.input ?? null,
330
+ contextWindow: input.usage?.contextWindow,
331
+ projectedChars: input.log.projectedChars(),
332
+ config: getConfig(),
333
+ });
334
+ if (freshVerdict === null || !freshVerdict.fire) {
335
+ // 门④ retracted:入队时 fire、锁内已不 fire——幻影触发可查(basis
336
+ // 用原 verdict 快照——触发时刻判据,非复评时刻)
337
+ appendSkip(input.log, 'retracted', basis);
338
+ return; // 不动冷却锚(skip 非 completed)
339
+ }
340
+ // 锁内新投影(全局串行链 = 互斥锁——排队期间他路压缩可能已改写投影,
341
+ // 规划与落账同账零迟滞窗)
342
+ const messages = input.log.projection();
343
+ const plan = planSegment({
344
+ events: input.log.events(),
345
+ messages,
346
+ tailKeep: getConfig().tailKeep,
347
+ });
348
+ if (plan === null) {
349
+ // 门⑤ no-segment:fire 但区间规划无合法段(head+tail 全兜的薄会话形)
350
+ appendSkip(input.log, 'no-segment', basis);
351
+ return; // 不动冷却锚——诚实无操作可观测
352
+ }
353
+ // —— 接管缝(U4):阈值路排队体内、区间规划后、start 落账前派发 ——
354
+ let algo = { kind: 'host' };
355
+ let effectivePlan = plan;
356
+ if (onBeforeCompact !== undefined) {
357
+ const dispatched = await dispatchBeforeCompact(input.log, plan, basis, messages);
358
+ if (dispatched === null)
359
+ return; // veto 收场(冷却锚不推进——否决非完成)
360
+ ({ algo, plan: effectivePlan } = dispatched);
361
+ }
362
+ // provider 槽(常设注册——takeover 缺席/被熔断忽略时顶上;熔断视为空;
363
+ // 只作用阈值路——溢出 路 compactForOverflow 不经此段)
364
+ if (algo.kind === 'host' && getProvider !== undefined) {
365
+ const provider = getProvider();
366
+ if (provider !== undefined) {
367
+ if (isTripped(provider.pluginId)) {
368
+ warn(`[COMPACTION_CIRCUIT_IGNORE] provider 槽被熔断视为空: ${provider.pluginId}`);
369
+ }
370
+ else {
371
+ algo = { kind: 'plugin', pluginId: provider.pluginId, fn: provider.fn };
372
+ }
373
+ }
374
+ }
375
+ await fiveStep(input.log, 'threshold', effectivePlan, basis, algo);
376
+ state.lastCompactAt = now();
377
+ }
378
+ catch (err) {
379
+ // fiveStep 已落 end-failed 闭段——此处只兜五步之外的意外(可观测不静默)
380
+ warn(`[COMPACTION_FAILED] ${input.log.sessionId}: ${String(err)}`);
381
+ }
382
+ finally {
383
+ state.pending = false;
384
+ }
385
+ });
386
+ },
387
+ async compactForOverflow(log) {
388
+ // 门三道之三(归驱动执法面):通道不可用直接终态——无摘要则无压缩
389
+ if (channel === undefined)
390
+ return 'failed';
391
+ // 入队锚:等待期间他路压缩若已落账,缩量已达成(归因不问路——恢复目标
392
+ // 是「缩了可续入」,谁压的不重要;FIFO 链保证本任务在他路之后执行)
393
+ const endSeqAtEntry = lastEndSeq(log);
394
+ return enqueue(async () => {
395
+ if (lastEndSeq(log) !== endSeqAtEntry)
396
+ return 'compacted';
397
+ const plan = planSegment({
398
+ events: log.events(),
399
+ messages: log.projection(),
400
+ tailKeep: getConfig().tailKeep, // 两路同生效(数值系宿主机制参数非策略算法)
401
+ });
402
+ if (plan === null)
403
+ return 'nothing'; // 区间不足(应急档也压无可压)
404
+ try {
405
+ // 溢出 路:恒宿主缺省算法、不派发接管缝(应急自救通道不让渡策略层)
406
+ await fiveStep(log, 'overflow', plan);
407
+ }
408
+ catch {
409
+ return 'failed'; // fiveStep 已落 end-failed 闭段——诚实报败,重试编舞归驱动
410
+ }
411
+ // 溢出压缩同样推进冷却锚:防紧随的阈值触发对刚压过的区间重复开工
412
+ stateOf(log.sessionId).lastCompactAt = now();
413
+ return 'compacted';
414
+ });
415
+ },
416
+ async drain() {
417
+ // 快照语义:等此刻前已入队/在飞的一切(chain 永不 reject——尾接吞异常);
418
+ // 等待期间新入队的不在本方法承诺内
419
+ await chain;
420
+ },
421
+ };
422
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * compaction 席位容器 + 接管缝归因铸造件(U4-3 装配批——05 §2.1 槽位化三层的
3
+ * 装载面装配件;动词词面真源 03 §2.2 第十二面/§2.7 两动词行)。
4
+ *
5
+ * 三件产物:
6
+ * - **席位容器**(createCompactionSlots):配置槽 + 摘要 provider 槽的单席位
7
+ * 状态机(先到占 / 他插件后到拒 / 同席位者可更新 / 卸载回收)。host 装配根
8
+ * 单真身:conversation-stack 消费 getConfig/getProvider(服务面三 seam 之
9
+ * 二的容器位),plugin-boot 逐插件 fork 绑定 bindForPlugin 产物(经
10
+ * ctx.get("compaction") 消费)。本模块零 context 边——窗口真源由装配层注入
11
+ * (inLoadWindow 回调,绑本插件 handle)。
12
+ * - **归因箱 + 记名/铸造两律**(BEFORE_COMPACT_ATTRIB / mark / forge):
13
+ * session_before_compact 值链的逐跳归因机制件——plugin-context 钩子包装层
14
+ * 消费(值链改写者记名 + takeover.pluginId 强制覆写,冒名结构性不存在);
15
+ * conversation-stack 派发包装层种子箱读末位。symbol 键经对象 spread 天然
16
+ * 随值链传播(改写必新建对象——引用比较判改写的服务面约定同源)。
17
+ */
18
+ import { BaseError } from '../contracts/index.js';
19
+ import { DEFAULT_COMPACTION_CONFIG } from './types.js';
20
+ /**
21
+ * 归因箱值链键(unique symbol——模块外不可字面伪造):宿主种子在载荷对象上,
22
+ * 插件改写值必经 spread/新建(「值链改写必新建对象」律),spread 复制 symbol
23
+ * 键属性 → 箱引用沿链共享。非接管缝的 waterfall 值不携带本键——记名/铸造
24
+ * 两律对其零接触(通用包装层的机制件消费位判据)。
25
+ */
26
+ export const BEFORE_COMPACT_ATTRIB = Symbol('session-before-compact-attribution');
27
+ /**
28
+ * 记名律:监听者改写值链时记末位改写者(plugin-context 钩子包装层逐跳调用)。
29
+ * 箱缺席 = 非接管缝值链——no-op(通用 waterfall 不受影响)。残余面:插件在
30
+ * handler 体内直改箱内容而不改值可伪记他名——末位真改写者必覆写之,且该
31
+ * 归因仅 fallback 记账审计面(非权限面),残余可受。
32
+ */
33
+ export function markBeforeCompactRewrite(value, pluginId) {
34
+ if (typeof value !== 'object' || value === null)
35
+ return;
36
+ const box = value[BEFORE_COMPACT_ATTRIB];
37
+ if (typeof box === 'object' && box !== null) {
38
+ box.lastAdjustedBy = pluginId;
39
+ }
40
+ }
41
+ /**
42
+ * 铸造律:takeover.pluginId 由宿主钩子包装层强制覆写为本监听者(03 §2.4 行
43
+ * 「归因铸造」/05 §2.1——e-4 caller 闭包同律,插件自填被覆写)。执法判据 =
44
+ * **接管位引用换**(produced.takeover !== received.takeover——「值链改写必
45
+ * 新建对象」律同源):本监听者新置/重建接管位才覆写;原引用透传(含改写
46
+ * 他位而保留接管对象引用的 spread 形)不覆写——防下游误夺上游接管归因。
47
+ * 只在携带归因箱的值上执法(非接管缝值链零接触);takeover 位缺席 = 纯改
48
+ * 写/放行,原值直返不改引用。
49
+ */
50
+ export function forgeBeforeCompactIdentity(produced, received, pluginId) {
51
+ if (typeof produced !== 'object' || produced === null)
52
+ return produced;
53
+ const record = produced;
54
+ if (record[BEFORE_COMPACT_ATTRIB] === undefined)
55
+ return produced; // 非接管缝值链——零接触
56
+ const takeover = record['takeover'];
57
+ if (typeof takeover !== 'object' || takeover === null)
58
+ return produced; // 无接管位——原值直返
59
+ // 上游接管位原引用透传——非本监听者所置,不覆写
60
+ const prev = typeof received === 'object' && received !== null
61
+ ? received['takeover']
62
+ : undefined;
63
+ if (takeover === prev)
64
+ return produced;
65
+ return { ...record, takeover: { ...takeover, pluginId } };
66
+ }
67
+ /**
68
+ * 组装席位容器(host 装配根单真身)。状态进程级内存态:/reload 卸载序经
69
+ * releaseFor 回收、重启清零——压缩配置非 durable 语义(缺省恒在场兜底)。
70
+ */
71
+ export function createCompactionSlots(options = {}) {
72
+ const baseConfig = options.baseConfig ?? DEFAULT_COMPACTION_CONFIG;
73
+ let configSeat;
74
+ let providerSeat;
75
+ return {
76
+ getConfig() {
77
+ return { ...baseConfig, ...(configSeat?.partial ?? {}) };
78
+ },
79
+ getProvider() {
80
+ return providerSeat;
81
+ },
82
+ bindForPlugin({ pluginId, inLoadWindow }) {
83
+ /** 装载窗闸(严于通律——回调窗延伸不适用;窗真源 = 本插件 handle) */
84
+ const assertLoadWindow = (verb) => {
85
+ if (inLoadWindow())
86
+ return;
87
+ throw new BaseError('PLUGIN_WINDOW_CLOSED', `注册动词 ${verb} 在装载窗口外被拒(插件 ${pluginId}——03 §2.2 第十二面:压缩策略面两动词装载窗 only 严于通律,回调窗延伸不适用〔装配期配置/注册动作非回调场景动作〕)`);
88
+ };
89
+ return {
90
+ setConfig(partial) {
91
+ assertLoadWindow('ctx.compaction.setConfig');
92
+ if (configSeat !== undefined && configSeat.pluginId !== pluginId) {
93
+ throw new BaseError('COMPACTION_CONFIG_TAKEN', `数值配置槽已被插件 ${configSeat.pluginId} 占据(插件 ${pluginId} 后到拒——单席位先到占;配置主权单源,两插件各设阈值 = 用户装配面错误,fail-loud 拒不静默 last-wins)`);
94
+ }
95
+ configSeat = { pluginId, partial };
96
+ return () => {
97
+ if (configSeat?.pluginId === pluginId)
98
+ configSeat = undefined; // 幂等:只摘自己(后设者不误伤)
99
+ };
100
+ },
101
+ registerSummarizer(fn) {
102
+ assertLoadWindow('ctx.compaction.registerSummarizer');
103
+ if (providerSeat !== undefined && providerSeat.pluginId !== pluginId) {
104
+ throw new BaseError('COMPACTION_SUMMARIZER_TAKEN', `摘要 provider 槽已被插件 ${providerSeat.pluginId} 占据(插件 ${pluginId} 后到拒——单席位先到占;溢出兜底恒宿主缺省算法,槽不可及)`);
105
+ }
106
+ providerSeat = { pluginId, fn };
107
+ return () => {
108
+ if (providerSeat?.pluginId === pluginId)
109
+ providerSeat = undefined;
110
+ };
111
+ },
112
+ };
113
+ },
114
+ releaseFor(pluginId) {
115
+ if (configSeat?.pluginId === pluginId)
116
+ configSeat = undefined;
117
+ if (providerSeat?.pluginId === pluginId)
118
+ providerSeat = undefined;
119
+ },
120
+ };
121
+ }
@@ -0,0 +1,10 @@
1
+ /** 缺省配置(05 §2.1「摘要参数」+「防抖三件」+「阈值」段定值——首版实测后调) */
2
+ export const DEFAULT_COMPACTION_CONFIG = {
3
+ thresholdRatio: 0.5,
4
+ tailKeep: 6,
5
+ cooldownMs: 600_000,
6
+ summaryRatio: 0.2,
7
+ summaryMinChars: 2000,
8
+ summaryMaxChars: 12000,
9
+ fallbackWindowTokens: 200_000,
10
+ };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * context 模块错误码注册(02 §5.3 #2:各模块落码批经 registerErrorCodes 显式注册,
3
+ * 禁字符串字面量散落)。
4
+ *
5
+ * 域码族注记:EVENT_* / SCOPE_* 系 04 §6 定名的域语义命名(事件面/作用域面),
6
+ * 视同 CONTEXT_ 域族成员(02 §5.3 前缀族注记,2026-09-05 落码批)。
7
+ */
8
+ import { registerErrorCodes } from '../contracts/index.js';
9
+ registerErrorCodes([
10
+ {
11
+ code: 'EVENT_NOT_REGISTERED',
12
+ module: 'context',
13
+ description: 'ctx.emit/on 未注册词 fail-loud(词汇注册表单源——禁散播未声明事件名)',
14
+ },
15
+ {
16
+ code: 'EVENT_DUPLICATE',
17
+ module: 'context',
18
+ description: '事件词汇撞名拒静默覆盖(注册表单源执法)',
19
+ },
20
+ {
21
+ code: 'SCOPE_STALE',
22
+ module: 'context',
23
+ description: '作用域已回卷后的迟到操作(迟到的服务注册/disposer 登记拒绝——异步竞速收口)',
24
+ },
25
+ {
26
+ code: 'SCOPE_EFFECT_CAPACITY',
27
+ module: 'context',
28
+ description: 'effect 总注册帽:本作用域自有登记达 10^4 拒新登记(03 §3.4 可用性防线——失控登记不拖垮回卷序;拒在 register 回调执行前)',
29
+ },
30
+ {
31
+ code: 'CONTEXT_SERVICE_MISSING',
32
+ module: 'context',
33
+ description: 'get 缺席 fail-loud(拼错名立即炸,不静默 undefined 传播;可选消费走 tryGet)',
34
+ },
35
+ {
36
+ code: 'CONTEXT_SERVICE_DUPLICATE',
37
+ module: 'context',
38
+ description: '同作用域 provide 撞名拒(两方抢一名即装配 bug;fork 子遮蔽父合法)',
39
+ },
40
+ ]);