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,38 @@
1
+ /** process stdin/stdout 直连适配器(组合根缺省注入位) */
2
+ export class ProcessTerminalIO {
3
+ write(data) {
4
+ process.stdout.write(data);
5
+ }
6
+ size() {
7
+ // 兜底用 || 而非 ??:pty 未设 winsize 时(expect/CI 伪终端)node 返回
8
+ // rows/columns = 0 而非 undefined——`??` 只兜 null/undefined 兜不住 0;
9
+ // 0 行 0 列的终端物理不存在,零值与缺席同属「无有效几何」,一律回落
10
+ // 80×24(2026-09-13 真模型五轮实测定罪:0 值直透曾致 CellGrid 零宽
11
+ // 只擦不写 + 负行号 CUP——固定区渲染全空)。
12
+ return { columns: process.stdout.columns || 80, rows: process.stdout.rows || 24 };
13
+ }
14
+ setRawMode(enable) {
15
+ if (process.stdin.isTTY)
16
+ process.stdin.setRawMode(enable);
17
+ }
18
+ isRaw() {
19
+ return process.stdin.isRaw ?? false;
20
+ }
21
+ pause() {
22
+ process.stdin.pause();
23
+ }
24
+ resume() {
25
+ process.stdin.resume();
26
+ }
27
+ onInput(listener) {
28
+ const stdin = process.stdin;
29
+ stdin.setEncoding('utf8');
30
+ stdin.on('data', listener);
31
+ return () => stdin.removeListener('data', listener);
32
+ }
33
+ onResize(listener) {
34
+ const stdout = process.stdout;
35
+ stdout.on('resize', listener);
36
+ return () => stdout.removeListener('resize', listener);
37
+ }
38
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * 自研 TUI 引擎契约件(07 篇 §4.1 自研引擎节件 1——批 10b 引擎核心)。
3
+ *
4
+ * 引擎与真 TTY 全解耦的接缝根基:TerminalIO 是注入面(按引擎生命周期需要
5
+ * 设计接口面,非照抄进程 API)、MemoryTerminalIO 内存实现是引擎公开测试面
6
+ * (结构性义务非可选件——「mock 只停在模型层」纪律的接缝根基)。
7
+ *
8
+ * 本文件只立契约(类型 + 协议),零实现、零外部依赖;实现可替换性由接口
9
+ * 面承载(CellBuffer 协议下实装在 cell 件、TerminalIO 下真终端适配在批 10c)。
10
+ */
11
+ /** 16 色色号构造(brand 收口:色号只能经此进入引擎面) */
12
+ export function ansiColor(n) {
13
+ if (!Number.isInteger(n) || n < 0 || n > 15) {
14
+ throw new Error(`ansiColor: 色号须为 0-15 整数,收到 ${n}`);
15
+ }
16
+ return n;
17
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * 文本度量件(07 篇 §4.1 自研引擎节件 2——批 10b 引擎核心)。
3
+ *
4
+ * 字素边界与列宽两件必须同时在场(立法缘由:berry 注释点名 OpenCode 反课
5
+ * 「只用字素切分不管列宽 → CJK 断行错位至今未修」)——
6
+ * - 字素边界:运行时内建 Intl.Segmenter(零新依赖,模块级单例);
7
+ * - 列宽:EAW 分类**数据面**外包 get-east-asian-width(§2.1 精确锁 1.6.0),
8
+ * `ambiguous = 1` 的中西混排语境取舍**决策面自持**在本件(见 eawWidth 注释)。
9
+ */
10
+ import { eastAsianWidthType } from 'get-east-asian-width';
11
+ /** 字素切分器(模块级单例——Segmenter 构造贵、无状态可复用) */
12
+ const segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
13
+ /** 变体选择子 16(emoji 呈现形——字素含之即宽 2) */
14
+ const VS16 = 0xfe0f;
15
+ /** Regional Indicator 起止(旗帜 = 恰一对 RI 合成一字素) */
16
+ const RI_FIRST = 0x1f1e6;
17
+ const RI_LAST = 0x1f1ff;
18
+ /**
19
+ * EAW 单码点分类宽(决策面自持位)。
20
+ *
21
+ * ambiguous(A 类:〇 ⊡ ⛭ …)在 CJK 语境常按 2 列渲染、西文语境按 1 列——
22
+ * 本仓取舍 **= 1**(中西混排对话正文按窄计),依据 = berry 实证同款取舍;
23
+ * 部分按 2 渲染的 CJK 终端上对齐误差可容忍,可配置性挂真实需求再裁
24
+ * (07 §4.1 终端支持矩阵降级三款注记在案)。
25
+ * 注:外包库的 eastAsianWidth() 缺省 ambiguousAsWide = true——与本取舍相反,
26
+ * 故只取其分类函数、宽度判定自持,不走其合成宽函数。
27
+ */
28
+ function eawWidth(codePoint) {
29
+ const type = eastAsianWidthType(codePoint);
30
+ return type === 'wide' || type === 'fullwidth' ? 2 : 1;
31
+ }
32
+ /**
33
+ * 字素级宽度四规则(07 引擎节件 2):
34
+ * ① 字素内任一码点 EAW wide/fullwidth → 2;
35
+ * ② 含 VS16 → 2(emoji 呈现形——窄基字符 + VS16 也占双列);
36
+ * ③ 恰一对 Regional Indicator → 2(旗帜——单码点 EAW 覆盖不到,孤立 RI 按 1);
37
+ * ④ 其余 → 1。
38
+ */
39
+ export function graphemeWidth(grapheme) {
40
+ const codePoints = [...grapheme].map((ch) => ch.codePointAt(0));
41
+ // 规则③:恰一对 RI(两面旗恰好两个 RI 码点合成)判 2;单 RI / 三连 RI 落规则④
42
+ const riCount = codePoints.filter((cp) => cp >= RI_FIRST && cp <= RI_LAST).length;
43
+ if (codePoints.length === 2 && riCount === 2)
44
+ return 2;
45
+ // 规则②:VS16 在场即宽(emoji 呈现形语义优先于基字符分类)
46
+ if (codePoints.includes(VS16))
47
+ return 2;
48
+ // 规则①:任一码点 wide/fullwidth → 2
49
+ for (const cp of codePoints) {
50
+ if (eawWidth(cp) === 2)
51
+ return 2;
52
+ }
53
+ // 规则④:其余 → 1(含控制字符的占位语义不在此件发明——网格写入层拒收)
54
+ return 1;
55
+ }
56
+ /** 切分字素序列(Intl.Segmenter——ZWJ 家族 / 旗帜 / 变体选择子整素不撕裂) */
57
+ export function splitGraphemes(text) {
58
+ if (text === '')
59
+ return [];
60
+ const out = [];
61
+ for (const seg of segmenter.segment(text))
62
+ out.push(seg.segment);
63
+ return out;
64
+ }
65
+ /** 整字三原语之一·测宽:全串字素宽度和 */
66
+ export function stringWidth(text) {
67
+ let width = 0;
68
+ for (const g of splitGraphemes(text))
69
+ width += graphemeWidth(g);
70
+ return width;
71
+ }
72
+ /**
73
+ * 整字三原语之二·按宽截断:截到不超过 cols 的最长字素前缀——
74
+ * 宽字素跨界整字丢弃**不产半字**(末位恰剩一列放不下双宽字素时整字放弃)。
75
+ */
76
+ export function truncateToWidth(text, cols) {
77
+ if (cols <= 0)
78
+ return '';
79
+ let used = 0;
80
+ let out = '';
81
+ for (const g of splitGraphemes(text)) {
82
+ const w = graphemeWidth(g);
83
+ if (used + w > cols)
84
+ break; // 剩余列宽不足整字——整字丢弃(不产半字)
85
+ used += w;
86
+ out += g;
87
+ }
88
+ return out;
89
+ }
90
+ /**
91
+ * 整字三原语之三·整字换行:按 cols 折行——
92
+ * 行末剩一列遇双宽字素整字下移**第二列不悬挂**;ZWJ 家族**跨行不撕裂**
93
+ * (字素切分在前、折行只动字素边界);显式 \n 强制换行(段语义保留)。
94
+ */
95
+ export function wrapText(text, cols) {
96
+ if (cols <= 0)
97
+ return [text]; // 非法宽防御:不折行整段返回(坏参不丢字)
98
+ const lines = [];
99
+ // 先按显式换行分段(空段保留——空行语义),段内再按宽折行
100
+ for (const paragraph of text.split('\n')) {
101
+ if (paragraph === '') {
102
+ lines.push('');
103
+ continue;
104
+ }
105
+ let current = '';
106
+ let used = 0;
107
+ for (const g of splitGraphemes(paragraph)) {
108
+ const w = graphemeWidth(g);
109
+ if (used + w > cols) {
110
+ lines.push(current); // 当前行满——整字下移开新行(双宽字不悬挂第二列)
111
+ current = g;
112
+ used = w;
113
+ }
114
+ else {
115
+ current += g;
116
+ used += w;
117
+ }
118
+ }
119
+ lines.push(current); // 段尾行(含整段未折情形)
120
+ }
121
+ return lines;
122
+ }
@@ -0,0 +1,15 @@
1
+ export { CommandRegistry, tokenize } from './commands.js';
2
+ export { AskQueue } from './ask-queue.js';
3
+ export { UiCore } from './ui-core.js';
4
+ export { SessionChannels } from './registry.js';
5
+ export { createChannels } from './service.js';
6
+ export { TuiBackend } from './tui/index.js';
7
+ // 真终端适配器 + 补全源族(批 12e host 装配消费——engine/·tui/ 组件族非公开
8
+ // 面纪律的件外出口位:「本面随真实件外消费扩」先例;余组件仍件内消费)
9
+ export { ProcessTerminalIO } from './engine/index.js';
10
+ export { FileMentionSource } from './tui/index.js';
11
+ // SDK 线协议(批 13a 契约先行——03 §10.6 件身份条:协议核心代码位与 channels
12
+ // 通道核同体,SDK 通道后端 = UiBackend 第三后端;本面出协议词汇/信封 + NDJSON
13
+ // 编解码 + admit/游标纯逻辑四件。后端实装(事件外推/请求受理/出站队列)随批
14
+ // 13b;stdio 传输归宿主 serve、HTTP+SSE/MCP 归 core:sdk 件〔src/sdk/ 13e〕)
15
+ export * from './sdk/index.js';
@@ -0,0 +1,56 @@
1
+ /**
2
+ * 多会话注册表与焦点编排(07 §4.1 通道契约——2026-09-06 channels 纵切批)。
3
+ *
4
+ * 职责:会话在场集合 + 焦点位(聚焦者全渲染/非聚焦摘要行的判定源)+
5
+ * 焦点切换清屏重画(拉投影 → repaint 回调——投影拉取经装配注入,
6
+ * channels 不 import session 是 02 §4.1 边表的刻意设计)。
7
+ *
8
+ * 焦点切换不自动回退:聚焦会话注销后焦点空悬(focused=null——非聚焦态
9
+ * 全体),下一焦点由用户动作显式给(切焦编舞归 TUI 呈现批)。
10
+ */
11
+ export class SessionChannels {
12
+ fetchProjection;
13
+ repaint;
14
+ sessions = new Set();
15
+ currentFocus = null;
16
+ constructor(fetchProjection, repaint) {
17
+ this.fetchProjection = fetchProjection;
18
+ this.repaint = repaint;
19
+ }
20
+ /** 会话入场(幂等;焦点不自动变更——显式 focus 才切) */
21
+ registerSession(sessionId) {
22
+ this.sessions.add(sessionId);
23
+ }
24
+ /** 会话退场:在场集移除;若正是聚焦者 → 焦点空悬(不自动接续) */
25
+ unregisterSession(sessionId) {
26
+ this.sessions.delete(sessionId);
27
+ if (this.currentFocus === sessionId)
28
+ this.currentFocus = null;
29
+ }
30
+ /** 当前聚焦会话(null = 焦点空悬——全体非聚焦态) */
31
+ get focusedId() {
32
+ return this.currentFocus;
33
+ }
34
+ isFocused(sessionId) {
35
+ return this.currentFocus === sessionId;
36
+ }
37
+ /** 在场判定(emit 路由前置——不在场会话的信封仍可分流,呈现裁剪归后端) */
38
+ has(sessionId) {
39
+ return this.sessions.has(sessionId);
40
+ }
41
+ /**
42
+ * 焦点切换(07 §4.1:清屏重画):置位 → 拉投影 → repaint。
43
+ * 投影拉取缺席 → 空投影重画(诚实缺席:装配缺真源是可观察态,不静默假装
44
+ * 有历史);拉取异常向上抛(fail-loud——投影真源故障不降级为「无历史」谎言)。
45
+ * 未注册会话 focus 视同注册(焦点即活跃声明——幂等)。
46
+ */
47
+ async focus(sessionId) {
48
+ this.registerSession(sessionId);
49
+ this.currentFocus = sessionId;
50
+ const projection = await (this.fetchProjection?.(sessionId) ?? []);
51
+ // 焦点可能在拉投影期间被再切(快速切焦)——只为本焦点落画(迟到的旧画弃)
52
+ if (this.currentFocus === sessionId) {
53
+ this.repaint(sessionId, projection);
54
+ }
55
+ }
56
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * 幂等 admit 判定纯函数(03 篇 §10.6 线协议七原则④ + 05 篇 §3.5 第二腿——
3
+ * durable 承载条)。
4
+ *
5
+ * **两词一字段两面**(05 §3.5):线面 `messageId`(03 §10.6 请求面动词族条)
6
+ * 受理时即落账为 durable `data.dedupeKey`——单键直写、无映射表、无变换。
7
+ * 本函数以 messageId 为键直查既存表(键域同一),语义真源在规范、查重实现
8
+ * (日志扫描 / 投影侧索引)随受理实装(批 13b)定形。
9
+ *
10
+ * 语义三档:fresh(无同键——受理新跑)/ duplicate(同键同内容——幂等重收执,
11
+ * 回执即既存事件本身、经 after 重放可取、不重跑)/ conflict(同键异内容——
12
+ * `SDK_MESSAGE_CONFLICT`,误判超时重发零副作用的反面执法位)。跨重启幂等
13
+ * 天然成立(durable 即真相——无第二套 admit 存储表)。
14
+ */
15
+ /**
16
+ * 判定一条 prompt 的受理档。
17
+ *
18
+ * @param known 既存 dedupeKey → 内容 映射(受理侧维护;跨重启语义由 durable
19
+ * 落账兑现——本函数纯逻辑不触存储)
20
+ * @param messageId 调用方自选幂等键(受理时即落账为 data.dedupeKey——两词一字段两面)
21
+ * @param content 消息内容(同键异内容的比对基准——字符串全等,无归一化)
22
+ */
23
+ export function admitMessage(known, messageId, content) {
24
+ const existing = known.get(messageId);
25
+ if (existing === undefined)
26
+ return { status: 'fresh' };
27
+ // 同键:内容全等分档——异内容即冲突(fail-loud 不静默覆盖)
28
+ return existing === content ? { status: 'duplicate' } : { status: 'conflict', code: 'SDK_MESSAGE_CONFLICT' };
29
+ }
@@ -0,0 +1,102 @@
1
+ import { SdkWireCore } from './wire-core.js';
2
+ /**
3
+ * 造 SDK 通道后端(每连接一枚——core 即该连接的单连接状态机)。
4
+ *
5
+ * @param deps 装配桥注入面(decideApproval/onSubscribed 两面由本件自持——
6
+ * 幂等账与重推编舞是后端语义不是桥语义)
7
+ * @param options 线核构造选项(时钟/节拍/背压参——透传)
8
+ */
9
+ export function createSdkBackend(deps, options) {
10
+ /** 未决 ask 账(approvalId → 账项——decide 幂等判据 + 重推面) */
11
+ const pending = new Map();
12
+ /** 后端内 ask 计数(调用方未指派 approvalId 时生成 `sdk-N`——连接内唯一) */
13
+ let askSeq = 0;
14
+ const core = new SdkWireCore({
15
+ ...deps,
16
+ // decide 应答:未决账项在册且未决 → 落定回执 applied;unknown/已决 →
17
+ // superseded(跨入口竞速败者——03 §10.6 第三腿幂等语义)
18
+ decideApproval: (approvalId, answer) => {
19
+ const entry = pending.get(approvalId);
20
+ if (entry === undefined || entry.settled)
21
+ return 'superseded';
22
+ entry.settled = true;
23
+ pending.delete(approvalId);
24
+ entry.resolve(answer);
25
+ return 'applied';
26
+ },
27
+ // 订阅受理钩:该会话未决 ask 全量重推(重连后凭 hello 重收 ask 面;
28
+ // 已决者早已出账——重推面天然只含未决)
29
+ onSubscribed: (sessionId) => {
30
+ for (const entry of pending.values()) {
31
+ if (entry.frame.sessionId === sessionId)
32
+ core.pushFrame(entry.frame, sessionId);
33
+ }
34
+ },
35
+ }, options);
36
+ const backend = {
37
+ id: 'sdk',
38
+ // 唯 approval 一位(文件头能力面注)——其余 false 使核降级判定不路由本后端
39
+ capabilities: {
40
+ notify: false,
41
+ confirm: false,
42
+ select: false,
43
+ input: false,
44
+ approval: true,
45
+ setStatus: false,
46
+ setWidget: false,
47
+ },
48
+ // 观众探针:活跃订阅数(零订阅 = 无人接帧 = 无观众)
49
+ hasAudience: () => core.subscriptionCount > 0,
50
+ // 能力位 false——核不扇出本后端(方法在场纯为接口完备)
51
+ notify: () => { },
52
+ // 活体信封直推直播腿(focused 无 SDK 语义——订阅即全量,无聚焦降档)
53
+ onEnvelope: (env) => core.pushEvent(env.sessionId, env.event),
54
+ askApproval: (sessionId, request, opts) => new Promise((resolve) => {
55
+ // fail-closed:该会话无订阅者即结构性无人可答——立即 unavailable
56
+ //(帧不出站;04 §9 无应答者语义,非用户主动取消——与 abort 路分立)
57
+ if (!core.isSubscribed(sessionId)) {
58
+ resolve('unavailable');
59
+ return;
60
+ }
61
+ const approvalId = request.approvalId ?? `sdk-${++askSeq}`;
62
+ const frame = {
63
+ kind: 'ask',
64
+ sessionId,
65
+ approvalId,
66
+ summary: request.summary,
67
+ ...(request.reason !== undefined ? { reason: request.reason } : {}),
68
+ ...(request.toolName !== undefined ? { toolName: request.toolName } : {}),
69
+ ...(request.suggestedEntry !== undefined ? { suggestedEntry: request.suggestedEntry } : {}),
70
+ };
71
+ const entry = { frame, settled: false, resolve };
72
+ pending.set(approvalId, entry);
73
+ core.pushFrame(frame, sessionId);
74
+ // 败腿撤销(UiCore 竞速先答后 abort 传播位):保守值 cancel(04 §9
75
+ // run 信号透传——用户主动打断语义,不落 unavailable)+ 出账(迟到
76
+ // decide 归 superseded);已决后迟到 abort 是无害 no-op
77
+ opts?.signal?.addEventListener('abort', () => {
78
+ if (entry.settled)
79
+ return;
80
+ entry.settled = true;
81
+ pending.delete(approvalId);
82
+ resolve('cancel');
83
+ }, { once: true });
84
+ }),
85
+ };
86
+ return {
87
+ backend,
88
+ core,
89
+ dispose: () => {
90
+ // 连接收口:在飞 ask 按通道消失语义收场(unavailable——连接断了无观众
91
+ // 可答,ApprovalService 落 timeout 源;用户主动打断走 abort 路 cancel)
92
+ for (const entry of pending.values()) {
93
+ if (entry.settled)
94
+ continue;
95
+ entry.settled = true;
96
+ entry.resolve('unavailable');
97
+ }
98
+ pending.clear();
99
+ core.close();
100
+ },
101
+ };
102
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * after 游标对账纯函数(03 篇 §10.6 线协议七原则③ + 05 篇 §3.5 第一腿——
3
+ * durable seq + after 游标 / 崩溃重连对账 / 重放-直播衔接序)。
4
+ *
5
+ * 高水位约定(与 ./protocol.ts 文件头单源同律):**highWaterSeq = 会话日志当前
6
+ * 长度**(= 下一将分配 seq;既存末条 seq = highWaterSeq − 1;空会话 = 0)。
7
+ *
8
+ * 两执法句在该约定下的形态:
9
+ * - 调用方已收末 seq ≥ 高水位 ⇒ 尾被截(撕裂尾截断后 seq 按 log.length 重编
10
+ * **复用**——调用方已收的尾段 seq 在恢复后日志中不存在或被同号异容复用);
11
+ * - `after=N` ≥ 高水位 ⇒ 游标非法(N 越过既存末条 = 声称已收不存在的事件;
12
+ * 含空会话 after=0 档——新会话订阅应缺席 after)。
13
+ * 两档同码 `SDK_CURSOR_INVALID`,调用方从头(或 getEntries)重对账——消费侧
14
+ * 幂等以 dedupeKey 兜底,at-least-once 边界如实。
15
+ */
16
+ /**
17
+ * 校验订阅/重放游标 after 的合法性(hello.after 与 getEntries.since 同语义)。
18
+ *
19
+ * 合法 ⇔ after < highWaterSeq(重放窗口 = (after, highWaterSeq];after 恰为
20
+ * 既存末条 seq = 完全追平档——窗口空、直接进直播,亦合法)。
21
+ */
22
+ export function validateAfterCursor(after, highWaterSeq) {
23
+ if (after >= highWaterSeq) {
24
+ return { ok: false, code: 'SDK_CURSOR_INVALID', reason: 'beyond-high-water' };
25
+ }
26
+ return { ok: true };
27
+ }
28
+ /**
29
+ * 崩溃重连尾截断侦测(05 §3.5 崩溃重连对账条——hello/ack 携高水位的消费侧)。
30
+ *
31
+ * @param lastReceivedSeq 调用方已收末条 seq(无对账面〔首连〕传 undefined——恒 ok)
32
+ * @param highWaterSeq 服务端 hello/ack 携带的会话当前高水位
33
+ * @returns 已收末 seq ≥ 高水位 ⇒ 尾被截(含 ==:声称知道下一将分配 seq 同为
34
+ * 幻影——撕裂截断后 seq 复用使等值也歧义,保守同码)
35
+ */
36
+ export function detectTailTruncation(lastReceivedSeq, highWaterSeq) {
37
+ if (lastReceivedSeq === undefined)
38
+ return { ok: true };
39
+ if (lastReceivedSeq >= highWaterSeq) {
40
+ return { ok: false, code: 'SDK_CURSOR_INVALID', reason: 'tail-truncated' };
41
+ }
42
+ return { ok: true };
43
+ }
44
+ /**
45
+ * 重放-直播衔接序定位(05 §3.5 衔接序第 ③ 步——**落码回归锁位**函数化):
46
+ * 直播起播 = 内存日志中 seq > 重放尾(lastReplayedSeq)的首条(含未落库在飞
47
+ * 事件)。重放尾与直播首无缝衔接 = 衔接完整性验收位,测试在册。
48
+ *
49
+ * @param log 会话内存日志(seq 单调连续升序——05 §1.1 写入序不变式)
50
+ * @param lastReplayedSeq 重放段末条 seq(空重放 = after 原值或 −1)
51
+ * @returns 直播起播下标(log.length = 重放已覆盖全部内存日志——直播等新事件)
52
+ */
53
+ export function resolveLiveStart(log, lastReplayedSeq) {
54
+ const index = log.findIndex((entry) => entry.seq > lastReplayedSeq);
55
+ return index === -1 ? log.length : index;
56
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * SDK 通道后端件内聚合面(channels 通道核第三后端——03 篇 §10.6 件身份条:
3
+ * 协议核心代码位与 channels 通道核同体,UiBackend 多后端接口位既有)。
4
+ *
5
+ * 批 13a 契约先行笔 = 协议词汇/信封 + NDJSON 编解码 + admit/游标纯逻辑;
6
+ * 批 13b = 线协议核心(wire-core 单连接状态机:六动词受理/活体外推/心跳
7
+ * 装配面/出站有界队列背压)+ 后端实装(13b-3)。stdio 传输归宿主 serve
8
+ * (07 §5);HTTP+SSE 与 MCP 包装归 core:sdk 件(src/sdk/ 批 13e)。
9
+ *
10
+ * SDK_ 五码注册在 contracts 错误码注册表 CORE_ERROR_CODES(规范定名码直入
11
+ * contracts 的仓内先例——CHANNEL_/AGENT_ 同款;注册笔系并行会话〔批 12 泳道〕
12
+ * 先行落、本批收编,勿在本件重复注册)。
13
+ */
14
+ export * from './protocol.js';
15
+ export * from './jsonl.js';
16
+ export * from './admit.js';
17
+ export * from './cursor.js';
18
+ export * from './schema.js';
19
+ export * from './wire-core.js';
20
+ export * from './backend.js';
@@ -0,0 +1,208 @@
1
+ import { SDK_FRAME_KINDS, SDK_REQUEST_VERBS } from './protocol.js';
2
+ import { validateSdkRequest } from './schema.js';
3
+ /** 行终止符两位(编码转义对象——显式转义写法防字面量在编辑/传输中丢失) */
4
+ const LS = '\u2028';
5
+ const PS = '\u2029';
6
+ /** 判别字段的受控名(请求 `verb` / 线帧 `kind`) */
7
+ const FRAME_KIND_SET = new Set(SDK_FRAME_KINDS);
8
+ const REQUEST_VERB_SET = new Set(SDK_REQUEST_VERBS);
9
+ /** decide.answer 四值闭集(contracts ApprovalAskAnswer 减 'unavailable'——结构性
10
+ * 自报值非通道答案,SDK 通道面不受理;2026-09-13 复盘发现 #28:修前注称「同集」
11
+ * 在 unavailable 入集后失真) */
12
+ const APPROVAL_ANSWERS = new Set(['approve', 'reject', 'cancel', 'always']);
13
+ /** decide-result.outcome 闭集(跨入口竞速回执两档) */
14
+ const DECIDE_OUTCOMES = new Set(['applied', 'superseded']);
15
+ /** 解码失败(fail-loud——坏行不产半帧;线面应答形〔错误帧映射〕归宿主 serve 装配批) */
16
+ export class SdkDecodeError extends Error {
17
+ rawLine;
18
+ constructor(message,
19
+ /** 原始行文本(审计面——截断到 200 字符防日志洪水) */
20
+ rawLine) {
21
+ super(`SDK 线解码失败:${message}`);
22
+ this.rawLine = rawLine;
23
+ this.name = 'SdkDecodeError';
24
+ }
25
+ }
26
+ /**
27
+ * 编码单帧为一行(含行尾 `\n`)。出站纪律:序列化 + U+2028/U+2029 转义——
28
+ * 返回值保证恰一个 `\n` 且在末尾(行安全锁,测试在册)。
29
+ */
30
+ export function encodeWireLine(frame) {
31
+ const json = JSON.stringify(frame).replaceAll(LS, '\\u2028').replaceAll(PS, '\\u2029');
32
+ return `${json}\n`;
33
+ }
34
+ /** 值判别:请求(verb 判别字段在闭集内)——非抛型窄卫(消费面路由用) */
35
+ export function isSdkRequest(value) {
36
+ return (typeof value === 'object' &&
37
+ value !== null &&
38
+ 'verb' in value &&
39
+ typeof value.verb === 'string' &&
40
+ REQUEST_VERB_SET.has(value.verb));
41
+ }
42
+ /** 值判别:线帧(kind 判别字段在闭集内)——非抛型窄卫 */
43
+ export function isSdkFrame(value) {
44
+ return (typeof value === 'object' &&
45
+ value !== null &&
46
+ 'kind' in value &&
47
+ typeof value.kind === 'string' &&
48
+ FRAME_KIND_SET.has(value.kind));
49
+ }
50
+ /** 结构校验:非对象 / 判别字段缺席 / 判别值不在闭集 —— 各自独立报因 */
51
+ function classify(value, rawLine) {
52
+ if (typeof value !== 'object' || value === null) {
53
+ throw new SdkDecodeError('非 JSON 对象', rawLine);
54
+ }
55
+ const v = value;
56
+ if (typeof v.verb === 'string' && REQUEST_VERB_SET.has(v.verb))
57
+ return 'request';
58
+ if (typeof v.kind === 'string' && FRAME_KIND_SET.has(v.kind))
59
+ return 'frame';
60
+ if (v.verb !== undefined || v.kind !== undefined) {
61
+ throw new SdkDecodeError(`判别值不识别(verb=${String(v.verb)} kind=${String(v.kind)}——闭集外)`, rawLine);
62
+ }
63
+ throw new SdkDecodeError('判别字段缺席(verb/kind 均无)', rawLine);
64
+ }
65
+ /** 标量必填断言(结构性校验——深校验挂账见文件头) */
66
+ function requireString(shape, key, rawLine) {
67
+ const v = shape[key];
68
+ if (typeof v !== 'string')
69
+ throw new SdkDecodeError(`${key} 必填且须为 string`, rawLine);
70
+ return v;
71
+ }
72
+ function requireNumber(shape, key, rawLine) {
73
+ const v = shape[key];
74
+ if (typeof v !== 'number' || !Number.isFinite(v)) {
75
+ throw new SdkDecodeError(`${key} 必填且须为有限数`, rawLine);
76
+ }
77
+ return v;
78
+ }
79
+ /** 逐请求动词的结构性必填校验(六动词族——03 §10.6 收窄律闭集) */
80
+ function validateRequest(shape, verb, rawLine) {
81
+ switch (verb) {
82
+ case 'hello':
83
+ requireNumber(shape, 'protocolVersion', rawLine);
84
+ break;
85
+ case 'prompt':
86
+ requireString(shape, 'messageId', rawLine);
87
+ requireString(shape, 'content', rawLine);
88
+ break;
89
+ case 'interrupt':
90
+ requireString(shape, 'sessionId', rawLine);
91
+ break;
92
+ case 'getEntries':
93
+ requireString(shape, 'sessionId', rawLine);
94
+ requireNumber(shape, 'since', rawLine);
95
+ break;
96
+ case 'decide':
97
+ requireString(shape, 'approvalId', rawLine);
98
+ if (!APPROVAL_ANSWERS.has(shape.answer)) {
99
+ throw new SdkDecodeError('answer 必填且须为 approve/reject/cancel/always 四值闭集', rawLine);
100
+ }
101
+ break;
102
+ case 'sessions':
103
+ break;
104
+ }
105
+ }
106
+ /** 逐线帧 kind 的结构性必填校验 */
107
+ function validateFrame(shape, kind, rawLine) {
108
+ switch (kind) {
109
+ case 'event':
110
+ requireNumber(shape, 'seq', rawLine);
111
+ requireString(shape, 'sessionId', rawLine);
112
+ if (typeof shape.event !== 'object' ||
113
+ shape.event === null ||
114
+ typeof shape.event.type !== 'string') {
115
+ throw new SdkDecodeError('event 必填且须携 string type(04 §2 十型判别)', rawLine);
116
+ }
117
+ break;
118
+ case 'hello':
119
+ requireString(shape, 'sessionId', rawLine);
120
+ requireNumber(shape, 'protocolVersion', rawLine);
121
+ requireNumber(shape, 'highWaterSeq', rawLine);
122
+ break;
123
+ case 'heartbeat':
124
+ requireString(shape, 'sessionId', rawLine);
125
+ if (shape.runState !== 'idle' && shape.runState !== 'running') {
126
+ throw new SdkDecodeError('runState 必填且须为 idle/running 两值闭集', rawLine);
127
+ }
128
+ requireNumber(shape, 'elapsedMs', rawLine);
129
+ break;
130
+ case 'ack':
131
+ requireString(shape, 'sessionId', rawLine);
132
+ requireString(shape, 'messageId', rawLine);
133
+ if (typeof shape.duplicate !== 'boolean') {
134
+ throw new SdkDecodeError('duplicate 必填且须为 boolean', rawLine);
135
+ }
136
+ requireNumber(shape, 'highWaterSeq', rawLine);
137
+ break;
138
+ case 'replay-end':
139
+ requireString(shape, 'sessionId', rawLine);
140
+ requireNumber(shape, 'lastReplayedSeq', rawLine);
141
+ break;
142
+ case 'entries':
143
+ requireString(shape, 'sessionId', rawLine);
144
+ break;
145
+ case 'decide-result':
146
+ requireString(shape, 'approvalId', rawLine);
147
+ if (!DECIDE_OUTCOMES.has(shape.outcome)) {
148
+ throw new SdkDecodeError('outcome 必填且须为 applied/superseded 两值闭集', rawLine);
149
+ }
150
+ break;
151
+ case 'ask':
152
+ // 审批外推帧恰三必填标量(reason/toolName/suggestedEntry 可选呈现位不校验)
153
+ requireString(shape, 'sessionId', rawLine);
154
+ requireString(shape, 'approvalId', rawLine);
155
+ requireString(shape, 'summary', rawLine);
156
+ break;
157
+ case 'sessions':
158
+ break;
159
+ case 'error':
160
+ requireString(shape, 'code', rawLine);
161
+ requireString(shape, 'message', rawLine);
162
+ break;
163
+ }
164
+ }
165
+ /**
166
+ * 解码单行为请求或线帧(fail-loud:非 JSON / 判别缺席 / 必填缺失各自报因,
167
+ * 坏行不产半帧)。行尾 `\r` 容忍(CRLF 传输面统一 LF——与 TUI 输入解码同律)。
168
+ */
169
+ export function decodeWireLine(line) {
170
+ const rawLine = line;
171
+ let parsed;
172
+ try {
173
+ parsed = JSON.parse(line);
174
+ }
175
+ catch {
176
+ throw new SdkDecodeError('非合法 JSON', rawLine);
177
+ }
178
+ const which = classify(parsed, rawLine);
179
+ const shape = parsed;
180
+ if (which === 'request') {
181
+ validateRequest(shape, shape.verb, rawLine);
182
+ // 深校验(批 13e 接入——./schema.ts 单源):结构层过后逐字段 typebox 校验
183
+ //(未知字段拒收/选填语义——HTTP 体校验位同源同件)
184
+ const deep = validateSdkRequest(parsed);
185
+ if (!deep.ok)
186
+ throw new SdkDecodeError(deep.reason, rawLine);
187
+ return deep.value;
188
+ }
189
+ validateFrame(shape, shape.kind, rawLine);
190
+ return parsed;
191
+ }
192
+ /**
193
+ * 流式分帧(stdio 管道跨 chunk 边界的安全分帧):按 `\n` 切行、行尾 `\r` 剥离
194
+ * (CRLF 统一 LF)、空行跳过(心跳间隙的空拍不产帧)。入参 chunk 与上次
195
+ * remainder 拼接后切分;无完整行的 chunk 只进 remainder(零字节丢失、零半帧)。
196
+ */
197
+ export function splitWireLines(chunk, remainder = '') {
198
+ const joined = remainder + chunk;
199
+ const lastNewline = joined.lastIndexOf('\n');
200
+ if (lastNewline === -1)
201
+ return { lines: [], remainder: joined };
202
+ const lines = joined
203
+ .slice(0, lastNewline)
204
+ .split('\n')
205
+ .map((line) => (line.endsWith('\r') ? line.slice(0, -1) : line))
206
+ .filter((line) => line.length > 0);
207
+ return { lines, remainder: joined.slice(lastNewline + 1) };
208
+ }