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,241 @@
1
+ import { createBudgetBroadcast } from './budget-broadcast.js';
2
+ /** 首跑 source(03 §2.2 provenance 盖章——core:issue 件身份;plugin: 域谱系) */
3
+ const ISSUE_SUBMIT_SOURCE = 'plugin:core:issue';
4
+ /** 唤醒消息 source(04 §5 + 05 §3.1 同笔词行——source 闭集恰六字面量成员) */
5
+ const WAKE_SOURCE = 'budget-extended';
6
+ /** 唤醒消息文本(durable user/message 载体——模型侧「预算恢复继续任务」指令) */
7
+ const WAKE_MESSAGE = '后台预算日池已恢复(budget_extended)——请继续此前停靠时未完成的任务,完成后按原交付纪律收口。';
8
+ /** watchdog / 唤醒 watcher 轮询间隔缺省(1s——观测粒度与轮询成本的折中;测试注窄值) */
9
+ const DEFAULT_POLL_MS = 1000;
10
+ /** assistant/message 事件计数(每 issue 帽的计量面——messagesUsed 同源) */
11
+ function countAssistantMessages(events) {
12
+ let count = 0;
13
+ for (const event of events) {
14
+ if (event.type === 'assistant/message')
15
+ count += 1;
16
+ }
17
+ return count;
18
+ }
19
+ /** 尾扫末条 assistant 消息文本(summary 来源——subagent-factory 同型扫描) */
20
+ function lastAssistantText(events) {
21
+ for (let i = events.length - 1; i >= 0; i -= 1) {
22
+ const event = events[i];
23
+ if (event === undefined || event.type !== 'assistant/message')
24
+ continue;
25
+ const blocks = event.data?.content;
26
+ if (!Array.isArray(blocks))
27
+ return '';
28
+ return blocks
29
+ .filter((block) => block.type === 'text')
30
+ .map((block) => block.text)
31
+ .join('');
32
+ }
33
+ return '';
34
+ }
35
+ /** 被拒审批计数(needs-human 检测——decision ∉ {approve, always} 即被拒面) */
36
+ function deniedApprovalCount(events) {
37
+ let count = 0;
38
+ for (const event of events) {
39
+ if (event.type !== 'approval/decided')
40
+ continue;
41
+ const decision = event.data?.decision;
42
+ if (decision !== 'approve' && decision !== 'always')
43
+ count += 1;
44
+ }
45
+ return count;
46
+ }
47
+ /**
48
+ * issue headless 会话真工厂(CorePluginHostDeps.issueSession seam 的本体——
49
+ * 装配根接线后 core:issue 主闸三通过,生产面件装载解锁)。
50
+ */
51
+ export function createIssueSessionFactory(options) {
52
+ const { stack, canAfford } = options;
53
+ const warn = options.warn ?? ((message) => console.error(message));
54
+ const pollMs = options.pollMs ?? DEFAULT_POLL_MS;
55
+ /** 停靠登记表(唤醒 watcher 扫描面——跨 startHeadless 共享,进程生命周期) */
56
+ const parked = new Set();
57
+ // 广播件解析(u-3——04 §5 定形注①升格):注入形 = 宿主单真身(三停靠面
58
+ // 同播,dispose 归宿主停机序不归本工厂);缺席 = 自建私有实例(同一实现
59
+ // 单源——独立装配形/测试形零降级,工厂 dispose 时同笔收口)
60
+ const ownsBroadcast = options.broadcast === undefined;
61
+ const broadcast = options.broadcast ?? createBudgetBroadcast({ canAfford, pollMs });
62
+ async function startHeadless(req) {
63
+ // 终局外壳:停靠 = 保持 pending(编排层 await 悬停——04 §5 不落终态语义
64
+ // 的实现载体);dispose 的 paused 收口也走同一 settle(一次结算幂等护栏)
65
+ let settleOutcome;
66
+ const outcome = new Promise((resolve) => {
67
+ settleOutcome = resolve;
68
+ });
69
+ // 会话装配(origin 'trigger'——SessionOrigin 闭集五值无 issue 位,触发
70
+ // 谱系归并;workspaceRoot = worktree canonical 路径——fs fence/bash 沙箱
71
+ // 锚定;extraTools = 件注册的只读工具面经管道注册位并入)
72
+ const child = stack.manager.create({
73
+ origin: 'trigger',
74
+ workspaceRoot: req.cwd,
75
+ title: 'issue(headless)',
76
+ extraTools: () => req.tools,
77
+ });
78
+ const driver = child.driver;
79
+ const sessionId = child.sessionId;
80
+ let finished = false; // 终局已收口(settle 幂等护栏)
81
+ let running = false; // 在飞旗(watchdog 只在飞时执法——停靠/idle 期不误伤)
82
+ let abortCause; // watchdog abort 归因(外部 abort 缺席)
83
+ const eventsOf = () => driver.session.events();
84
+ const messagesUsed = () => countAssistantMessages(eventsOf());
85
+ let watchdog;
86
+ const stopWatchdog = () => {
87
+ if (watchdog !== undefined) {
88
+ clearInterval(watchdog);
89
+ watchdog = undefined;
90
+ }
91
+ };
92
+ /** 终局收口(一次结算 + 清轮询 + 双侧摘登记 + 单会话收口——subagent-factory 同律) */
93
+ const finish = (result) => {
94
+ if (finished)
95
+ return;
96
+ finished = true;
97
+ parked.delete(entry);
98
+ broadcast.unregister(entry); // 广播侧同笔摘(宿主级面不再扫描已收口项)
99
+ stopWatchdog();
100
+ stack.manager.retire(sessionId); // dismantle 终态停摆 + 摘活体登记(05 §7 retire 律)
101
+ settleOutcome(result);
102
+ };
103
+ /** 停靠登记(04 §5——outcome 悬置 + 会话上下文保留,驱动活体不 dismantle)。
104
+ * u-3 落词(04 §5 定形注②):预算语境停靠在目标会话落 session/paused
105
+ * (reason 'budget')——daemon 猝死后冷启动可恢复呈现;两触发位(起跑前
106
+ * 池检/watchdog 收口后)皆在 run 收口后或未起跑,词行「收口后落词」合规。
107
+ * 复停靠(唤醒轮再停靠)再落一笔——append 事实流,尾条即停靠态由
108
+ * SessionLiveState 推导 */
109
+ const parkNow = (reason) => {
110
+ parked.add(entry);
111
+ broadcast.register(entry); // 宿主级广播面登记(canAfford 恢复同播三面之一)
112
+ driver.session.append('session/paused', { reason: 'budget' });
113
+ warn(`issue 会话停靠(${sessionId}):${reason}——待 budget_extended 唤醒(04 §5 不落终态)`);
114
+ };
115
+ /**
116
+ * 单轮起跑编舞(首跑与唤醒轮共用):起跑前池检 → watchdog 启动 →
117
+ * submit → 终局映射。
118
+ */
119
+ const runRound = async (text, source, backgroundWake) => {
120
+ if (finished)
121
+ return;
122
+ // 起跑前池检(04 §5 有帽 run 池尽停靠——首跑与唤醒轮同律)
123
+ if (!canAfford()) {
124
+ parkNow('起跑前日池尽');
125
+ return;
126
+ }
127
+ abortCause = undefined;
128
+ running = true;
129
+ // watchdog 两执法(在飞期轮询——主 loop stream 道无预算闸,会话侧停靠
130
+ // 执法位在此;先帽后池序:每 issue 帽是编排纪律、日池是全局纪律)
131
+ watchdog = setInterval(() => {
132
+ if (!running || abortCause !== undefined)
133
+ return;
134
+ if (messagesUsed() >= req.budgetMessages) {
135
+ abortCause = 'cap';
136
+ driver.abort();
137
+ return;
138
+ }
139
+ if (!canAfford()) {
140
+ abortCause = 'budget';
141
+ driver.abort();
142
+ }
143
+ }, pollMs);
144
+ watchdog.unref();
145
+ let result;
146
+ try {
147
+ result = await driver.submit(text, { source, ...(backgroundWake ? { backgroundWake: true } : {}) });
148
+ }
149
+ catch (err) {
150
+ // 驱动层永不抛(契约位)——防御收口:诚实 failed 不悬挂编排层
151
+ running = false;
152
+ stopWatchdog();
153
+ finish({
154
+ status: 'failed',
155
+ messagesUsed: messagesUsed(),
156
+ reason: `会话提交异常(防御位——驱动层契约永不抛):${err instanceof Error ? err.message : String(err)}`,
157
+ });
158
+ return;
159
+ }
160
+ running = false;
161
+ stopWatchdog();
162
+ if (finished)
163
+ return; // dispose 已收口(迟到结算弃)
164
+ const used = messagesUsed();
165
+ if (result.status === 'completed') {
166
+ // needs-human 检测(04 §10 消费注执法细则):被拒审批在场 = 成果需
167
+ // 人审(保守偏向——判定纯事件载荷闭集,不猜模型文本)
168
+ const denied = deniedApprovalCount(eventsOf());
169
+ if (denied > 0) {
170
+ finish({
171
+ status: 'needs-human',
172
+ messagesUsed: used,
173
+ reason: `审批被拒 ${denied} 次(无人应答收口/守门拒——04 §9 fail-closed),成果需人审`,
174
+ });
175
+ return;
176
+ }
177
+ finish({ status: 'completed', messagesUsed: used, summary: lastAssistantText(eventsOf()) });
178
+ return;
179
+ }
180
+ if (result.status === 'failed') {
181
+ finish({ status: 'failed', messagesUsed: used, reason: result.errorMessage ?? 'run 失败(无错误详情)' });
182
+ return;
183
+ }
184
+ if (result.status === 'aborted') {
185
+ if (abortCause === 'budget') {
186
+ parkNow('run 中日池尽(watchdog 协作中止)');
187
+ return;
188
+ }
189
+ if (abortCause === 'cap') {
190
+ finish({
191
+ status: 'failed',
192
+ messagesUsed: used,
193
+ reason: `每 issue 预算帽耗尽(${req.budgetMessages} 条——04 §5 每 issue 帽与全局池两层分账)`,
194
+ });
195
+ return;
196
+ }
197
+ finish({ status: 'failed', messagesUsed: used, reason: 'run 被外部中止' });
198
+ return;
199
+ }
200
+ // injected / wake-refused(通道收执形):活体 driver 提交 injected 理论
201
+ // 不达;wake-refused 真实可达(连续后台唤醒超帽——鲸鱼任务诚实边界)
202
+ finish({
203
+ status: 'failed',
204
+ messagesUsed: used,
205
+ reason: result.status === 'wake-refused'
206
+ ? '连续后台唤醒超帽(04 §4 maxConsecutiveWakes=3——鲸鱼任务跨 4 个日池窗,需人工介入或提额)'
207
+ : `提交未起跑(${result.status}——理论不达防御位,诚实回执)`,
208
+ });
209
+ };
210
+ const entry = {
211
+ wake() {
212
+ parked.delete(entry); // 先摘(再停靠时 parkNow 重登记——双侧同笔)
213
+ broadcast.unregister(entry);
214
+ void runRound(WAKE_MESSAGE, WAKE_SOURCE, true);
215
+ },
216
+ dispose() {
217
+ finish({
218
+ status: 'paused',
219
+ reason: '宿主停机收口——停靠项保留 worktree 与授予(orphanScan 标注,重轮询再入走让位序)',
220
+ });
221
+ },
222
+ };
223
+ // 首轮起跑(fire——outcome promise 即回执,编排层自行 await)
224
+ void runRound(req.prompt, ISSUE_SUBMIT_SOURCE, false);
225
+ return { sessionId, outcome };
226
+ }
227
+ // 唤醒扫描(u-3 升格——04 §5 定形注①):原工厂私有 watcher 已升格宿主级
228
+ // 广播件(budget-broadcast.ts 装配根单真身三停靠面同播;电平判语义逐字
229
+ // 平移——有停靠项且 canAfford 恢复即触发,防环不靠边沿靠 driver 三帽)。
230
+ // 本工厂只持登记面(parkNow 双登记 / finish·wake 双侧摘)
231
+ return {
232
+ startHeadless,
233
+ dispose() {
234
+ for (const entry of [...parked])
235
+ entry.dispose(); // finish 内双侧摘登记
236
+ // 自建私有形同笔收口;注入形宿主真身归停机序——不可在此关闭
237
+ if (ownsBroadcast)
238
+ broadcast.dispose();
239
+ },
240
+ };
241
+ }
@@ -0,0 +1,372 @@
1
+ /**
2
+ * host/loader — 插件装载管线(03 §1.3/§1.4/§3/§5.7;批 12d)。
3
+ *
4
+ * 装载序 = 服务可用性驱动的 Kahn 轮次(§1.3「inject 求值后服务缺席即拒启」
5
+ * 的排序真源):逐行扫描,inject ⊆ 现行服务集者装载——apply 期间 ctx.provide
6
+ * 落新服务,后续轮次自然解锁依赖方;不动点余行 = inject 不可达(用户行
7
+ * 隔离降级 PLUGIN_INJECT_UNRESOLVED,core: 行 fail-loud 拒启——狗粮纪律)。
8
+ *
9
+ * 双轨装载(§1.4):core: 官方引用形对象直调 apply(零 jiti 零门禁——同仓
10
+ * 同版本);用户插件磁盘装载——jiti 实例(virtualModules 宿主面直注〔防双
11
+ * 实例〕+ import 门禁字面量腿〔transform throw 形 + fsCache 关——03 §3.3
12
+ * 批 12d 勘正〕+ interop default)。入口解析序三态承 manifest 件(12a):
13
+ * entry-file / 纯声明包(零码装载)/ default-export(包主入口)。
14
+ *
15
+ * 失败三档(§5.7):① 清单校验失败拒换——归调用方(parseEnabledRows 先行);
16
+ * ② 用户行装载失败 = 行级隔离降级——onBootFailure 记账(boot-failures 件
17
+ * 接线位)+ 其余行照常;③ disposer 回卷失败 = 降级存活(unload 聚合报告)。
18
+ * core: 行装载失败 = fail-loud 抛(已活行 LIFO 回卷后不留半装配)。
19
+ *
20
+ * 时钟(§3.4):apply 10s / 插件 disposer 回卷 1s——超时按失败处置不悬挂。
21
+ * ctx 面归调用方(createContext 注入——泛型 TCtx;注册动词族/服务目录随
22
+ * conversation/TUI 装配批定形)。
23
+ */
24
+ import { readFileSync } from 'node:fs';
25
+ import { dirname, join, resolve, sep } from 'node:path';
26
+ import { fileURLToPath } from 'node:url';
27
+ import { createJiti } from 'jiti';
28
+ import { BaseError } from '../contracts/index.js';
29
+ import { providerApiFace } from '../llm/index.js';
30
+ import { synthesizePluginConfig } from './config-schema.js';
31
+ import { createGateTransform } from './import-gate.js';
32
+ import { withoutSessionAnchor } from './session-anchor.js';
33
+ import { withTimeout } from './runtime.js';
34
+ /**
35
+ * 声明载荷目录解析 + 包根包含执法(生态启动批 eco-1——pi 反面实证:pi-lens
36
+ * 声明 `"skills": ["../../skills"]` 裸 resolve 逃逸包根仍被装载)。resolve
37
+ * 归一后须等于基或以 `<base>/` 开头(同 isWithinRoots 谓词形——port.ts/
38
+ * skills manage 同律);违例抛 PLUGIN_SHAPE_INVALID(磁盘行折失败行隔离降级)。
39
+ */
40
+ function resolveDeclaredDirs(dirs, base, pluginId) {
41
+ return (dirs ?? []).map((dir) => {
42
+ const abs = resolve(base, dir);
43
+ if (abs !== base && !abs.startsWith(base + sep)) {
44
+ throw new BaseError('PLUGIN_SHAPE_INVALID', `声明载荷目录逃逸装载基(插件 ${pluginId}:${dir} 解析为 ${abs}、出界基 ${base}——须相对包根内路径)`);
45
+ }
46
+ return abs;
47
+ });
48
+ }
49
+ /** core: 行声明基(宿主包根——src/host 与 dist/host 双形态上推两级皆包根) */
50
+ const hostPackageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
51
+ /** core: 行 fail-loud 抛形(已活行已 LIFO 回卷——不留半装配) */
52
+ export class CorePluginBootError extends BaseError {
53
+ failure;
54
+ constructor(failure, options) {
55
+ super(failure.code, `core: 官方件装载失败拒启(${failure.id}):${failure.message}`, options);
56
+ this.failure = failure;
57
+ }
58
+ }
59
+ /**
60
+ * 装载管线主入口(async——jiti ESM 求值 + 虚拟面 lazy 直注)。
61
+ */
62
+ export async function loadPlugins(options) {
63
+ const applyBudgetMs = options.applyBudgetMs ?? 10_000;
64
+ const disposerBudgetMs = options.disposerBudgetMs ?? 1_000;
65
+ const virtualFaces = options.virtualFaces ?? (await loadDefaultVirtualFaces());
66
+ const activated = [];
67
+ const failed = [];
68
+ const skipped = [];
69
+ // 回卷栈:依激活序入栈——unload LIFO 全序(§5.7 档③)
70
+ const disposeStack = [];
71
+ let unloaded = false;
72
+ const pending = [];
73
+ for (const row of options.plan) {
74
+ if (row.disabled)
75
+ skipped.push({ id: row.id, reason: 'disabled(启用行禁用位)' });
76
+ else
77
+ pending.push(row);
78
+ }
79
+ // Kahn 轮次:每轮取首条 inject 可满足行装载(apply 落新服务解锁后续轮)
80
+ while (pending.length > 0) {
81
+ const index = pending.findIndex((row) => injectOf(row).every((name) => options.services.get(name) !== undefined));
82
+ if (index === -1)
83
+ break; // 不动点——余行 inject 不可达
84
+ const [row] = pending.splice(index, 1);
85
+ try {
86
+ await loadRow(row, { options, virtualFaces, applyBudgetMs, disposeStack, activated });
87
+ }
88
+ catch (err) {
89
+ const failure = toFailure(row.id, err);
90
+ if (row.kind === 'core') {
91
+ // core: fail-loud 拒启——已活行 LIFO 回卷后抛(不留半装配)
92
+ await rollback(disposeStack, disposerBudgetMs);
93
+ throw new CorePluginBootError(failure, { cause: err });
94
+ }
95
+ failed.push(failure); // 用户行隔离降级——其余行照常(档②)
96
+ options.onBootFailure?.(row.id, row.manifest?.version ?? '', failure);
97
+ }
98
+ }
99
+ // 不动点余行:inject 不可达(含环——环成员互等永不满足)
100
+ for (const row of pending.splice(0)) {
101
+ const missing = injectOf(row).filter((name) => options.services.get(name) === undefined);
102
+ const failure = {
103
+ id: row.id,
104
+ code: 'PLUGIN_INJECT_UNRESOLVED',
105
+ message: `硬依赖服务不可达:${missing.length > 0 ? missing.join('、') : '(环依赖)'}`,
106
+ };
107
+ if (row.kind === 'core') {
108
+ await rollback(disposeStack, disposerBudgetMs);
109
+ throw new CorePluginBootError(failure);
110
+ }
111
+ failed.push(failure);
112
+ options.onBootFailure?.(row.id, row.manifest.version ?? '', failure);
113
+ }
114
+ return {
115
+ activated,
116
+ failed,
117
+ skipped,
118
+ unload: async () => {
119
+ if (unloaded)
120
+ return { disposed: [], failed: [] }; // 幂等
121
+ unloaded = true;
122
+ const disposed = [];
123
+ const unloadFailed = [];
124
+ while (disposeStack.length > 0) {
125
+ const item = disposeStack.pop();
126
+ try {
127
+ await withTimeout(Promise.resolve(item.fn()), disposerBudgetMs, `disposer ${item.id}`);
128
+ disposed.push(item.id);
129
+ }
130
+ catch (err) {
131
+ // 档③降级存活——聚合报告不炸
132
+ unloadFailed.push({ id: item.id, error: err });
133
+ }
134
+ }
135
+ return { disposed, failed: unloadFailed };
136
+ },
137
+ };
138
+ }
139
+ /** 单行装载(行内失败上抛——由调用侧分档处置) */
140
+ async function loadRow(row, state) {
141
+ const { options, applyBudgetMs } = state;
142
+ try {
143
+ // 开门授予集只随磁盘行透传(core: 行无 opens 位——读侧已拒,结构性保证)
144
+ const ctx = options.createContext(row.id, row.kind === 'disk' ? row.opens : undefined);
145
+ // 装载位配置合成(ix-3——03 §1.2 合成序):configSchema 缺席 = 行为零变化
146
+ // (原值直传零校验——双轨同判);在场 = 行 config 整值覆盖宿主默认 → 字段级
147
+ // 校验 → default 兜底 → secret 凭证直取 → 未声明键透传。磁盘轨宿主默认
148
+ // 回落 = 清单 config 键(schema/值分键后值单源;旧「config 当 typebox
149
+ // schema 消费」路径已拆);core 轨 = 引用形 config 位(既有回落不变)。
150
+ const defaultConfig = row.kind === 'core' ? row.reference.config : row.manifest.config;
151
+ const config = synthesizePluginConfig({
152
+ pluginId: row.id,
153
+ ...(row.kind === 'core'
154
+ ? row.reference.configSchema !== undefined && { fields: row.reference.configSchema }
155
+ : row.manifest.configSchema !== undefined && { fields: row.manifest.configSchema }),
156
+ ...(row.config !== undefined && { rowConfig: row.config }),
157
+ ...(defaultConfig !== undefined && { defaultConfig }),
158
+ ...(options.getConfigSecret !== undefined && {
159
+ getSecret: (key) => options.getConfigSecret?.(row.id, key),
160
+ }),
161
+ ...(options.allowMissingRequiredSecret === true && { allowMissingRequiredSecret: true }),
162
+ ...(options.warn !== undefined && { warn: options.warn }),
163
+ });
164
+ if (row.kind === 'core') {
165
+ // §1.4 直调轨:零 jiti 零门禁;其余契约(时钟/回卷)与磁盘插件同轨
166
+ warnMissingOptional(row.id, row.reference.optionalInject, state);
167
+ await invokeApply(row.id, row.reference.apply, ctx, config, applyBudgetMs, state.disposeStack,
168
+ // 官方件宿主面(core 行独有——03 §2.1 官方件异步续段通道;缺席位
169
+ // undefined = 直调形)
170
+ state.options.coreHostChannel?.(row.id));
171
+ // core 行声明基 = 宿主包根(06 §11.6 位 6 注记:core: 件出厂内容经位 4
172
+ // 声明即达——官方件技能/agents 从属资源与磁盘件同形同律)
173
+ state.activated.push({
174
+ id: row.id,
175
+ skillDirs: resolveDeclaredDirs(row.reference.skills, hostPackageRoot, row.id),
176
+ agentDirs: resolveDeclaredDirs(row.reference.agents, hostPackageRoot, row.id),
177
+ });
178
+ return;
179
+ }
180
+ // 磁盘轨:行 config 形状执法已随 typebox 旧路径拆除(ix-3——行 config
181
+ // 须对象在 readEnabledRows 行校验层;字段级校验/secret 明文拒在上方
182
+ // 合成步统一执法——configSchema 缺席时零变化原值直传)
183
+ // 纯声明包(entryPlan 三态之一):零码装载——声明载荷清单随行激活(相对
184
+ // pluginDir 解析 + 包根包含执法;skills/agents 任一缺席 = 该清单空数组)
185
+ if (row.manifest.entryPlan.kind === 'declared-payload') {
186
+ state.activated.push({
187
+ id: row.id,
188
+ skillDirs: resolveDeclaredDirs(row.manifest.skills, row.pluginDir, row.id),
189
+ agentDirs: resolveDeclaredDirs(row.manifest.agents, row.pluginDir, row.id),
190
+ });
191
+ return;
192
+ }
193
+ const module = await loadDiskModule(row, state.options.jitiFactory, state.virtualFaces);
194
+ const apply = module.default;
195
+ if (typeof apply !== 'function') {
196
+ throw new BaseError('PLUGIN_SHAPE_INVALID', `入口 default export 缺席或非函数(插件 ${row.id})`);
197
+ }
198
+ // 模块内 inject 执法(装载位——两段式的执法腿):硬依赖缺席拒启
199
+ const missing = (module.inject ?? []).filter((name) => options.services.get(name) === undefined);
200
+ if (missing.length > 0) {
201
+ throw new BaseError('PLUGIN_INJECT_UNRESOLVED', `硬依赖服务不可达(模块声明):${missing.join('、')}`);
202
+ }
203
+ warnMissingOptional(row.id, module.optionalInject, state);
204
+ await invokeApply(row.id, apply, ctx, config, applyBudgetMs, state.disposeStack);
205
+ state.activated.push({
206
+ id: row.id,
207
+ skillDirs: resolveDeclaredDirs(row.manifest.skills, row.pluginDir, row.id),
208
+ agentDirs: resolveDeclaredDirs(row.manifest.agents, row.pluginDir, row.id),
209
+ });
210
+ }
211
+ finally {
212
+ // 行收口即关窗(finally 语义——成功/失败/零码行皆达;03 §2.1 装载窗口)
213
+ options.onApplySettled?.(row.id);
214
+ }
215
+ }
216
+ /**
217
+ * 软依赖缺席 warn(03 §1.3「缺席仅记 warn、注入值 undefined」——诚实降级
218
+ * 不拒启)。逐名点名:present 名不记、全 present 不记、warn 落点缺席不记。
219
+ */
220
+ function warnMissingOptional(pluginId, names, state) {
221
+ if (names === undefined || names.length === 0 || state.options.warn === undefined)
222
+ return;
223
+ const missing = names.filter((name) => state.options.services.get(name) === undefined);
224
+ if (missing.length > 0) {
225
+ state.options.warn(`插件 ${pluginId} 软依赖缺席(注入值 undefined——诚实降级):${missing.join('、')}`);
226
+ }
227
+ }
228
+ /** 磁盘模块装载(jiti 轨——入口解析三态的文件级落点) */
229
+ async function loadDiskModule(row, jitiFactory, virtualFaces) {
230
+ const faces = { ...virtualFaces }; // 面快照(本插件 jiti 实例的直注集)
231
+ const opts = {
232
+ virtualModules: faces,
233
+ transform: createGateTransform({ pluginId: row.id, pluginDir: row.pluginDir }),
234
+ };
235
+ const jiti = jitiFactory?.(row.pluginDir, opts) ??
236
+ createJiti(join(row.pluginDir, 'package.json'), {
237
+ virtualModules: faces,
238
+ transform: opts.transform,
239
+ interopDefault: true,
240
+ moduleCache: true,
241
+ fsCache: false, // 门禁身份不入缓存键——缓存命中即跳过 transform 穿门(03 §3.3 勘正)
242
+ });
243
+ const entryId = row.manifest.entryPlan.kind === 'entry-file'
244
+ ? join(row.pluginDir, row.manifest.entryPlan.entry)
245
+ : join(row.pluginDir, resolvePkgMain(row.pluginDir));
246
+ let namespace;
247
+ try {
248
+ namespace = (await jiti.import(entryId));
249
+ }
250
+ catch (err) {
251
+ throw mapJitiError(row.id, err);
252
+ }
253
+ return {
254
+ default: namespace.default,
255
+ inject: asStringArray(namespace.inject, row.id, 'inject'),
256
+ optionalInject: asStringArray(namespace.optionalInject, row.id, 'optionalInject'),
257
+ events: asStringArray(namespace.events, row.id, 'events'),
258
+ };
259
+ }
260
+ /**
261
+ * 缺省虚拟面五键(宿主模块对象直注——防双实例)。
262
+ *
263
+ * typebox 三键 = 宿主进程同一份(插件经虚拟键拿到的 Type/Value/Compile 与
264
+ * 宿主共用模块实例——Kind 符号失配结构性不可能);berry-agent 主键 =
265
+ * contracts 公开面;berry-agent/llm 键 = llm/provider-face 的 providerApiFace
266
+ * (生态启动批 eco-1 接线——face 件/API 注册表/surface 三处早已在册而装载
267
+ * 注入缺席,provider 插件装载必炸于模块解析的三读分叉收口)。
268
+ * `berry-agent/sqlite` 键仍随 persist SqliteFace 落码批接入(无真身不进表)。
269
+ *
270
+ * export(装机面落码批 #10——install 收割腿与装载器同源共用:虚拟面单源,
271
+ * 不散拷)。
272
+ */
273
+ export async function loadDefaultVirtualFaces() {
274
+ const [contracts, typebox, typeboxValue, typeboxCompile] = await Promise.all([
275
+ import('../contracts/index.js'),
276
+ import('typebox'),
277
+ import('typebox/value'),
278
+ import('typebox/compile'),
279
+ ]);
280
+ return {
281
+ 'berry-agent': contracts,
282
+ typebox,
283
+ 'typebox/value': typeboxValue,
284
+ 'typebox/compile': typeboxCompile,
285
+ // 顶层导出形直注(非命名空间整包)——与 surface.json 收割的 4 符号面同源
286
+ 'berry-agent/llm': providerApiFace,
287
+ };
288
+ }
289
+ /** jiti 错误映射(三码分流:门禁拒 / 入口不可达 / 装载失败) */
290
+ function mapJitiError(pluginId, err) {
291
+ const message = err instanceof Error ? err.message : String(err);
292
+ if (err instanceof BaseError && err.code === 'PLUGIN_IMPORT_FORBIDDEN')
293
+ return err; // 字面量腿原样透传
294
+ if (message.includes('PLUGIN_IMPORT_FORBIDDEN')) {
295
+ // 兜底腿毒丸(裸 Error 码前缀形——毒丸经 jiti eval 不得 import 宿主面)
296
+ return new BaseError('PLUGIN_IMPORT_FORBIDDEN', `[${pluginId}] ${message}`, { cause: err });
297
+ }
298
+ if (message.includes('Cannot find module') || err.code === 'MODULE_NOT_FOUND') {
299
+ return new BaseError('PLUGIN_ENTRY_UNRESOLVED', `入口模块解析失败(插件 ${pluginId}):${message}`, {
300
+ cause: err,
301
+ });
302
+ }
303
+ return new BaseError('PLUGIN_LOAD_FAILED', `jiti 装载失败(插件 ${pluginId}):${message}`, { cause: err });
304
+ }
305
+ /** apply 三态包装:时钟帽 + disposer 收栈 + 错误归一 PLUGIN_APPLY_FAILED */
306
+ async function invokeApply(id, apply, ctx, config, applyBudgetMs, disposeStack, host) {
307
+ let returned;
308
+ try {
309
+ // 时钟帽包裹(返回值经闭包捕获——withTimeout 面 Promise<void>)+
310
+ // 装载期语境遮蔽(ix-2——07 §4.3 档位 2 两处遮蔽之二):apply 即使处
311
+ // 命令异步链内(/reload、/plugins install 等命令触发的装载流)亦按无锚
312
+ // 判——装载期结构性无自动锚(apply 内 ctx.ui 阻塞三件拒、单向原语
313
+ // no-op warn)。ALS.exit 语境跟随 async:apply 全执行段均在遮蔽内。
314
+ await withTimeout(withoutSessionAnchor(() => Promise.resolve(apply(ctx, config, host)).then((r) => {
315
+ returned = r;
316
+ })), applyBudgetMs, `apply ${id}`);
317
+ }
318
+ catch (err) {
319
+ const message = err instanceof Error ? err.message : String(err);
320
+ throw new BaseError('PLUGIN_APPLY_FAILED', `apply 抛错/超时(插件 ${id},帽 ${applyBudgetMs}ms):${message}`, {
321
+ cause: err,
322
+ });
323
+ }
324
+ if (typeof returned === 'function') {
325
+ disposeStack.push({ id, fn: returned }); // 激活序入栈——unload LIFO
326
+ }
327
+ }
328
+ /**
329
+ * 包主入口解析(default-export 态:package.json main;缺省 index.js)。
330
+ * export(装机面落码批 #10——install 收割腿与装载器同源共用:入口解析序
331
+ * 单源,不散拷)。
332
+ */
333
+ export function resolvePkgMain(pluginDir) {
334
+ try {
335
+ const pkg = JSON.parse(readFileSync(join(pluginDir, 'package.json'), 'utf8'));
336
+ return pkg.main !== undefined && pkg.main.length > 0 ? pkg.main : 'index.js';
337
+ }
338
+ catch {
339
+ return 'index.js'; // package.json 不可读——入口解析交给 jiti 失败红(ENTRY_UNRESOLVED)
340
+ }
341
+ }
342
+ /** named export 字符串数组形执法(坏形 = 形状违例) */
343
+ function asStringArray(value, id, field) {
344
+ if (value === undefined)
345
+ return undefined;
346
+ if (!Array.isArray(value) || value.some((v) => typeof v !== 'string')) {
347
+ throw new BaseError('PLUGIN_SHAPE_INVALID', `入口 named export ${field} 须字符串数组(插件 ${id})`);
348
+ }
349
+ return value;
350
+ }
351
+ /** 行 inject 面(排序位):core: 注册表声明 / 磁盘行预收割位(两段式——装载位另执法) */
352
+ function injectOf(row) {
353
+ return row.kind === 'core' ? (row.reference.inject ?? []) : (row.inject ?? []);
354
+ }
355
+ /** 不动点回卷(core: fail-loud 前置清场——已活行 LIFO) */
356
+ async function rollback(disposeStack, disposerBudgetMs) {
357
+ while (disposeStack.length > 0) {
358
+ const item = disposeStack.pop();
359
+ try {
360
+ await withTimeout(Promise.resolve(item.fn()), disposerBudgetMs, `disposer ${item.id}`);
361
+ }
362
+ catch {
363
+ // 清场路径静默——主错误(拒启因由)才是呈报面
364
+ }
365
+ }
366
+ }
367
+ /** 失败归一(BaseError 直取码身份;裸错误归 PLUGIN_LOAD_FAILED 位) */
368
+ function toFailure(id, err) {
369
+ if (err instanceof BaseError)
370
+ return { id, code: err.code, message: err.message };
371
+ return { id, code: 'PLUGIN_LOAD_FAILED', message: err instanceof Error ? err.message : String(err) };
372
+ }