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,22 @@
1
+ /**
2
+ * core:sdk 件域(批 13e 起域——03 §10.6 件身份条:HTTP+SSE 传输与 MCP
3
+ * server 包装归本件;协议核心与 stdio 归宿主/channels 侧既有)。
4
+ *
5
+ * 本笔(13e-1)= 契约先行:HTTP 面端点词面(03 §10.6 批 13e 落码定形注①)
6
+ * + 开面配置形(07 §5 serve 旗标族落码定名——sock 缺省接入点/TCP 可选位/
7
+ * 凭证双载体)+ 三防线判定器与 token 生成(10.4 回环钉死条款同律复用 +
8
+ * 差异面④ Host 扩集谓词)。传输实装(node:http 微路由 + SSE + 一核多流
9
+ * 扇出)随 13e-2 兑现(本域 http 件);daemon 编舞(pid/sock/log 三足迹 +
10
+ * serve status/stop)随 13e-3;MCP server 包装(行帧 JSON-RPC 反向位、
11
+ * 工具面收窄两件——`berry-agent`/`berry-agent-reply`)随 13f 兑现(本域
12
+ * mcp 件)。
13
+ *
14
+ * 装配纪律(件不 import 宿主实现——webui claim 桥晚绑同款):协议核经
15
+ * channels 公开面消费(SdkWireDeps 由宿主装配桥注入),本域零 host 依赖。
16
+ */
17
+ export * from './types.js';
18
+ export * from './security.js';
19
+ export * from './http.js';
20
+ export * from './mcp.js';
21
+ export * from './plugin-routes.js';
22
+ export * from './plugin-route-registry.js';
@@ -0,0 +1,289 @@
1
+ import { createSdkBackend, splitWireLines, validateSdkRequest, } from '../channels/index.js';
2
+ /* ---------------- MCP 协议常量(行帧 JSON-RPC 2.0——10.1 反向位同集) ---------------- */
3
+ /** 已知 protocolVersion 集(回显制——请求值在集内即回显) */
4
+ const MCP_KNOWN_VERSIONS = ['2024-11-05', '2025-03-26', '2025-06-18'];
5
+ /** 服务端缺省 protocolVersion(请求值不在已知集时回本值) */
6
+ const MCP_DEFAULT_VERSION = '2025-06-18';
7
+ /** JSON-RPC 2.0 错误码(协议错档——业务错走工具结果 isError 位不占此面) */
8
+ const JSONRPC_PARSE_ERROR = -32700;
9
+ const JSONRPC_METHOD_NOT_FOUND = -32601;
10
+ const JSONRPC_INVALID_PARAMS = -32602;
11
+ const JSONRPC_INTERNAL_ERROR = -32603;
12
+ /** 会话发起/续接工具名(codex `codex` 形镜像——对外声明值位) */
13
+ export const MCP_PROMPT_TOOL_NAME = 'berry-agent';
14
+ /** 回复轮询工具名(codex `codex-reply` 形镜像) */
15
+ export const MCP_POLL_TOOL_NAME = 'berry-agent-reply';
16
+ /** 工具面收窄两件(tools/list 应答单源——inputSchema 手写字面静态声明) */
17
+ const MCP_TOOLS = [
18
+ {
19
+ name: MCP_PROMPT_TOOL_NAME,
20
+ description: '向 berry-agent 会话发起或续接一条消息(异步档:受理即回执——回复增量经 berry-agent-reply 轮询获取)',
21
+ inputSchema: {
22
+ type: 'object',
23
+ properties: {
24
+ message: { type: 'string', description: '用户消息正文' },
25
+ sessionId: { type: 'string', description: '续接会话句柄——缺席即新建会话' },
26
+ messageId: {
27
+ type: 'string',
28
+ description: '幂等键(同键同内容重发收执 duplicate 不重跑)——缺席则每次全新',
29
+ },
30
+ },
31
+ required: ['message'],
32
+ },
33
+ },
34
+ {
35
+ name: MCP_POLL_TOOL_NAME,
36
+ description: '轮询会话回复增量(durable 投影——含消息定稿与 run 终态事件;lastSeq 为续读位)',
37
+ inputSchema: {
38
+ type: 'object',
39
+ properties: {
40
+ sessionId: { type: 'string', description: '会话句柄' },
41
+ since: {
42
+ type: 'number',
43
+ description: '已收末条 seq(窗口 (since, 高水位])——缺席 -1 从头取全窗',
44
+ },
45
+ },
46
+ required: ['sessionId'],
47
+ },
48
+ },
49
+ ];
50
+ /**
51
+ * 起 MCP server 面:一行一 JSON-RPC 消息(请求/通知)进、一行一应答出。
52
+ *
53
+ * 应答写出纪律:MCP 应答帧小量低频(一请求一应答),Node 流内缓冲即背压
54
+ * 界——不设独立队列(serve 直写面的有界队列背压机械不在此复制,量级不配)。
55
+ */
56
+ export function runMcpFace(options) {
57
+ const log = options.log ?? (() => { });
58
+ // 收集式 sink:MCP 形请求/应答档——无订阅即无直播帧流;应答帧在
59
+ // handleRequest 同步事务内收齐、由工具调用处取走翻译成 MCP 工具结果
60
+ //(协议核应答帧不达 MCP 线——包装层只投影不转发)。
61
+ // capturing 位 = 零直播面的传输边界执法:prompt 自动订阅(13b 落码定形)
62
+ // 会让在飞 run 的活体帧流入本 sink——无 MCP 直播线可承载即弃(返回 true
63
+ // 视为写达,出站队列恒空——背压机械不触发);仅在 roundTrip 事务内收帧
64
+ let collected = [];
65
+ let capturing = false;
66
+ const sink = {
67
+ write: (frame) => {
68
+ if (capturing)
69
+ collected.push(frame);
70
+ return true;
71
+ },
72
+ };
73
+ // 协议核单源(admit/先决门/深校验后的受理全在核内;MCP 形无连接级
74
+ // hello——noDelta 无承载位,核选项全缺省)
75
+ const backend = createSdkBackend({ ...options.bridge, sink });
76
+ // 未携幂等键的 prompt 计数(「幂等语义未申请即不虚构」——每次全新)
77
+ let unkeyedSeq = 0;
78
+ /**
79
+ * 经协议核受理一请求并同步取应答帧(映射层薄——全部受理语义单源在核)。
80
+ * 应答缺席 = 核内不变式破(理论不达)——由 {@link translate} 出内部错。
81
+ */
82
+ const roundTrip = (req) => {
83
+ collected = [];
84
+ capturing = true;
85
+ try {
86
+ backend.core.handleRequest(req);
87
+ }
88
+ finally {
89
+ capturing = false; // 事务外活体帧一律弃(零直播面——见 sink 位注)
90
+ }
91
+ const frames = collected;
92
+ collected = [];
93
+ return frames[0];
94
+ };
95
+ /** 写一行 JSON-RPC 消息 */
96
+ const writeMessage = (msg) => {
97
+ options.io.output.write(`${JSON.stringify(msg)}\n`);
98
+ };
99
+ /** JSON-RPC 成功应答(请求 id 原样回联——string/number 两形皆透传) */
100
+ const writeResult = (id, result) => {
101
+ writeMessage({ jsonrpc: '2.0', id, result });
102
+ };
103
+ const writeError = (id, code, message) => {
104
+ writeMessage({ jsonrpc: '2.0', id, error: { code, message } });
105
+ };
106
+ /** tools/call 工具结果速记(isError 位 = 业务错两分档——非 JSON-RPC error) */
107
+ const writeToolResult = (id, text, isError = false) => {
108
+ writeResult(id, { content: [{ type: 'text', text }], ...(isError ? { isError: true } : {}) });
109
+ };
110
+ /** 应答帧 → MCP 工具结果翻译(ack/entries/error 三投影位——两动词共用) */
111
+ const translate = (frame, id, sinceDefault = -1) => {
112
+ if (frame === undefined) {
113
+ writeError(id, JSONRPC_INTERNAL_ERROR, '协议核应答缺席(内部不变式破)');
114
+ return;
115
+ }
116
+ if (frame.kind === 'error') {
117
+ writeToolResult(id, JSON.stringify({ code: frame.code, message: frame.message }), true);
118
+ return;
119
+ }
120
+ if (frame.kind === 'ack') {
121
+ // ack 投影(受理回执——异步档的会话句柄获取位)
122
+ writeToolResult(id, JSON.stringify({
123
+ sessionId: frame.sessionId,
124
+ messageId: frame.messageId,
125
+ duplicate: frame.duplicate,
126
+ ...(frame.routedChannel !== undefined ? { routedChannel: frame.routedChannel } : {}),
127
+ highWaterSeq: frame.highWaterSeq,
128
+ }));
129
+ return;
130
+ }
131
+ if (frame.kind === 'entries') {
132
+ // 空窗 lastSeq = 续读位原样回显(无进展不虚报)
133
+ const lastSeq = frame.entries.length > 0 ? frame.entries[frame.entries.length - 1].seq : sinceDefault;
134
+ writeToolResult(id, JSON.stringify({ sessionId: frame.sessionId, entries: frame.entries, lastSeq }));
135
+ return;
136
+ }
137
+ // 其余应答帧形理论不达(两动词只产 ack/entries/error)——内部错如实出
138
+ writeError(id, JSONRPC_INTERNAL_ERROR, `意外应答帧形 ${String(frame.kind)}`);
139
+ };
140
+ /** 工具参数 → 深校验请求(同源校验器单源——stdio 解码位/HTTP 体校验位同款) */
141
+ const validate = (id, raw) => {
142
+ const validated = validateSdkRequest(raw);
143
+ if (!validated.ok) {
144
+ writeToolResult(id, `参数不符:${validated.reason}`, true);
145
+ return { ok: false };
146
+ }
147
+ return { ok: true, value: validated.value };
148
+ };
149
+ /** 工具调用 → 协议核动词映射(两件收窄面) */
150
+ const callTool = (id, name, args) => {
151
+ if (name === MCP_PROMPT_TOOL_NAME) {
152
+ // 幂等键调用方自选律:缺席 = 计数器形全新键(幂等语义未申请即不虚构)
153
+ const messageId = typeof args?.messageId === 'string' && args.messageId !== '' ? args.messageId : `mcp-${++unkeyedSeq}`;
154
+ const v = validate(id, {
155
+ verb: 'prompt',
156
+ messageId,
157
+ content: args?.message,
158
+ ...(typeof args?.sessionId === 'string' ? { sessionId: args.sessionId } : {}),
159
+ });
160
+ if (!v.ok)
161
+ return;
162
+ translate(roundTrip(v.value), id);
163
+ return;
164
+ }
165
+ if (name === MCP_POLL_TOOL_NAME) {
166
+ // since 缺省 -1 从头哨兵(cursor.ts resolveLiveStart 同款语义)
167
+ const since = typeof args?.since === 'number' ? args.since : -1;
168
+ const v = validate(id, { verb: 'getEntries', sessionId: args?.sessionId, since });
169
+ if (!v.ok)
170
+ return;
171
+ if (v.value.verb !== 'getEntries')
172
+ return; // 构造面恒真——纯类型收窄位
173
+ // 分页跟尽义务在调用方(05 §3.4)——本面即调用方:nextCursor 在场即
174
+ // 续读(v1 装配桥并页兑现零游标——环体防御性在场,正常零次执行;
175
+ // 帽 100 防环游标死循环)
176
+ let frame = roundTrip(v.value);
177
+ let hops = 0;
178
+ while (frame !== undefined && frame.kind === 'entries' && frame.nextCursor !== undefined && hops < 100) {
179
+ hops++;
180
+ const more = roundTrip({ ...v.value, cursor: frame.nextCursor });
181
+ if (more === undefined || more.kind !== 'entries')
182
+ break;
183
+ frame = { ...more, entries: [...frame.entries, ...more.entries] };
184
+ }
185
+ translate(frame, id, since);
186
+ return;
187
+ }
188
+ // 未知工具 = 协议参数错(非业务错——工具面两件闭集外即调用方笔误)
189
+ writeError(id, JSONRPC_INVALID_PARAMS, `未知工具 ${String(name)}(工具面 = ${MCP_PROMPT_TOOL_NAME} / ${MCP_POLL_TOOL_NAME})`);
190
+ };
191
+ /** 单条 JSON-RPC 消息路由(请求/通知两分) */
192
+ const handleMessage = (msg) => {
193
+ if (typeof msg !== 'object' || msg === null)
194
+ return; // 帧形不理(无 id 可应答)
195
+ const { method, id, params } = msg;
196
+ // 通知(id 缺席)零应答——notifications/initialized 等一律静默收执
197
+ if (id === undefined)
198
+ return;
199
+ switch (method) {
200
+ case 'initialize': {
201
+ const requested = params?.protocolVersion;
202
+ const version = typeof requested === 'string' && MCP_KNOWN_VERSIONS.includes(requested)
203
+ ? requested // 已知集回显(客户端版本优先)
204
+ : MCP_DEFAULT_VERSION; // 未知/缺席 → 服务端缺省
205
+ writeResult(id, {
206
+ protocolVersion: version,
207
+ capabilities: { tools: {} }, // 工具面在场(listChanged 不发——静态两件)
208
+ serverInfo: options.serverInfo,
209
+ });
210
+ return;
211
+ }
212
+ case 'ping':
213
+ writeResult(id, {});
214
+ return;
215
+ case 'tools/list':
216
+ writeResult(id, { tools: MCP_TOOLS });
217
+ return;
218
+ case 'tools/call': {
219
+ const p = (params ?? {});
220
+ callTool(id, p.name, typeof p.arguments === 'object' && p.arguments !== null
221
+ ? p.arguments
222
+ : undefined);
223
+ return;
224
+ }
225
+ default:
226
+ writeError(id, JSONRPC_METHOD_NOT_FOUND, `未知方法 ${String(method)}`);
227
+ }
228
+ };
229
+ // —— 收场单次性(EOF 优雅 0 / 输入输出面坏死 1)——
230
+ let settled = false;
231
+ let resolveDone;
232
+ const done = new Promise((resolve) => {
233
+ resolveDone = resolve;
234
+ });
235
+ const finish = (code) => {
236
+ if (settled)
237
+ return;
238
+ settled = true;
239
+ options.io.input.removeListener('data', onData);
240
+ options.io.input.removeListener('end', onEnd);
241
+ options.io.input.removeListener('close', onEnd);
242
+ options.io.input.removeListener('error', onInputError);
243
+ options.io.output.removeListener('error', onOutputError);
244
+ backend.dispose();
245
+ resolveDone(code);
246
+ };
247
+ // —— 入站环:分帧(行安全同源件)→ JSON 解析 → 消息路由 ——
248
+ let remainder = '';
249
+ const onData = (chunk) => {
250
+ if (settled)
251
+ return;
252
+ const split = splitWireLines(chunk, remainder);
253
+ remainder = split.remainder;
254
+ for (const line of split.lines) {
255
+ if (line.length === 0)
256
+ continue; // 空行不理
257
+ let msg;
258
+ try {
259
+ msg = JSON.parse(line);
260
+ }
261
+ catch {
262
+ // 坏行:JSON-RPC 规范形应答(id null——解析失败无从取 id)
263
+ writeError(null, JSONRPC_PARSE_ERROR, '行不是合法 JSON');
264
+ continue;
265
+ }
266
+ handleMessage(msg);
267
+ }
268
+ };
269
+ const onEnd = () => finish(0); // EOF = 调用方收线(对端 agent 退出)
270
+ const onInputError = (err) => {
271
+ log(`stdin 异常:${err.message}`);
272
+ finish(1);
273
+ };
274
+ const onOutputError = (err) => {
275
+ log(`stdout 异常:${err.message}`);
276
+ finish(1);
277
+ };
278
+ options.io.input.setEncoding?.('utf8');
279
+ options.io.input.on('data', onData);
280
+ options.io.input.on('end', onEnd);
281
+ options.io.input.on('close', onEnd);
282
+ options.io.input.on('error', onInputError);
283
+ options.io.output.on('error', onOutputError);
284
+ return {
285
+ backend: backend.backend,
286
+ done,
287
+ dispose: () => finish(0), // 宿主 closer 消费(信号路优雅档);幂等
288
+ };
289
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * sdk/plugin-route-registry — 插件道路由受理器真身(U5-2 装配+时序笔)。
3
+ *
4
+ * 03 §10.6 U5 定形注「时序缝定形」段的码面兑现:**host-owned 待注册路由
5
+ * registry**(受理与挂载两时点解耦——「插件 boot 先于 HTTP 面存在」三入口
6
+ * 结构性缝的正解)。受理动作恒 apply 期入本账(装载窗执法在前);挂载由
7
+ * 两路 replay:
8
+ * - **构造期路**:daemon / TUI `--port` / serve 前台三入口开面时
9
+ * `snapshot()` 快照注入 `SdkHttpFaceOptions.routes` 位;
10
+ * - **晚注册路**:面已开后受理(/reload 换代重走受理序——受理窗装载期
11
+ * 恒先于或外在于开面时点,两序皆可达)经 `attachFace` 持有的活面
12
+ * `face.register` 晚注册位(claim 桥同款)。
13
+ *
14
+ * 受理序(§2.2 第十三面全链)= 装载窗(`PLUGIN_WINDOW_CLOSED`——
15
+ * registerOAuthFlow〔c-6〕/registerSummarizer〔U4〕同律窗先于门)→ 门检
16
+ * (`adjudicateCapabilityDoor`——assertDoor 第二落地点;core: 官方件直开
17
+ * 豁免〔§4.6 判据:装配即用户意图〕但审计照记——「豁免免的是门不是账」
18
+ * triggers 同律)→ 纯函数裁决(`adjudicatePluginRoute`——SDK_ROUTE_ 四码)
19
+ * → 查重(面在场走 face.register 既有 method+path 双键普通 Error;缺席态
20
+ * 账内同形 Error——与 core: 道同表同律零新码)→ 受理入账(面在场附带
21
+ * 挂载)→ `capability/used` 审计恰一笔(05 §1.1——method+path 全路径归因
22
+ * 键;拒路径零审计)→ 返摘除 fn。
23
+ *
24
+ * /reload 换代门复检(03 §10.6):disposer 摘旧 + 新代重走本受理序——
25
+ * bootPlugins 对每插件 fork.effect 兜底 `releaseFor`(compaction 同律双
26
+ * 保险);撤授予位后旧路由不残留(换代重注册现判现拒)。
27
+ */
28
+ import { adjudicateCapabilityDoor } from '../contracts/api.js';
29
+ import { BaseError } from '../contracts/index.js';
30
+ import { adjudicatePluginRoute } from './plugin-routes.js';
31
+ /** 服务名常量(SERVICE_CATALOG 第三条 'sdk-routes'——fork 绑定落位单源) */
32
+ export const SDK_ROUTES_SERVICE = 'sdk-routes';
33
+ /** 高危面名(03 §4.6 v1 首批第二枚——门检/审计归因单源) */
34
+ export const SDK_ROUTE_CAPABILITY = 'sdk.register-route';
35
+ /**
36
+ * 受理器工厂(assembly 单真身构造位)。纯账面状态 + 零自持监听——面生命
37
+ * 周期归三入口,本件只做受理裁决与两时点解耦的账。
38
+ */
39
+ export function createPluginRouteRegistry(options = {}) {
40
+ /** 在账路由(`${method} ${path}` 双键唯一——查重账与 core: 道注册键同形) */
41
+ const entries = new Map();
42
+ /** 活面引用(attachFace 后受理的晚注册承载位——face.stop 后 detachFace 清空) */
43
+ let liveFace;
44
+ /** 本插件在账数(数帽分帐输入——跨插件不共享) */
45
+ const countOf = (pluginId) => {
46
+ let n = 0;
47
+ for (const entry of entries.values())
48
+ if (entry.pluginId === pluginId)
49
+ n += 1;
50
+ return n;
51
+ };
52
+ /** 账面摘除(摘除 fn 与 releaseFor 共用——面在场连带摘挂 + 键释放可重注册) */
53
+ const remove = (key) => {
54
+ const entry = entries.get(key);
55
+ if (entry === undefined)
56
+ return;
57
+ entry.removeLive?.();
58
+ entries.delete(key);
59
+ };
60
+ return {
61
+ bindForPlugin({ pluginId, getOpens, inLoadWindow }) {
62
+ return {
63
+ register(descriptor) {
64
+ // —— ① 装载窗(c-6/U4 同律窗先于门——受理窗装载期 only,回调窗延伸不适用)
65
+ if (!inLoadWindow()) {
66
+ throw new BaseError('PLUGIN_WINDOW_CLOSED', `注册动词 ctx.get("sdk-routes").register 在装载窗口外被拒(插件 ${pluginId}——路由注册只在 apply 执行期间合法,03 §2.2 第十三面)`);
67
+ }
68
+ // —— ② 门检前置先于查重(registerUiBackend 同律——未开门连查重都不可达);
69
+ // core: 官方件直开豁免(§4.6 判据:装配即用户意图),审计照记不豁免
70
+ if (!pluginId.startsWith('core:')) {
71
+ const verdict = adjudicateCapabilityDoor(getOpens(), SDK_ROUTE_CAPABILITY);
72
+ if (!verdict.ok) {
73
+ throw new BaseError('PLUGIN_CAPABILITY_DOOR_CLOSED', verdict.message);
74
+ }
75
+ }
76
+ // —— ③ 纯函数裁决(词形/保留字 → 档位收窄 → 数帽 → 落面铸造——U5-1 件)
77
+ const judged = adjudicatePluginRoute(pluginId, descriptor, { pluginRouteCount: countOf(pluginId) });
78
+ if (!judged.ok) {
79
+ throw new BaseError(judged.code, judged.message);
80
+ }
81
+ const full = judged.descriptor;
82
+ const key = `${full.method} ${full.path}`;
83
+ // —— ④ 查重:面在场走 face.register 既有 throw(同表同律字面复用);
84
+ // pending 态账内同形普通 Error(core: 道注册期消息形——零新码零重复执法)
85
+ if (entries.has(key)) {
86
+ if (liveFace !== undefined) {
87
+ liveFace.register(full); // 既有 method+path 双键 throw——与 core: 道同表
88
+ }
89
+ throw new Error(`路由重复注册(method+path 双键唯一):${key}——注册期 fail-loud`);
90
+ }
91
+ // —— ⑤ 受理入账(面在场附带挂载晚注册位——摘除 fn 双轨记录)
92
+ let removeLive;
93
+ if (liveFace !== undefined)
94
+ removeLive = liveFace.register(full);
95
+ entries.set(key, { pluginId, descriptor: full, ...(removeLive !== undefined ? { removeLive } : {}) });
96
+ // —— ⑥ capability/used 恰一笔(受理成功后落;拒路径零审计;core: 照记)
97
+ options.onCapabilityUsed?.({
98
+ pluginId,
99
+ capability: SDK_ROUTE_CAPABILITY,
100
+ method: full.method,
101
+ path: full.path,
102
+ });
103
+ // 摘除 fn(disposer 律 §2.6——/reload 换代回收腿的手动面;releaseFor 兜底双保险)
104
+ return () => remove(key);
105
+ },
106
+ };
107
+ },
108
+ releaseFor(pluginId) {
109
+ for (const [key, entry] of [...entries]) {
110
+ if (entry.pluginId === pluginId)
111
+ remove(key);
112
+ }
113
+ },
114
+ snapshot() {
115
+ return [...entries.values()].map((entry) => entry.descriptor);
116
+ },
117
+ attachFace(face) {
118
+ if (liveFace === face)
119
+ return; // 幂等保真(同面重复挂接 no-op)
120
+ liveFace = face;
121
+ },
122
+ detachFace() {
123
+ liveFace = undefined;
124
+ },
125
+ };
126
+ }
@@ -0,0 +1,149 @@
1
+ /* ---------------- 规约常量(03 §10.6 U5 定形注——数值钉死位) ---------------- */
2
+ /** 插件道根段前缀(`/plugins/<id>/` 两段复合——前缀恒受理面施加、插件只申报 suffix) */
3
+ export const PLUGIN_ROUTE_ROOT = '/plugins';
4
+ /**
5
+ * 插件道请求体帽钳值(1MiB——可小不可大;**缺席即本值**:受理面填值后转发,
6
+ * 落面 descriptor 该位恒在场且 ≤1MiB,防面级缺省 10MiB 静默穿透)。
7
+ */
8
+ export const PLUGIN_ROUTE_BODY_LIMIT_MAX_BYTES = 1024 * 1024;
9
+ /** per-plugin 路由数帽(受理面计数、超帽拒 `SDK_ROUTE_LIMIT_REACHED`) */
10
+ export const PLUGIN_ROUTE_LIMIT = 16;
11
+ /** 插件道 open 档合法语义位子集(core: 道三值去 `auth-exchange`——插件不得自建换证端点) */
12
+ export const PLUGIN_ROUTE_OPEN_PURPOSES = ['liveness', 'static-shell'];
13
+ /* ---------------- 拼合单源(id → URL 段规约) ---------------- */
14
+ /**
15
+ * 插件道路径拼合(单源——eliza 前缀默认施加判例采纳、rawPath 逃生门不设):
16
+ * `/plugins/<encodeURIComponent(pluginId)><suffix>`。id 经 URL 段安全编码
17
+ * 承载特殊字符('core:xxx' 冒号形 → 'core%3Axxx'——编码后不含 `/`,段边界
18
+ * 单源性由此保证)。
19
+ */
20
+ export function pluginRoutePath(pluginId, suffix) {
21
+ return `${PLUGIN_ROUTE_ROOT}/${encodeURIComponent(pluginId)}${suffix}`;
22
+ }
23
+ /**
24
+ * 插件道路由受理裁决(纯函数零副作用——03 §10.6 受理序的纯函数段):
25
+ *
26
+ * ① 词形/保留字校验(`SDK_ROUTE_PATH_RESERVED`——防御性回弹):
27
+ * suffix 须 `/` 开头(单独 `*` 全局形拒);保留根段闭集 {`/v1/`, `/plugins/`}
28
+ * 撞入即拒(执法位本在 core: 道注册器,插件道经前缀施加律结构性框死于
29
+ * `/plugins/<id>/` 之下——受理面此校验系纵深防御的回弹层);段式正规形
30
+ * (无空段 / `:name` 参数段字母数字下划线 / 通配 `*` 至多一枚且须尾段 /
31
+ * 字面段不含保留字符 `:` `*`)。
32
+ * ② 档位收窄裁决(`SDK_ROUTE_AUTH_FORBIDDEN` / `SDK_ROUTE_BODY_LIMIT`):
33
+ * 鉴权档子集(self 拒、open 限 liveness·static-shell 两 purpose);
34
+ * bodyLimitBytes 非正整数或 >1MiB 拒(可小不可大)。
35
+ * ③ 数帽(`SDK_ROUTE_LIMIT_REACHED`):本插件在册数达 16 拒。
36
+ * ④ 落面铸造:前缀施加 + loopbackOnly 恒 true + bodyLimitBytes 填值(缺席
37
+ * 即 1MiB)——descriptor 落面该位恒在场,面级缺省 10MiB 结构性不可达。
38
+ *
39
+ * 门检前置先于本函数(未开门连查重都不可达——registerUiBackend 同律)、
40
+ * capability/used 审计恰一笔(拒路径零审计)均归 U5-2 受理器编排。
41
+ */
42
+ export function adjudicatePluginRoute(pluginId, descriptor, state) {
43
+ const { method, path: suffix, auth, handler } = descriptor;
44
+ // —— ① 词形/保留字(PATH_RESERVED——防御性回弹层)——
45
+ if (typeof pluginId !== 'string' || pluginId === '') {
46
+ return { ok: false, code: 'SDK_ROUTE_PATH_RESERVED', message: '插件 id 空缺——前缀拼合不可达' };
47
+ }
48
+ // 单独 `*`(全局吞一切)不以 / 开头,由起头判据一并拒;message 点名全局形归属
49
+ if (typeof suffix !== 'string' || !suffix.startsWith('/')) {
50
+ return {
51
+ ok: false,
52
+ code: 'SDK_ROUTE_PATH_RESERVED',
53
+ message: `插件道路由 path 须以 / 开头的 suffix(单独 * 全局形系 core: 道 SPA fallback 专属承载位、插件道不可达):${method} ${suffix}`,
54
+ };
55
+ }
56
+ // 保留根段防御性回弹:前缀施加律下结构性不可撞,纵深防御仍显式拒
57
+ if (suffix === '/v1' ||
58
+ suffix.startsWith('/v1/') ||
59
+ suffix === PLUGIN_ROUTE_ROOT ||
60
+ suffix.startsWith(`${PLUGIN_ROUTE_ROOT}/`)) {
61
+ return {
62
+ ok: false,
63
+ code: 'SDK_ROUTE_PATH_RESERVED',
64
+ message: `插件道路由 path 撞保留根段闭集 {/v1/, /plugins/}——前缀施加律下 /plugins/<id>/ 域外结构性不可达(防御性回弹拒):${method} ${suffix}`,
65
+ };
66
+ }
67
+ const segments = suffix.slice(1).split('/');
68
+ if (segments.some((seg) => seg === '')) {
69
+ return { ok: false, code: 'SDK_ROUTE_PATH_RESERVED', message: `插件道路由 path 空段非法:${method} ${suffix}` };
70
+ }
71
+ // 通配 `*`:至多一枚且须尾段(域内 catch-all——suffix 尾 * 映射 /plugins/<id>/*)
72
+ const starIndex = segments.indexOf('*');
73
+ if (starIndex !== -1 && (starIndex !== segments.length - 1 || segments.indexOf('*', starIndex + 1) !== -1)) {
74
+ return {
75
+ ok: false,
76
+ code: 'SDK_ROUTE_PATH_RESERVED',
77
+ message: `插件道路由 path 通配 * 至多一枚且须尾段:${method} ${suffix}`,
78
+ };
79
+ }
80
+ for (const seg of segments) {
81
+ if (seg.startsWith(':')) {
82
+ if (!/^[A-Za-z0-9_]+$/.test(seg.slice(1))) {
83
+ return {
84
+ ok: false,
85
+ code: 'SDK_ROUTE_PATH_RESERVED',
86
+ message: `插件道路由参数名非法(字母数字下划线):${method} ${suffix}`,
87
+ };
88
+ }
89
+ }
90
+ else if (seg !== '*' && /[:*]/.test(seg)) {
91
+ return {
92
+ ok: false,
93
+ code: 'SDK_ROUTE_PATH_RESERVED',
94
+ message: `插件道路由段含保留字符(: / *):${method} ${suffix}`,
95
+ };
96
+ }
97
+ }
98
+ // —— ② 档位收窄(AUTH_FORBIDDEN / BODY_LIMIT)——
99
+ // 鉴权档子集:self 逃生档不对插件道开放(issue webhook HMAC 位系 host 装配独占)。
100
+ // 类型面已结构性排除 self(PluginRouteAuth 子集——tsc 即证无交集);本判定
101
+ // 系 JS 调用方(jiti 装载零编译期)与类型漂移的防御性回弹,故显式拓宽比较
102
+ if (auth === 'self') {
103
+ return {
104
+ ok: false,
105
+ code: 'SDK_ROUTE_AUTH_FORBIDDEN',
106
+ message: '鉴权档 self 不对插件道开放(issue webhook HMAC 位系 host 装配独占逃生档)',
107
+ };
108
+ }
109
+ // open 档 purpose 射界子集:liveness·static-shell 两值(auth-exchange 不开——插件不得自建换证端点)
110
+ if (typeof auth === 'object' && auth !== null && auth.mode === 'open') {
111
+ const purpose = auth.purpose;
112
+ if (!PLUGIN_ROUTE_OPEN_PURPOSES.includes(purpose)) {
113
+ return {
114
+ ok: false,
115
+ code: 'SDK_ROUTE_AUTH_FORBIDDEN',
116
+ message: `open 档 purpose ${purpose} 不对插件道开放——合法语义位子集:${PLUGIN_ROUTE_OPEN_PURPOSES.join(' / ')}(auth-exchange 换证端点不开放)`,
117
+ };
118
+ }
119
+ }
120
+ // 体帽钳制:缺席即 1MiB 受理面填值;在场须正整数且 ≤1MiB(可小不可大)
121
+ const declared = descriptor.bodyLimitBytes;
122
+ if (declared !== undefined &&
123
+ (!Number.isSafeInteger(declared) || declared <= 0 || declared > PLUGIN_ROUTE_BODY_LIMIT_MAX_BYTES)) {
124
+ return {
125
+ ok: false,
126
+ code: 'SDK_ROUTE_BODY_LIMIT',
127
+ message: `bodyLimitBytes 须为正整数且 ≤${PLUGIN_ROUTE_BODY_LIMIT_MAX_BYTES}(1MiB,可小不可大;缺席即该帽值):${String(declared)}`,
128
+ };
129
+ }
130
+ const bodyLimitBytes = declared ?? PLUGIN_ROUTE_BODY_LIMIT_MAX_BYTES;
131
+ // —— ③ 数帽(LIMIT_REACHED——per-plugin 在册数达帽拒)——
132
+ if (state.pluginRouteCount >= PLUGIN_ROUTE_LIMIT) {
133
+ return {
134
+ ok: false,
135
+ code: 'SDK_ROUTE_LIMIT_REACHED',
136
+ message: `插件道路由数帽 ${PLUGIN_ROUTE_LIMIT}已达(per-plugin 计数——摘除 fn 释放后可再注册)`,
137
+ };
138
+ }
139
+ // —— ④ 落面铸造(core: 道形——三处收窄全部落位)——
140
+ const full = {
141
+ method,
142
+ path: pluginRoutePath(pluginId, suffix),
143
+ auth,
144
+ loopbackOnly: true, // 恒 true:子集形无自选位(缺省 true 且不可自选 false 的结构性执法)
145
+ bodyLimitBytes, // 恒在场且 ≤1MiB:面级缺省 10MiB 结构性不可达
146
+ handler,
147
+ };
148
+ return { ok: true, descriptor: full };
149
+ }