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,24 @@
1
+ /** 线协议版本(③/⑤ 版本握手第一天就有:连接即 hello 双方携 protocolVersion 比对) */
2
+ export const SDK_PROTOCOL_VERSION = 1;
3
+ /**
4
+ * 线帧 kind 闭集(十:活体事件帧 + 线控四件〔hello/heartbeat/ack/replay-end〕+
5
+ * 请求应答三件〔entries/sessions/decide-result〕+ 错误帧 + ask 审批外推帧——
6
+ * ask 独立于 event 位,审批挂起态非活体流,见 {@link SdkAskFrame} 文注)。
7
+ */
8
+ export const SDK_FRAME_KINDS = [
9
+ 'event',
10
+ 'hello',
11
+ 'heartbeat',
12
+ 'ack',
13
+ 'replay-end',
14
+ 'error',
15
+ 'entries',
16
+ 'sessions',
17
+ 'decide-result',
18
+ 'ask',
19
+ ];
20
+ /**
21
+ * 请求动词六件闭集(03 §10.6 请求面动词族条——v1 收窄律;steer/followUp/inject
22
+ * 三通道词**不在场**是执法位:驱动侧单源律的结构锁,测试断言在册)。
23
+ */
24
+ export const SDK_REQUEST_VERBS = ['hello', 'prompt', 'interrupt', 'decide', 'getEntries', 'sessions'];
@@ -0,0 +1,91 @@
1
+ /**
2
+ * SDK 线协议请求面 typebox 深校验(03 §10.6 请求面动词族条「每动词 typebox
3
+ * schema 校验后消费——webui 微路由同纪律」;13a 挂账批 13e 兑现件)。
4
+ *
5
+ * 单源律:六动词 schema 全集即请求面唯一深校验真源——stdio 解码位
6
+ * (./jsonl.ts decodeWireLine 尾段)与 HTTP 体校验位(core:sdk 件经 channels
7
+ * 公开面消费)同源接入,零第二套。结构层(判别字段闭集/必填标量型)仍由
8
+ * jsonl.ts 先行快校——本层补齐逐字段型/选填语义/未知字段拒收。
9
+ *
10
+ * 非抛型结果面:{ok, value} | {ok:false, reason}——错误帧映射归宿主传输层
11
+ * (stdio = SdkDecodeError 包装;HTTP = 400 携结构化错误码),本层零错误词。
12
+ */
13
+ import { Type } from 'typebox';
14
+ import { Value } from 'typebox/value';
15
+ // 顶部 typebox 注入形态说明:typebox 主包 + value 子路径均入 channels externals
16
+ // 白名单(tools/conversation/web/exec/skills 同律——schema 层宿主件直用)。
17
+ /** 公共字段词面:未知字段拒收(收窄律执法——请求面六动词载荷即全集) */
18
+ const strict = { additionalProperties: false };
19
+ /** hello 请求 schema(protocolVersion 必填;sessionId/after/noDelta 选填) */
20
+ const HelloSchema = Type.Object({
21
+ verb: Type.Literal('hello'),
22
+ protocolVersion: Type.Integer(),
23
+ sessionId: Type.Optional(Type.String()),
24
+ after: Type.Optional(Type.Integer()),
25
+ noDelta: Type.Optional(Type.Boolean()),
26
+ }, strict);
27
+ /** prompt 请求 schema(messageId/content 必填;sessionId 选填——缺席即新建) */
28
+ const PromptSchema = Type.Object({
29
+ verb: Type.Literal('prompt'),
30
+ messageId: Type.String(),
31
+ content: Type.String(),
32
+ sessionId: Type.Optional(Type.String()),
33
+ }, strict);
34
+ /** interrupt 请求 schema(sessionId 必填) */
35
+ const InterruptSchema = Type.Object({
36
+ verb: Type.Literal('interrupt'),
37
+ sessionId: Type.String(),
38
+ }, strict);
39
+ /** decide 请求 schema(approvalId/answer 必填;note 选填——answer 四值闭集) */
40
+ const DecideSchema = Type.Object({
41
+ verb: Type.Literal('decide'),
42
+ approvalId: Type.String(),
43
+ answer: Type.Union([
44
+ Type.Literal('approve'),
45
+ Type.Literal('reject'),
46
+ Type.Literal('cancel'),
47
+ Type.Literal('always'),
48
+ ]),
49
+ note: Type.Optional(Type.String()),
50
+ }, strict);
51
+ /** getEntries 请求 schema(sessionId/since 必填;cursor 选填——分页续读) */
52
+ const GetEntriesSchema = Type.Object({
53
+ verb: Type.Literal('getEntries'),
54
+ sessionId: Type.String(),
55
+ since: Type.Integer(),
56
+ cursor: Type.Optional(Type.String()),
57
+ }, strict);
58
+ /** sessions 请求 schema(零载荷) */
59
+ const SessionsSchema = Type.Object({ verb: Type.Literal('sessions') }, strict);
60
+ /** 六动词 schema 分派表(verb 判别字段路由——与 SDK_REQUEST_VERBS 同序) */
61
+ const SCHEMAS_BY_VERB = {
62
+ hello: HelloSchema,
63
+ prompt: PromptSchema,
64
+ interrupt: InterruptSchema,
65
+ decide: DecideSchema,
66
+ getEntries: GetEntriesSchema,
67
+ sessions: SessionsSchema,
68
+ };
69
+ /**
70
+ * 请求深校验(非抛型)。输入须已过 jsonl.ts 结构层(verb 在闭集内)——本层
71
+ * 补逐字段校验;独立调用时对判别字段缺席/闭集外同样诚实拒收(reason 单句)。
72
+ */
73
+ export function validateSdkRequest(value) {
74
+ if (typeof value !== 'object' || value === null) {
75
+ return { ok: false, reason: '请求须为 JSON 对象' };
76
+ }
77
+ const verb = value.verb;
78
+ const schema = typeof verb === 'string' ? SCHEMAS_BY_VERB[verb] : undefined;
79
+ if (schema === undefined) {
80
+ return { ok: false, reason: `请求动词不识别(verb=${String(verb)}——闭集外或缺席)` };
81
+ }
82
+ if (!Value.Check(schema, value)) {
83
+ // 逐字段首错定位(fail-loud 可行动报因——webui 微路由同纪律)
84
+ const errors = [...Value.Errors(schema, value)];
85
+ const first = errors[0];
86
+ const at = first === undefined ? '' : `(${first.instancePath || '(root)'}:${first.message})`;
87
+ return { ok: false, reason: `${verb} 请求载荷不合 schema${at}` };
88
+ }
89
+ // Value.Check 通过即结构吻合 SdkRequest 判别联合——窄化断言位
90
+ return { ok: true, value: value };
91
+ }
@@ -0,0 +1,446 @@
1
+ import { admitMessage } from './admit.js';
2
+ import { validateAfterCursor } from './cursor.js';
3
+ import { SDK_PROTOCOL_VERSION, } from './protocol.js';
4
+ /**
5
+ * 判帧是否纯活体可丢(⑦ 背压 shedding 面——03 §10.6「纯活体帧可丢」):
6
+ * message_update delta 与 tool_execution_update 进度帧——丢帧无害,durable
7
+ * 定稿/锚定帧兜底(双轨律掉帧语义)。其余 event 帧与全部线控/回执帧不可丢。
8
+ */
9
+ export function isDroppableFrame(frame) {
10
+ return (frame.kind === 'event' && (frame.event.type === 'message_update' || frame.event.type === 'tool_execution_update'));
11
+ }
12
+ /**
13
+ * 出站有界队列(⑦):sink 可写即直写;背压期入队;溢出先丢队内最旧可丢帧
14
+ * (shedding——丢纯活体保线控),无可丢可让则过载(线控帧不可丢 ⇒ 断连档)。
15
+ */
16
+ export class SdkOutboundQueue {
17
+ sink;
18
+ cap;
19
+ queue = [];
20
+ overloaded = false;
21
+ constructor(sink, cap) {
22
+ this.sink = sink;
23
+ this.cap = cap;
24
+ }
25
+ /** 累计丢弃纯活体帧数(观测面——宿主日志/指标可读) */
26
+ droppedCount = 0;
27
+ /** 容量观测面(过载错误帧文案与宿主指标用) */
28
+ get capacity() {
29
+ return this.cap;
30
+ }
31
+ enqueue(frame) {
32
+ if (this.overloaded)
33
+ return 'dropped'; // 断连档后全弃(宿主即将关传输)
34
+ if (this.queue.length === 0 && this.sink.write(frame))
35
+ return 'sent';
36
+ if (this.queue.length >= this.cap) {
37
+ // 溢出纪律:入站纯活体帧先自弃(最廉 shedding);线控/锚定帧入站则让
38
+ // 位队内最旧纯活体帧,队内无可让 ⇒ 过载(线控帧不可丢——断连档)
39
+ if (isDroppableFrame(frame)) {
40
+ this.droppedCount++;
41
+ return 'dropped';
42
+ }
43
+ while (this.queue.length >= this.cap) {
44
+ const shedIndex = this.queue.findIndex(isDroppableFrame);
45
+ if (shedIndex === -1) {
46
+ this.overloaded = true;
47
+ return 'overload';
48
+ }
49
+ this.queue.splice(shedIndex, 1);
50
+ this.droppedCount++;
51
+ }
52
+ }
53
+ this.queue.push(frame);
54
+ return 'queued';
55
+ }
56
+ /**
57
+ * 宿主 writable 时冲刷(按序 FIFO;sink 再拒即停——余量留下轮)。
58
+ * @returns 本轮冲出帧数
59
+ */
60
+ drain() {
61
+ let drained = 0;
62
+ while (this.queue.length > 0 && this.sink.write(this.queue[0])) {
63
+ this.queue.shift();
64
+ drained++;
65
+ }
66
+ return drained;
67
+ }
68
+ get isOverloaded() {
69
+ return this.overloaded;
70
+ }
71
+ }
72
+ /**
73
+ * SDK 线协议核心(单连接状态机)。
74
+ *
75
+ * 生命周期间隙:宿主建核 → 行解码(./jsonl.ts)→ handleRequest 逐请求;
76
+ * 13b-3 后端把活体事件推入 {@link pushEvent};宿主定时调
77
+ * {@link heartbeatTick}(心跳义务的装配驱动位)与 {@link drain}(writable)。
78
+ */
79
+ export class SdkWireCore {
80
+ deps;
81
+ subs = new Map();
82
+ /** admit 已知键账(连接生命周期内快速档——跨重启档走 lookupDedupeKey) */
83
+ known = new Map();
84
+ /** 连接级 messageId → sessionId 反查账(sessionId 缺席重发的快速档定位面) */
85
+ messageIndex = new Map();
86
+ queue;
87
+ now;
88
+ heartbeatIntervalMs;
89
+ overloadRetryAfterMs;
90
+ /** 连接级缺省 noDelta(连接级 hello 落定;prompt 自动订阅继承) */
91
+ connectionNoDelta;
92
+ closed = false;
93
+ constructor(deps, options = {}) {
94
+ this.deps = deps;
95
+ this.now = options.now ?? Date.now;
96
+ this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? 5_000;
97
+ this.overloadRetryAfterMs = options.overloadRetryAfterMs ?? 1_000;
98
+ this.connectionNoDelta = options.initialConnectionNoDelta ?? false;
99
+ this.queue = new SdkOutboundQueue(deps.sink, options.queueCap ?? 512);
100
+ }
101
+ // ---------------------------------------------------------------------------
102
+ // 请求面六动词受理
103
+ // ---------------------------------------------------------------------------
104
+ /** 请求受理总入口(行解码后逐请求路由——本方法即 NDJSON 请求面的全语义) */
105
+ handleRequest(req) {
106
+ if (this.closed) {
107
+ // 版本拒连/过载断连后的残行:同码直写重申(绕队列——连接将亡,宿主随即关传输);
108
+ // 宿主主动 close()(无因)后的残行静默弃(装配纪律位:关传输后不应再喂行)
109
+ if (this.closedReason !== undefined) {
110
+ this.deps.sink.write({
111
+ kind: 'error',
112
+ code: this.closedReason,
113
+ message: '连接已关闭(版本握手拒连或出站过载断连)——残行不受理',
114
+ });
115
+ }
116
+ return;
117
+ }
118
+ switch (req.verb) {
119
+ case 'hello':
120
+ this.handleHello(req.protocolVersion, req.sessionId, req.after, req.noDelta);
121
+ break;
122
+ case 'prompt':
123
+ this.handlePrompt(req.sessionId, req.messageId, req.content);
124
+ break;
125
+ case 'interrupt':
126
+ this.handleInterrupt(req.sessionId);
127
+ break;
128
+ case 'decide':
129
+ this.handleDecide(req.approvalId, req.answer, req.note);
130
+ break;
131
+ case 'getEntries':
132
+ this.handleGetEntries(req.sessionId, req.since, req.cursor);
133
+ break;
134
+ case 'sessions':
135
+ this.emit({ kind: 'sessions', sessions: this.deps.listSessions() });
136
+ break;
137
+ }
138
+ }
139
+ /** hello:版本握手(⑤)→ 会话订阅 + 游标重放(③)→ 衔接界标 */
140
+ handleHello(protocolVersion, sessionId, after, noDelta) {
141
+ // ⑤ 版本握手第一天就有:不符即拒连(先应答后置闭——emit 对 closed 自静默)
142
+ if (protocolVersion !== SDK_PROTOCOL_VERSION) {
143
+ this.emitError('SDK_PROTOCOL_MISMATCH', `线协议版本不符:服务端 ${SDK_PROTOCOL_VERSION} / 调用方 ${protocolVersion}`);
144
+ this.closed = true;
145
+ this.closedReason = 'SDK_PROTOCOL_MISMATCH';
146
+ return;
147
+ }
148
+ // 连接级握手(无会话订阅):落定连接缺省 noDelta——显式携值胜出、缺席承
149
+ // 构造初值(07 §5 --no-delta 宿主立场缺省;选项缺席时初值 false 语义不变)
150
+ if (sessionId === undefined) {
151
+ this.connectionNoDelta = noDelta ?? this.connectionNoDelta;
152
+ this.emit({ kind: 'hello', protocolVersion: SDK_PROTOCOL_VERSION, sessionId: '', highWaterSeq: 0 });
153
+ return;
154
+ }
155
+ const state = this.deps.sessionStateOf(sessionId);
156
+ if (state === 'missing') {
157
+ this.emitError('SESSION_NOT_FOUND', `会话 ${sessionId} 不存在`, sessionId);
158
+ return;
159
+ }
160
+ // 订阅三步(同步读面下原子——文件头衔接序文注):①快照高水位(先于挂监听,
161
+ // 快照后事件只走直播段——零跨段重复)②重放至快照 ③replay-end 后转直播
162
+ const highWater = this.deps.highWaterOf(sessionId) ?? 0;
163
+ this.emit({ kind: 'hello', protocolVersion: SDK_PROTOCOL_VERSION, sessionId, highWaterSeq: highWater });
164
+ let lastReplayed = -1;
165
+ if (after !== undefined) {
166
+ const check = validateAfterCursor(after, highWater);
167
+ if (!check.ok) {
168
+ this.emitError('SDK_CURSOR_INVALID', `订阅游标非法(${check.reason}):after=${after} 高水位=${highWater}`, sessionId);
169
+ return; // 游标非法即拒订阅(不挂监听——调用方重对账后再来)
170
+ }
171
+ const page = this.deps.queryEntries(sessionId, after);
172
+ if (page.entries.length > 0) {
173
+ this.emit({ kind: 'entries', sessionId, entries: page.entries, nextCursor: page.nextCursor });
174
+ lastReplayed = page.entries[page.entries.length - 1].seq;
175
+ }
176
+ else {
177
+ lastReplayed = after; // 空窗(完全追平档)——after 原值即重放尾
178
+ }
179
+ }
180
+ this.emit({ kind: 'replay-end', sessionId, lastReplayedSeq: lastReplayed });
181
+ this.subs.set(sessionId, {
182
+ noDelta: noDelta ?? this.connectionNoDelta,
183
+ phase: 'live',
184
+ running: false,
185
+ runStartAt: 0,
186
+ lastElapsedMs: 0,
187
+ stage: null,
188
+ lastFrameAt: this.now(),
189
+ });
190
+ this.deps.onSubscribed?.(sessionId);
191
+ }
192
+ /** prompt:admit 三档(④)→ fresh 受理 / duplicate 幂等重收执 / conflict 拒收 */
193
+ handlePrompt(sessionId, messageId, content) {
194
+ // sessionId 缺席但连接内既见该 messageId:定位既落会话(重发走原会话快速档——
195
+ // 反查账是连接级索引,不参与 durable 语义)
196
+ if (sessionId === undefined) {
197
+ const indexed = this.messageIndex.get(messageId);
198
+ if (indexed !== undefined)
199
+ sessionId = indexed;
200
+ }
201
+ // 显式携带即续接:missing/closed 双拒(已闭对齐 webui 404 语义——读面走 getEntries)
202
+ if (sessionId !== undefined) {
203
+ const state = this.deps.sessionStateOf(sessionId);
204
+ if (state === 'missing') {
205
+ this.emitError('SESSION_NOT_FOUND', `会话 ${sessionId} 不存在`, sessionId);
206
+ return;
207
+ }
208
+ if (state === 'closed') {
209
+ this.emitError('SESSION_CLOSED', `会话 ${sessionId} 已关闭(新发拒收——回放走 getEntries)`, sessionId);
210
+ return;
211
+ }
212
+ }
213
+ // admit 判定:连接内快速档(known)→ durable 档(lookupDedupeKey 跨重启幂等)
214
+ let sessionKnown = sessionId === undefined ? undefined : this.known.get(sessionId);
215
+ let verdict = admitMessage(sessionKnown ?? new Map(), messageId, content);
216
+ if (verdict.status === 'fresh' && sessionId !== undefined) {
217
+ const durable = this.deps.lookupDedupeKey(sessionId, messageId);
218
+ if (durable !== undefined) {
219
+ // durable 命中——种子进连接内账后按同键判定(后续快速档直达)
220
+ sessionKnown ??= new Map();
221
+ sessionKnown.set(messageId, durable);
222
+ this.known.set(sessionId, sessionKnown);
223
+ verdict = admitMessage(sessionKnown, messageId, content);
224
+ }
225
+ }
226
+ if (verdict.status === 'conflict') {
227
+ this.emitError('SDK_MESSAGE_CONFLICT', `messageId=${messageId} 同键异内容(幂等 admit 冲突档)`, sessionId);
228
+ return;
229
+ }
230
+ if (verdict.status === 'duplicate') {
231
+ const sid = sessionId;
232
+ this.messageIndex.set(messageId, sid);
233
+ this.emit({
234
+ kind: 'ack',
235
+ sessionId: sid,
236
+ messageId,
237
+ duplicate: true,
238
+ highWaterSeq: this.deps.highWaterOf(sid) ?? 0,
239
+ });
240
+ return;
241
+ }
242
+ // fresh:唯一写路径过桥(sessionId 缺席即新建——桥落定回示句柄)
243
+ const outcome = this.deps.submitPrompt({ sessionId, content, messageId });
244
+ const record = this.known.get(outcome.sessionId) ?? new Map();
245
+ record.set(messageId, content);
246
+ this.known.set(outcome.sessionId, record);
247
+ this.messageIndex.set(messageId, outcome.sessionId);
248
+ // 自动订阅(13b 落码定形):prompt 落定会话即挂直播(ack 携句柄 → 事件随后
249
+ // 即流;只直播不重放——重放走显式 hello.after)。既有订阅不覆写(hello 订阅
250
+ // 携 noDelta 等订阅参数胜出);新会话无未决 ask,onSubscribed 只对新挂者发。
251
+ if (!this.subs.has(outcome.sessionId)) {
252
+ this.subs.set(outcome.sessionId, {
253
+ noDelta: this.connectionNoDelta,
254
+ phase: 'live',
255
+ running: false,
256
+ runStartAt: 0,
257
+ lastElapsedMs: 0,
258
+ stage: null,
259
+ lastFrameAt: this.now(),
260
+ });
261
+ this.deps.onSubscribed?.(outcome.sessionId);
262
+ }
263
+ this.emit({
264
+ kind: 'ack',
265
+ sessionId: outcome.sessionId,
266
+ messageId,
267
+ duplicate: false,
268
+ routedChannel: outcome.routedChannel,
269
+ highWaterSeq: this.deps.highWaterOf(outcome.sessionId) ?? 0,
270
+ });
271
+ }
272
+ /** interrupt:受理经事件流可观察(turn/end reason='interrupted')——无应答帧 */
273
+ handleInterrupt(sessionId) {
274
+ if (this.deps.sessionStateOf(sessionId) === 'missing') {
275
+ this.emitError('SESSION_NOT_FOUND', `会话 ${sessionId} 不存在`, sessionId);
276
+ return;
277
+ }
278
+ this.deps.interruptSession(sessionId);
279
+ }
280
+ /** decide:跨入口审批竞速应答——applied/superseded 回执(03 §10.6 第三腿) */
281
+ handleDecide(approvalId, answer, note) {
282
+ const outcome = this.deps.decideApproval(approvalId, answer, note);
283
+ this.emit({ kind: 'decide-result', approvalId, outcome });
284
+ }
285
+ /** getEntries:对账读面(since 同 after 语义——05 §3.5 第一腿) */
286
+ handleGetEntries(sessionId, since, cursor) {
287
+ if (this.deps.sessionStateOf(sessionId) === 'missing') {
288
+ this.emitError('SESSION_NOT_FOUND', `会话 ${sessionId} 不存在`, sessionId);
289
+ return;
290
+ }
291
+ const highWater = this.deps.highWaterOf(sessionId) ?? 0;
292
+ const check = validateAfterCursor(since, highWater);
293
+ if (!check.ok) {
294
+ this.emitError('SDK_CURSOR_INVALID', `对账游标非法(${check.reason}):since=${since} 高水位=${highWater}`, sessionId);
295
+ return;
296
+ }
297
+ const page = this.deps.queryEntries(sessionId, since, cursor);
298
+ this.emit({ kind: 'entries', sessionId, entries: page.entries, nextCursor: page.nextCursor });
299
+ }
300
+ // ---------------------------------------------------------------------------
301
+ // 活体外推腿(13b-3 后端 onEnvelope → 此处;订阅过滤 + 水位 seq + 心跳账)
302
+ // ---------------------------------------------------------------------------
303
+ /**
304
+ * 活体事件推入(双轨律直播段唯一入口)。未订阅会话丢弃;noDelta 订阅剥
305
+ * message_update;seq = 推入时刻高水位(durable 水位语义——弱单调,协议
306
+ * 文注定形)。同步推入顺带推导心跳账(run 态/阶段)。
307
+ */
308
+ pushEvent(sessionId, event) {
309
+ if (this.closed)
310
+ return;
311
+ const sub = this.subs.get(sessionId);
312
+ if (sub === undefined || sub.phase !== 'live')
313
+ return;
314
+ this.trackHeartbeat(sub, event);
315
+ if (sub.noDelta && event.type === 'message_update')
316
+ return; // ① delta 缺省开、--no-delta 退订
317
+ this.emit({ kind: 'event', seq: this.deps.highWaterOf(sessionId) ?? 0, sessionId, event }, sessionId);
318
+ }
319
+ /**
320
+ * 非事件帧外推(批 13b-3 后端消费位——ask 审批外推帧的入口):独立帧族
321
+ * 不占 seq 不进 durable(03 §10.6 第三腿),直接过出站单点(背压/静默账
322
+ * 与事件帧同律)。订阅判据由调用方先决(后端 fail-closed 位)。
323
+ */
324
+ pushFrame(frame, sessionId) {
325
+ this.emit(frame, sessionId);
326
+ }
327
+ /** 订阅观测面(后端 fail-closed 判据——ask 帧去向位) */
328
+ isSubscribed(sessionId) {
329
+ return this.subs.get(sessionId)?.phase === 'live';
330
+ }
331
+ /**
332
+ * 退订(批 13e-2 HTTP 传输位:SSE 流全撤即退——ask fail-closed 判据与真
333
+ * 观众同步;未决 ask 不动〔重连 hello 即重推〕,admit 账不动〔连接级〕)。
334
+ * 退订后同会话再 hello/prompt 即重挂新订阅态。
335
+ */
336
+ unsubscribe(sessionId) {
337
+ this.subs.delete(sessionId);
338
+ }
339
+ /** 活跃订阅数(后端 hasAudience 探针——零订阅即无观众) */
340
+ get subscriptionCount() {
341
+ let count = 0;
342
+ for (const sub of this.subs.values())
343
+ if (sub.phase === 'live')
344
+ count++;
345
+ return count;
346
+ }
347
+ /** 心跳账推导(事件流 → runState/stage——非事件型 probe 走 tick 时查面) */
348
+ trackHeartbeat(sub, event) {
349
+ switch (event.type) {
350
+ case 'agent_start':
351
+ sub.running = true;
352
+ sub.runStartAt = this.now();
353
+ sub.stage = null;
354
+ break;
355
+ case 'agent_end':
356
+ sub.running = false;
357
+ sub.lastElapsedMs = Math.max(0, this.now() - sub.runStartAt);
358
+ sub.stage = null;
359
+ break;
360
+ case 'message_start':
361
+ case 'message_update':
362
+ sub.stage = { type: 'thinking' };
363
+ break;
364
+ case 'tool_execution_start':
365
+ sub.stage = { type: 'tool', name: event.name, sinceMs: this.now() };
366
+ break;
367
+ case 'tool_execution_end':
368
+ sub.stage = null; // 工具间隙(下一 message_start/tool_execution_start 再立)
369
+ break;
370
+ default:
371
+ break; // turn_* 不改阶段账
372
+ }
373
+ }
374
+ /**
375
+ * 心跳拍(② 协议义务的装配驱动位——宿主定间隔调)。静默填充式:会话级
376
+ * 最近一帧距今超阈才发(活跃流自证活性,心跳只填间隙);阶段优先 retry
377
+ * probe(驱动退避等待期——事件流不可推导的非事件型源)。
378
+ */
379
+ heartbeatTick() {
380
+ if (this.closed)
381
+ return;
382
+ const now = this.now();
383
+ for (const [sessionId, sub] of this.subs) {
384
+ if (sub.phase !== 'live' || now - sub.lastFrameAt < this.heartbeatIntervalMs)
385
+ continue;
386
+ const probe = this.deps.retryProbeOf(sessionId);
387
+ const stage = probe !== null && probe.nextAt !== null
388
+ ? { type: 'retry', probe }
389
+ : sub.stage === null
390
+ ? null
391
+ : sub.stage.type === 'tool'
392
+ ? { type: 'tool', name: sub.stage.name, stageElapsedMs: Math.max(0, now - sub.stage.sinceMs) }
393
+ : { type: 'thinking' };
394
+ const elapsedMs = sub.running ? Math.max(0, now - sub.runStartAt) : sub.lastElapsedMs;
395
+ this.emit({ kind: 'heartbeat', sessionId, runState: sub.running ? 'running' : 'idle', stage, elapsedMs }, sessionId);
396
+ }
397
+ }
398
+ // ---------------------------------------------------------------------------
399
+ // 出站(emit + ⑦ 背压执法)与生命周期间隙
400
+ // ---------------------------------------------------------------------------
401
+ /** 出站单点:入有界队列;过载即断连档(SDK_OVERLOADED 尽力直写 + 宿主关传输) */
402
+ emit(frame, sessionId) {
403
+ if (this.closed)
404
+ return;
405
+ const status = this.queue.enqueue(frame);
406
+ if (sessionId !== undefined) {
407
+ const sub = this.subs.get(sessionId);
408
+ if (sub !== undefined)
409
+ sub.lastFrameAt = this.now(); // 任何携会话帧都刷新静默账
410
+ }
411
+ if (status !== 'overload')
412
+ return;
413
+ // ⑦ 线控帧不可丢 ⇒ 过载断连:错误帧绕队列尽力直写,钩宿主关传输
414
+ this.closed = true;
415
+ this.closedReason = 'SDK_OVERLOADED';
416
+ this.deps.sink.write({
417
+ kind: 'error',
418
+ code: 'SDK_OVERLOADED',
419
+ message: `出站队列溢出(容量 ${this.queue.capacity})——线控帧不可丢,断连档`,
420
+ willRetry: true,
421
+ retryAfterMs: this.overloadRetryAfterMs,
422
+ });
423
+ this.deps.onOverload?.(this.overloadRetryAfterMs);
424
+ }
425
+ emitError(code, message, sessionId) {
426
+ this.emit({ kind: 'error', sessionId, code, message }, sessionId);
427
+ }
428
+ closedReason;
429
+ /** 闭合观测面(13e HTTP 传输位:闭合面新请求直接 503——不留半开档) */
430
+ get isClosed() {
431
+ return this.closed;
432
+ }
433
+ /** 宿主 writable 时冲刷在队帧(背压恢复腿) */
434
+ drain() {
435
+ return this.queue.drain();
436
+ }
437
+ /** 出站队列观测面(dropped 计数——宿主日志/指标) */
438
+ get droppedFrameCount() {
439
+ return this.queue.droppedCount;
440
+ }
441
+ /** 连接收口(宿主关传输时调——清订阅,后续 push/请求全静默) */
442
+ close() {
443
+ this.closed = true;
444
+ this.subs.clear();
445
+ }
446
+ }