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.
- package/LICENSE +21 -0
- package/README.md +96 -0
- package/dist/.api-emit.stamp +1 -0
- package/dist/.build-meta.json +6 -0
- package/dist/agent/events.js +1 -0
- package/dist/agent/index.js +2 -0
- package/dist/agent/loop.js +139 -0
- package/dist/agent/queue.js +119 -0
- package/dist/agent/stream.js +62 -0
- package/dist/agent/tools-batch.js +221 -0
- package/dist/agent/types.js +8 -0
- package/dist/api/berry-agent-llm.d.ts +18 -0
- package/dist/api/berry-agent.d.ts +9 -0
- package/dist/api/surface.json +1632 -0
- package/dist/api/tsconfig.paths.json +12 -0
- package/dist/api/typebox-compile.d.ts +2 -0
- package/dist/api/typebox-value.d.ts +2 -0
- package/dist/api/typebox.d.ts +2 -0
- package/dist/browser/cdp.js +256 -0
- package/dist/browser/codes.js +31 -0
- package/dist/browser/discover.js +107 -0
- package/dist/browser/engine.js +169 -0
- package/dist/browser/index.js +17 -0
- package/dist/browser/install.js +342 -0
- package/dist/browser/page.js +321 -0
- package/dist/browser/service.js +158 -0
- package/dist/browser/tools.js +198 -0
- package/dist/browser/types.js +81 -0
- package/dist/channels/ask-queue.js +82 -0
- package/dist/channels/commands.js +129 -0
- package/dist/channels/engine/cell.js +143 -0
- package/dist/channels/engine/diff.js +143 -0
- package/dist/channels/engine/engine.js +416 -0
- package/dist/channels/engine/index.js +8 -0
- package/dist/channels/engine/input-keys.js +86 -0
- package/dist/channels/engine/input.js +596 -0
- package/dist/channels/engine/memory-io.js +71 -0
- package/dist/channels/engine/process-io.js +38 -0
- package/dist/channels/engine/types.js +17 -0
- package/dist/channels/engine/width.js +122 -0
- package/dist/channels/index.js +15 -0
- package/dist/channels/registry.js +56 -0
- package/dist/channels/sdk/admit.js +29 -0
- package/dist/channels/sdk/backend.js +102 -0
- package/dist/channels/sdk/cursor.js +56 -0
- package/dist/channels/sdk/index.js +20 -0
- package/dist/channels/sdk/jsonl.js +208 -0
- package/dist/channels/sdk/protocol.js +24 -0
- package/dist/channels/sdk/schema.js +91 -0
- package/dist/channels/sdk/wire-core.js +446 -0
- package/dist/channels/service.js +169 -0
- package/dist/channels/tui/autocomplete/autocomplete.js +42 -0
- package/dist/channels/tui/autocomplete/file-mentions.js +106 -0
- package/dist/channels/tui/autocomplete/popup.js +148 -0
- package/dist/channels/tui/autocomplete/provider.js +7 -0
- package/dist/channels/tui/autocomplete/token.js +68 -0
- package/dist/channels/tui/backend/ansi-rows.js +211 -0
- package/dist/channels/tui/backend/main-screen.js +207 -0
- package/dist/channels/tui/backend/osc.js +91 -0
- package/dist/channels/tui/backend/transcript.js +230 -0
- package/dist/channels/tui/backend/tui-backend.js +915 -0
- package/dist/channels/tui/editor/editor-model.js +543 -0
- package/dist/channels/tui/editor/editor-view.js +140 -0
- package/dist/channels/tui/editor/editor.js +275 -0
- package/dist/channels/tui/editor/undo-stack.js +35 -0
- package/dist/channels/tui/editor/visual-lines.js +126 -0
- package/dist/channels/tui/editor/word-nav.js +106 -0
- package/dist/channels/tui/history/history-viewer.js +403 -0
- package/dist/channels/tui/index.js +3 -0
- package/dist/channels/tui/layout.js +167 -0
- package/dist/channels/tui/markdown/blocks.js +111 -0
- package/dist/channels/tui/markdown/inline.js +75 -0
- package/dist/channels/tui/markdown/markdown.js +182 -0
- package/dist/channels/tui/memory/memory-viewer.js +540 -0
- package/dist/channels/tui/overlay/alt-screen.js +105 -0
- package/dist/channels/tui/overlay/overlay.js +57 -0
- package/dist/channels/tui/overlay/select-confirm.js +156 -0
- package/dist/channels/tui/panels/todo-panel.js +54 -0
- package/dist/channels/tui/panels/tool-progress-panel.js +93 -0
- package/dist/channels/tui/scroll/scroll-view.js +254 -0
- package/dist/channels/tui/status/status-line.js +76 -0
- package/dist/channels/tui/text.js +38 -0
- package/dist/channels/tui/theme.js +41 -0
- package/dist/channels/types.js +11 -0
- package/dist/channels/ui-core.js +209 -0
- package/dist/checkpoint/capture.js +45 -0
- package/dist/checkpoint/codes.js +33 -0
- package/dist/checkpoint/command.js +82 -0
- package/dist/checkpoint/gate.js +48 -0
- package/dist/checkpoint/index.js +20 -0
- package/dist/checkpoint/restore.js +172 -0
- package/dist/checkpoint/store.js +247 -0
- package/dist/checkpoint/types.js +4 -0
- package/dist/checkpoint/walk.js +146 -0
- package/dist/compaction/ccr-tools.js +112 -0
- package/dist/compaction/ccr.js +84 -0
- package/dist/compaction/codes.js +21 -0
- package/dist/compaction/index.js +18 -0
- package/dist/compaction/policy.js +200 -0
- package/dist/compaction/service.js +422 -0
- package/dist/compaction/slots.js +121 -0
- package/dist/compaction/types.js +10 -0
- package/dist/context/codes.js +40 -0
- package/dist/context/events.js +160 -0
- package/dist/context/index.js +13 -0
- package/dist/context/logger.js +136 -0
- package/dist/context/scope.js +182 -0
- package/dist/context/workspace.js +104 -0
- package/dist/contracts/agent-events.d.ts +73 -0
- package/dist/contracts/agent-events.js +16 -0
- package/dist/contracts/api.d.ts +249 -0
- package/dist/contracts/api.js +299 -0
- package/dist/contracts/approval.d.ts +37 -0
- package/dist/contracts/approval.js +9 -0
- package/dist/contracts/env-ref.d.ts +41 -0
- package/dist/contracts/env-ref.js +32 -0
- package/dist/contracts/errors.d.ts +56 -0
- package/dist/contracts/errors.js +266 -0
- package/dist/contracts/events.d.ts +70 -0
- package/dist/contracts/events.js +329 -0
- package/dist/contracts/index.d.ts +32 -0
- package/dist/contracts/index.js +26 -0
- package/dist/contracts/llm.d.ts +275 -0
- package/dist/contracts/llm.js +12 -0
- package/dist/contracts/messages.d.ts +63 -0
- package/dist/contracts/messages.js +65 -0
- package/dist/contracts/redact.d.ts +46 -0
- package/dist/contracts/redact.js +223 -0
- package/dist/contracts/tools.d.ts +197 -0
- package/dist/contracts/tools.js +33 -0
- package/dist/contracts/types.d.ts +302 -0
- package/dist/contracts/types.js +50 -0
- package/dist/contracts/ui.d.ts +122 -0
- package/dist/contracts/ui.js +15 -0
- package/dist/conversation/agent-service.js +61 -0
- package/dist/conversation/approval-wiring.js +82 -0
- package/dist/conversation/backoff.js +38 -0
- package/dist/conversation/codes.js +47 -0
- package/dist/conversation/control-tools.js +102 -0
- package/dist/conversation/control.js +210 -0
- package/dist/conversation/driver.js +922 -0
- package/dist/conversation/index.js +12 -0
- package/dist/conversation/model-visible.js +169 -0
- package/dist/conversation/open-tools.js +126 -0
- package/dist/conversation/reseed.js +132 -0
- package/dist/conversation/sessions.js +215 -0
- package/dist/conversation/todo.js +203 -0
- package/dist/conversation/types.js +35 -0
- package/dist/conversation/wiring.js +204 -0
- package/dist/credentials/codes.js +59 -0
- package/dist/credentials/commands.js +186 -0
- package/dist/credentials/env-ref.js +34 -0
- package/dist/credentials/index.js +28 -0
- package/dist/credentials/migration.js +18 -0
- package/dist/credentials/oauth.js +266 -0
- package/dist/credentials/refresh.js +152 -0
- package/dist/credentials/secrets.js +118 -0
- package/dist/credentials/types.js +30 -0
- package/dist/exec/bash.js +279 -0
- package/dist/exec/codes.js +37 -0
- package/dist/exec/env.js +99 -0
- package/dist/exec/environment.js +39 -0
- package/dist/exec/git-guard.js +623 -0
- package/dist/exec/index.js +24 -0
- package/dist/exec/registry.js +120 -0
- package/dist/exec/spawn.js +281 -0
- package/dist/exec/tail.js +77 -0
- package/dist/exec/types.js +5 -0
- package/dist/goal/codes.js +33 -0
- package/dist/goal/command.js +175 -0
- package/dist/goal/fold.js +121 -0
- package/dist/goal/gates.js +122 -0
- package/dist/goal/index.js +22 -0
- package/dist/goal/migration.js +48 -0
- package/dist/goal/service.js +501 -0
- package/dist/goal/todo-tool.js +174 -0
- package/dist/goal/types.js +1 -0
- package/dist/goal/update-tool.js +46 -0
- package/dist/host/approval-cmd.js +276 -0
- package/dist/host/assembly.js +1146 -0
- package/dist/host/boot-failures.js +104 -0
- package/dist/host/budget-advisory.js +74 -0
- package/dist/host/budget-broadcast.js +50 -0
- package/dist/host/builtins.js +54 -0
- package/dist/host/cli.js +544 -0
- package/dist/host/codes.js +120 -0
- package/dist/host/config-schema.js +195 -0
- package/dist/host/conversation-stack.js +654 -0
- package/dist/host/core-plugins.js +1685 -0
- package/dist/host/credentials-cmd.js +42 -0
- package/dist/host/disclosure.js +30 -0
- package/dist/host/dispatch.js +105 -0
- package/dist/host/doors-cmd.js +154 -0
- package/dist/host/dump-config.js +44 -0
- package/dist/host/hook-dispatch-guard.js +27 -0
- package/dist/host/import-gate.js +153 -0
- package/dist/host/index.js +54 -0
- package/dist/host/issue-session.js +241 -0
- package/dist/host/loader.js +372 -0
- package/dist/host/main.js +161 -0
- package/dist/host/manifest.js +381 -0
- package/dist/host/mcp-entry.js +53 -0
- package/dist/host/plugin-boot.js +952 -0
- package/dist/host/plugin-context.js +672 -0
- package/dist/host/plugin-install.js +480 -0
- package/dist/host/plugin-reload.js +124 -0
- package/dist/host/plugin-store.js +462 -0
- package/dist/host/plugin-tools.js +359 -0
- package/dist/host/plugin-uninstall.js +257 -0
- package/dist/host/plugins-cmd.js +302 -0
- package/dist/host/plugins-command.js +130 -0
- package/dist/host/plugins-config.js +195 -0
- package/dist/host/prompt-sections.js +133 -0
- package/dist/host/run-entry.js +544 -0
- package/dist/host/runtime.js +209 -0
- package/dist/host/scheduler-tick.js +294 -0
- package/dist/host/serve-daemon.js +501 -0
- package/dist/host/serve-entry.js +279 -0
- package/dist/host/session-anchor.js +41 -0
- package/dist/host/sessions-cmd.js +219 -0
- package/dist/host/sessions-face.js +47 -0
- package/dist/host/settings-store.js +112 -0
- package/dist/host/signals.js +69 -0
- package/dist/host/single-instance.js +105 -0
- package/dist/host/subagent-factory.js +291 -0
- package/dist/host/testkit/harness.js +283 -0
- package/dist/host/testkit/index.js +14 -0
- package/dist/host/testkit/matrix.js +302 -0
- package/dist/host/tool-policy-store.js +177 -0
- package/dist/host/triggers.js +212 -0
- package/dist/host/tui-entry.js +216 -0
- package/dist/host/webui-bridge.js +165 -0
- package/dist/issue/codes.js +38 -0
- package/dist/issue/filter.js +146 -0
- package/dist/issue/github.js +116 -0
- package/dist/issue/index.js +17 -0
- package/dist/issue/mount.js +70 -0
- package/dist/issue/poll.js +58 -0
- package/dist/issue/service.js +341 -0
- package/dist/issue/tools.js +70 -0
- package/dist/issue/types.js +34 -0
- package/dist/issue/webhook.js +126 -0
- package/dist/llm/codes.js +77 -0
- package/dist/llm/complete.js +196 -0
- package/dist/llm/events.js +15 -0
- package/dist/llm/index.js +24 -0
- package/dist/llm/inflight.js +65 -0
- package/dist/llm/model-id.js +58 -0
- package/dist/llm/provider-face.d.ts +32 -0
- package/dist/llm/provider-face.js +34 -0
- package/dist/llm/recovery.js +146 -0
- package/dist/llm/runtime.js +46 -0
- package/dist/llm/stream-fn.js +147 -0
- package/dist/lsp/codes.js +31 -0
- package/dist/lsp/connection.js +147 -0
- package/dist/lsp/frame.js +110 -0
- package/dist/lsp/index.js +16 -0
- package/dist/lsp/inject.js +58 -0
- package/dist/lsp/instance.js +264 -0
- package/dist/lsp/service.js +522 -0
- package/dist/lsp/tools.js +53 -0
- package/dist/lsp/types.js +201 -0
- package/dist/mcp/bridge.js +222 -0
- package/dist/mcp/codes.js +21 -0
- package/dist/mcp/index.js +19 -0
- package/dist/mcp/jsonrpc.js +230 -0
- package/dist/mcp/service.js +125 -0
- package/dist/mcp/tools.js +145 -0
- package/dist/mcp/types.js +108 -0
- package/dist/memory/cite.js +131 -0
- package/dist/memory/codes.js +50 -0
- package/dist/memory/command.js +71 -0
- package/dist/memory/consolidate.js +219 -0
- package/dist/memory/cycle.js +109 -0
- package/dist/memory/dao.js +878 -0
- package/dist/memory/diff.js +238 -0
- package/dist/memory/extract.js +181 -0
- package/dist/memory/fts.js +58 -0
- package/dist/memory/index.js +42 -0
- package/dist/memory/inject.js +354 -0
- package/dist/memory/merge.js +178 -0
- package/dist/memory/migration.js +132 -0
- package/dist/memory/pollution.js +59 -0
- package/dist/memory/port.js +385 -0
- package/dist/memory/review.js +201 -0
- package/dist/memory/scan.js +89 -0
- package/dist/memory/tools.js +508 -0
- package/dist/memory/types.js +140 -0
- package/dist/obs/codes.js +38 -0
- package/dist/obs/db.js +115 -0
- package/dist/obs/index.js +20 -0
- package/dist/obs/rollup.js +61 -0
- package/dist/obs/service.js +363 -0
- package/dist/obs/session-tools.js +282 -0
- package/dist/obs/session-view.js +225 -0
- package/dist/obs/tool.js +97 -0
- package/dist/obs/types.js +1 -0
- package/dist/persist/audit.js +63 -0
- package/dist/persist/aux.js +39 -0
- package/dist/persist/codes.js +35 -0
- package/dist/persist/index.js +30 -0
- package/dist/persist/load-history.js +50 -0
- package/dist/persist/migrations.js +37 -0
- package/dist/persist/paths.js +108 -0
- package/dist/persist/persistence.js +229 -0
- package/dist/persist/schema.js +99 -0
- package/dist/persist/secret-box.js +95 -0
- package/dist/persist/store.js +706 -0
- package/dist/persist/write-behind.js +238 -0
- package/dist/safety/approval.js +171 -0
- package/dist/safety/bwrap.js +123 -0
- package/dist/safety/codes.js +57 -0
- package/dist/safety/danger.js +505 -0
- package/dist/safety/gate.js +226 -0
- package/dist/safety/index.js +24 -0
- package/dist/safety/presets.js +73 -0
- package/dist/safety/roots.js +139 -0
- package/dist/safety/sandbox.js +214 -0
- package/dist/safety/seatbelt.js +88 -0
- package/dist/safety/sensitive.js +34 -0
- package/dist/safety/tool-policy.js +203 -0
- package/dist/safety/types.js +13 -0
- package/dist/scheduler/codes.js +54 -0
- package/dist/scheduler/cron-backend.js +135 -0
- package/dist/scheduler/engine.js +280 -0
- package/dist/scheduler/gates.js +60 -0
- package/dist/scheduler/index.js +25 -0
- package/dist/scheduler/migration.js +22 -0
- package/dist/scheduler/runner.js +171 -0
- package/dist/scheduler/schedule.js +193 -0
- package/dist/scheduler/service.js +283 -0
- package/dist/scheduler/tick.js +139 -0
- package/dist/scheduler/types.js +1 -0
- package/dist/sdk/http.js +865 -0
- package/dist/sdk/index.js +22 -0
- package/dist/sdk/mcp.js +289 -0
- package/dist/sdk/plugin-route-registry.js +126 -0
- package/dist/sdk/plugin-routes.js +149 -0
- package/dist/sdk/security.js +116 -0
- package/dist/sdk/types.js +32 -0
- package/dist/session/budget.js +159 -0
- package/dist/session/codes.js +40 -0
- package/dist/session/derive.js +180 -0
- package/dist/session/event-data.js +1 -0
- package/dist/session/fork.js +72 -0
- package/dist/session/import-gates.js +148 -0
- package/dist/session/index.js +18 -0
- package/dist/session/recover.js +103 -0
- package/dist/session/session.js +279 -0
- package/dist/session/snapshot.js +107 -0
- package/dist/skills/agents.js +242 -0
- package/dist/skills/codes.js +51 -0
- package/dist/skills/discovery.js +266 -0
- package/dist/skills/frontmatter.js +215 -0
- package/dist/skills/index.js +28 -0
- package/dist/skills/load.js +118 -0
- package/dist/skills/manage.js +201 -0
- package/dist/skills/registry.js +138 -0
- package/dist/skills/render.js +124 -0
- package/dist/skills/sections.js +171 -0
- package/dist/skills/types.js +21 -0
- package/dist/subagent/codes.js +50 -0
- package/dist/subagent/declarative.js +55 -0
- package/dist/subagent/index.js +19 -0
- package/dist/subagent/notify.js +51 -0
- package/dist/subagent/provide.js +7 -0
- package/dist/subagent/registry.js +149 -0
- package/dist/subagent/service.js +338 -0
- package/dist/subagent/surface.js +44 -0
- package/dist/subagent/tool.js +153 -0
- package/dist/subagent/types.js +25 -0
- package/dist/tools/apply-patch.js +164 -0
- package/dist/tools/codes.js +109 -0
- package/dist/tools/fs.js +501 -0
- package/dist/tools/index.js +17 -0
- package/dist/tools/observed.js +99 -0
- package/dist/tools/pipeline.js +176 -0
- package/dist/tools/protected-read.js +54 -0
- package/dist/tools/registry.js +250 -0
- package/dist/tools/search.js +451 -0
- package/dist/tools/worktree.js +327 -0
- package/dist/web/codes.js +37 -0
- package/dist/web/gate.js +43 -0
- package/dist/web/hygiene.js +195 -0
- package/dist/web/index.js +16 -0
- package/dist/web/service.js +207 -0
- package/dist/web/ssrf-guard.js +44 -0
- package/dist/web/tool.js +76 -0
- package/dist/web/types.js +10 -0
- package/dist/webui/assets/index-B-t2O8gh.js +22 -0
- package/dist/webui/assets/index-DOlgSZ9Q.css +2 -0
- package/dist/webui/index.html +14 -0
- package/dist/webui/index.js +14 -0
- package/dist/webui/server.js +549 -0
- package/dist/webui/types.js +38 -0
- package/examples/README.md +26 -0
- package/examples/minimal-code-plugin/entry.js +43 -0
- package/examples/minimal-code-plugin/package.json +12 -0
- package/examples/pure-skill-pack/package.json +14 -0
- package/examples/pure-skill-pack/skills/markdown-table/SKILL.md +32 -0
- package/package.json +86 -0
|
@@ -0,0 +1,952 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* host/plugin-boot — 插件装载装配序(03 §5.3/§5.4/§5.7/§2.4 生命周期组;批 12f-2b)。
|
|
3
|
+
*
|
|
4
|
+
* 职责链(11 步序——读侧三源 → 合成 → 装载 → 收口):
|
|
5
|
+
* ① enabled.yaml 读侧(§5.3):缺席 = 全 core: 内置态(首启零文件零负担);
|
|
6
|
+
* 损坏 = **fail-loud 拒启给修复指引**(yaml 解析错/行校验败 → PLUGIN_ROW_INVALID
|
|
7
|
+
* 抛——与装机账本 warn 降级分立两律);memory 形无数据目录同缺席语义。
|
|
8
|
+
* ② core: 内置全启 + 用户行覆盖(§5.3):core 注册表成员全进计划;用户行
|
|
9
|
+
* core: 同名行字段级后写胜出(config/disabled——config 整值替换非合并)。
|
|
10
|
+
* ③ 装机账本读侧(§5.4):损坏 = warn 点名 + 空账本降级(不 brick);
|
|
11
|
+
* 条目 `installPath` 解析磁盘行装机目录(绝对直用/相对 join 数据目录)。
|
|
12
|
+
* ④ 磁盘行合成(§5.4 boot 读侧消费):package.json 过清单校验
|
|
13
|
+
* (parseManifest official:false)成装载计划行;账本缺席/目录不可读/
|
|
14
|
+
* 清单坏形 = 行级隔离降级(§5.7 档②——失败面 + boot-failures 记账)。
|
|
15
|
+
* ⑤ 钩子词汇预注册(§2.4 主表 41 词镜像——已注册词过滤幂等;与 open 域
|
|
16
|
+
* 工具词集无交叠,共存安全)。
|
|
17
|
+
* ⑥ ServiceBag 接共享根作用域(§2.2 provide 表行——跨插件可见面;get 走
|
|
18
|
+
* tryGet——Kahn 轮次可用性判定;'secrets' fork 级席位以标记位应答,c-3)。
|
|
19
|
+
* ⑦ 逐插件 ctx 装配(12f-2a 件):per-plugin fork(effect 回卷隔离)+
|
|
20
|
+
* createPluginContext + secrets 面自域绑定(c-3——03 §2.2 第十面,
|
|
21
|
+
* core:credentials 席在场且 store 注入时 fork.provide)+ sessions-
|
|
22
|
+
* control(e4-3)/compaction(U4-3)两席位 fork 绑定;
|
|
23
|
+
* onApplySettled → closeWindow(行收口即关窗)。
|
|
24
|
+
* ⑧ loadPlugins 接线:onBootFailure → recordBootFailure 记账、activated →
|
|
25
|
+
* clearBootFailure 清名(横幅只报仍坏行);memory 形诊断面整跳。
|
|
26
|
+
* ⑨ closer 'plugin-unload':report.unload()(apply disposer LIFO)后 fork
|
|
27
|
+
* 作用域逆序 dispose(ctx.effect 回卷)——注册序在 conversation 栈之后 =
|
|
28
|
+
* 插件卸载晚于对话栈拆解(drain 序即注册序);Job 归属围栏收口腿(Job
|
|
29
|
+
* 消费面批桥二)夹在两序间——activated 逐插件 closeOwner(04 §10 定形)。
|
|
30
|
+
* ⑩ --no-plugins 短路(07 §六/§5.7):装载面整跳——core: 与用户行都不装,
|
|
31
|
+
* 空报告/零计数/不注册 closer/不发生命周期事件;注册表仍交空形(消费面稳定)。
|
|
32
|
+
* ⑪ 生命周期事件(§2.4 生命周期组):装载收口**批量补发**(report 迭代发
|
|
33
|
+
* plugin/activated·failed·skipped + composition/reloaded 三清单载荷)——
|
|
34
|
+
* 逐行时点发射位装载器仍未开(/reload 批已飞未启——2026-09-13 勘正,
|
|
35
|
+
* 维持收口批量补发形态);合成失败行并入 failed 面。
|
|
36
|
+
*
|
|
37
|
+
* 消费腿实况(2026-09-11 勘正——原「挂账」注记所指批次均已飞):boot 级
|
|
38
|
+
* 工具注册表经 assembly bootTools 取值器并入会话工具面(conversation-stack);
|
|
39
|
+
* promptSections 经 assembly pluginSections 取值器由 driver systemPrompt 装配位消费。
|
|
40
|
+
*/
|
|
41
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
42
|
+
import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
|
|
43
|
+
import { parse as parseYaml } from 'yaml';
|
|
44
|
+
import { BaseError } from '../contracts/index.js';
|
|
45
|
+
// internal 桶机制符号深导(02 §4.3 #2 深挖面册首条——API 治理批 2 分桶:
|
|
46
|
+
// 机制符号非插件 API 不进公开根,内核消费全深导 contracts/api.js)
|
|
47
|
+
import { materializeHostFace } from '../contracts/api.js';
|
|
48
|
+
import { EventDispatch } from '../context/index.js';
|
|
49
|
+
import { createToolRegistry } from '../tools/index.js';
|
|
50
|
+
import { createEnvRefResolver, createSecretsFace, pluginNamespace } from '../credentials/index.js';
|
|
51
|
+
// 跨会话操控受理器(e4-3——sessions-control fork 绑定;host→conversation 边在册)
|
|
52
|
+
import { bindControlForPlugin, SESSIONS_CONTROL_SERVICE } from '../conversation/index.js';
|
|
53
|
+
import { SDK_ROUTES_SERVICE } from '../sdk/index.js';
|
|
54
|
+
// 程序化子代理物化消费腿(遗漏审计批 G——注册即派生):物化真身 + toolDeps
|
|
55
|
+
// 类型(与 core:subagent 件声明式腿同源单物化函数)
|
|
56
|
+
import { createDeclarativeAgentTool } from '../subagent/index.js';
|
|
57
|
+
import { clearBootFailure, formatPluginFailureText, recordBootFailure } from './boot-failures.js';
|
|
58
|
+
import { loadPlugins } from './loader.js';
|
|
59
|
+
import { enabledYamlPath, parseEnabledRows, parseManifest } from './manifest.js';
|
|
60
|
+
import { PLUGIN_HOOK_VOCABULARY, createPluginContext } from './plugin-context.js';
|
|
61
|
+
import { PromptSectionRegistry } from './prompt-sections.js';
|
|
62
|
+
/**
|
|
63
|
+
* 'secrets' 席位可满足标记(c-3):ServiceBag Kahn 可满足判专用占位——
|
|
64
|
+
* secrets 真身是 fork 级逐插件绑定(createContext 落绑定),共享根结构性
|
|
65
|
+
* 无此名,标记位使 inject: ['secrets'] 声明可解。恒真值非 undefined 即可,
|
|
66
|
+
* 结构性不出装载器(插件消费面只见 fork 绑定真身)。
|
|
67
|
+
*/
|
|
68
|
+
const SECRETS_SEAT_MARKER = { seat: 'secrets' };
|
|
69
|
+
/**
|
|
70
|
+
* 'sessions-control' 席位可满足标记(e4-3):与 SECRETS_SEAT_MARKER 同构——
|
|
71
|
+
* 受理器真身是 fork 级逐插件绑定(caller 闭包铸归因),共享根结构性无此名,
|
|
72
|
+
* 标记位使 inject: ['sessions-control'] 声明可解。
|
|
73
|
+
*/
|
|
74
|
+
const SESSIONS_CONTROL_SEAT_MARKER = { seat: 'sessions-control' };
|
|
75
|
+
/**
|
|
76
|
+
* 'compaction' 席位可满足标记(U4-3——03 §2.2 第十二面):与 SECRETS_SEAT_
|
|
77
|
+
* MARKER 同构——压缩策略面真身是 fork 级逐插件绑定(席位执法/窗口真源绑本
|
|
78
|
+
* 插件 handle),共享根结构性无此名,标记位使 inject: ['compaction'] 声明
|
|
79
|
+
* 可解。
|
|
80
|
+
*/
|
|
81
|
+
const COMPACTION_SEAT_MARKER = { seat: 'compaction' };
|
|
82
|
+
/**
|
|
83
|
+
* 'sdk-routes' 席位可满足标记(U5-2——03 §2.2 第十三面):与 SECRETS_SEAT_
|
|
84
|
+
* MARKER 同构——路由受理面真身是 fork 级逐插件绑定(受理裁决/窗/门真源绑本
|
|
85
|
+
* 插件 handle),共享根结构性无此名,标记位使 inject: ['sdk-routes'] 声明
|
|
86
|
+
* 可解。
|
|
87
|
+
*/
|
|
88
|
+
const SDK_ROUTES_SEAT_MARKER = { seat: 'sdk-routes' };
|
|
89
|
+
/** 缺省真盘实现(读失败一律 null——文件缺席语义);导出 = /reload 预检装配位复用(单源) */
|
|
90
|
+
export function defaultFs() {
|
|
91
|
+
return {
|
|
92
|
+
read: (path) => {
|
|
93
|
+
try {
|
|
94
|
+
return readFileSync(path, 'utf8');
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
write: (path, text) => writeFileSync(path, text),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* per-plugin 工具名账工厂(装载史批 h-3——05 §9 世代行 activated 成员
|
|
105
|
+
* tools 列真值源):bootPlugins 每周期新实例(/reload 换代即新账不串代)。
|
|
106
|
+
* toolsOf = 收口时点在册集快照(世代行落笔即冻结——历史代不受后续回卷影响)。
|
|
107
|
+
*/
|
|
108
|
+
export function createPluginToolLedger() {
|
|
109
|
+
const account = new Map();
|
|
110
|
+
return {
|
|
111
|
+
add: (pluginId, toolName) => {
|
|
112
|
+
let names = account.get(pluginId);
|
|
113
|
+
if (names === undefined) {
|
|
114
|
+
names = new Set();
|
|
115
|
+
account.set(pluginId, names);
|
|
116
|
+
}
|
|
117
|
+
names.add(toolName);
|
|
118
|
+
},
|
|
119
|
+
remove: (pluginId, toolName) => {
|
|
120
|
+
account.get(pluginId)?.delete(toolName);
|
|
121
|
+
},
|
|
122
|
+
toolsOf: (pluginId) => [...(account.get(pluginId) ?? [])],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* 插件装载主入口(async——装载管线内含 jiti ESM 求值)。
|
|
127
|
+
*
|
|
128
|
+
* counts 口径:total = 计划行数 + 合成失败行数;enabled = report.activated
|
|
129
|
+
* 行数;failed = 合成失败 + report.failed 行数(skipped 行不入三数——禁用
|
|
130
|
+
* 非失败非启用,披露段不虚报)。
|
|
131
|
+
*/
|
|
132
|
+
export async function bootPlugins(options) {
|
|
133
|
+
const warn = options.warn ?? ((message) => process.stderr.write(`${message}\n`));
|
|
134
|
+
const fs = options.fs ?? defaultFs();
|
|
135
|
+
// boot 级注册表:两消费面(ctx 注册动词 + TUI 披露)同实例
|
|
136
|
+
const tools = createToolRegistry(options.dispatch);
|
|
137
|
+
// 程序化子代理物化回调(消费腿——遗漏审计批 G,03 §2.2 行 109「注册即
|
|
138
|
+
// 派生」):registry 本体闭包铸造;owner 恒 'core:subagent'(agent_ 派生
|
|
139
|
+
// 族域归属——03 §2.7 前缀闸豁免域;注册者归因由 service providers 册
|
|
140
|
+
// owner 分域键承载);声明式腿同函数单条物化(createDeclarativeAgentTool
|
|
141
|
+
// 单源)。缺席 = 诊断形(动词只落 service 位)
|
|
142
|
+
const subagentToolMaterializer = options.subagentToolDeps !== undefined
|
|
143
|
+
? (def) => tools.register({ ...createDeclarativeAgentTool(def, options.subagentToolDeps), owner: 'core:subagent' })
|
|
144
|
+
: undefined;
|
|
145
|
+
// 漂移观测接线(03 §2.5 节区稳定性纪律——cache 经济批 ca-2):承诺稳定段
|
|
146
|
+
// 内容漂移经 warn 落日志(性能事件 fail-open——不拒不炸请求);注册集变更
|
|
147
|
+
// 清基线重立在 registry 内执法(装载面真变更不落 warn)
|
|
148
|
+
const promptSections = new PromptSectionRegistry({
|
|
149
|
+
onDrift: ({ slot, owner }) => warn(`提示词段 ${slot}(${owner})物化内容漂移——注册面缺省承诺会话内稳定,builder 输出跨请求变化即前缀缓存失效;若属可变内容请声明 volatile:{reason}(03 §2.5)`),
|
|
150
|
+
});
|
|
151
|
+
const hostFace = materializeHostFace({
|
|
152
|
+
version: options.version,
|
|
153
|
+
apiVersion: options.apiVersion ?? '1.0',
|
|
154
|
+
capabilities: [],
|
|
155
|
+
experimentalKeys: [],
|
|
156
|
+
});
|
|
157
|
+
// ⑩ 安全模式短路:装载面整跳;注册表仍交空形(消费面形态统一)
|
|
158
|
+
if (options.noPlugins === true) {
|
|
159
|
+
const emptyReport = {
|
|
160
|
+
activated: [],
|
|
161
|
+
failed: [],
|
|
162
|
+
skipped: [],
|
|
163
|
+
unload: async () => ({ disposed: [], failed: [] }),
|
|
164
|
+
};
|
|
165
|
+
// 世代快照照落(05 §9 边沿定形——世代存在且为空:--no-plugins 安全模式
|
|
166
|
+
// 也是一次真实装载形态);face 缺席 = 诊断形不落行(诚实缺席律)
|
|
167
|
+
options.loadHistory?.recordLoadGeneration({ activated: [], skipped: [], failed: [] });
|
|
168
|
+
return {
|
|
169
|
+
report: emptyReport,
|
|
170
|
+
counts: { total: 0, enabled: 0, failed: 0 },
|
|
171
|
+
tools,
|
|
172
|
+
toolsOf: () => [],
|
|
173
|
+
configFaceOf: () => undefined,
|
|
174
|
+
promptSections,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
// ⑤ 钩子词汇预注册(03 §2.4 主表镜像——一词两册装配序律:预注册在前,
|
|
178
|
+
// 各装配面〔open-tools/sessions〕自举注册幂等跳过已注册词;已注册词过滤幂等)
|
|
179
|
+
const hookWords = PLUGIN_HOOK_VOCABULARY.map((h) => h.name).filter((name) => !options.dispatch.isRegistered(name));
|
|
180
|
+
options.dispatch.registerEventNames(hookWords);
|
|
181
|
+
// ① enabled.yaml 读侧(损坏 fail-loud——两律分立在件头注释);行集 + 顶层
|
|
182
|
+
// doors 段(开门制扩展批——doors 段经 boot 读侧解析即装载期 fail-loud 执法)
|
|
183
|
+
const { rows, doors } = readEnabledRows(options.runtime.dataDir, fs);
|
|
184
|
+
// ③ 装机账本读侧(损坏 warn 降级——与启用清单 fail-loud 分立)
|
|
185
|
+
const ledger = readLedger(options.runtime.dataDir, fs, warn);
|
|
186
|
+
// ②④ 计划合成(core 内置全启 + overlay + 磁盘行账本解析)
|
|
187
|
+
const synthesized = synthesizePlan({
|
|
188
|
+
rows,
|
|
189
|
+
corePlugins: options.corePlugins ?? [],
|
|
190
|
+
dataDir: options.runtime.dataDir,
|
|
191
|
+
ledger,
|
|
192
|
+
fs,
|
|
193
|
+
});
|
|
194
|
+
const synthesisFailures = synthesized.synthesisFailures;
|
|
195
|
+
// ⑫ 快速试件合成行(--plugin-file——03 §7 八不变式,生态启动批 eco-3a):
|
|
196
|
+
// 纯内存注入装载计划多一行(行 id = 保留字 QUICK_TEST_ROW_ID),同装载
|
|
197
|
+
// 管线(合成 DiskPluginSpec——jiti/门禁/Kahn/时钟帽全真)无专用旁路;零
|
|
198
|
+
// 落盘退出即消失(enabled.yaml/ledger.json 不沾;boot-failures 记账过滤见
|
|
199
|
+
// onBootFailure——防幽灵残账在下次 boot 横幅误报)。路径/清单/撞名失败
|
|
200
|
+
// fail-loud 拒启(PLUGIN_ROW_INVALID——试件行违例同族码)。/reload 换代
|
|
201
|
+
// reapply 恒 runBoot(false) 单参 = 行自然消失(不变式 4——assembly 参形
|
|
202
|
+
// 单源保证,本函数零额外状态)。
|
|
203
|
+
const plan = [...synthesized.plan];
|
|
204
|
+
if (options.pluginFile !== undefined) {
|
|
205
|
+
plan.push(resolveQuickTestRow(options.pluginFile, plan, fs));
|
|
206
|
+
}
|
|
207
|
+
// ⑧ 记账路径(memory 形无数据目录——诊断面整跳)
|
|
208
|
+
const bookkeepingPath = options.runtime.dataDir === null ? null : join(options.runtime.dataDir, 'boot-failures.json');
|
|
209
|
+
const bookkeepingFs = toBootFailuresFs(fs);
|
|
210
|
+
// 合成失败行:档②语义记账(装载未达——warn 横幅与 boot-failures 皆见;
|
|
211
|
+
// obs-a——failure 细节随账落 lastError/lastFailedAt)
|
|
212
|
+
for (const failure of synthesisFailures) {
|
|
213
|
+
warn(`插件装载失败(${failure.id}):${formatPluginFailureText(failure)}`);
|
|
214
|
+
if (bookkeepingPath !== null)
|
|
215
|
+
recordBootFailure(bookkeepingPath, failure.id, '', failure, bookkeepingFs);
|
|
216
|
+
}
|
|
217
|
+
// ⑥⑦ ctx 装配族:ServiceBag 接共享根 + 逐插件 fork + 行收口关窗
|
|
218
|
+
const handles = new Map();
|
|
219
|
+
const pluginScopes = []; // 激活序入栈——closer 逆序 dispose
|
|
220
|
+
// 世代死域旗(03 §10.1 异步续段开窗批连带定形——开窗器 fail-closed):本代
|
|
221
|
+
// 卸载/换代即翻 true;本代铸的全部开窗器(coreHostChannel 闭包常驻物——
|
|
222
|
+
// /reload 换代后旧代续段仍可持旧开窗器)此后开窗即拒,防 owner 归已死代
|
|
223
|
+
// 的幽灵注册行。置位真源 = 下方 unloadAll 顶部(/reload rollback 腿与
|
|
224
|
+
// shutdown 同一回卷口——回卷最前即封窗,拆除期零新开窗)
|
|
225
|
+
let generationDead = false;
|
|
226
|
+
// core:credentials 件席在场判(c-3——03 §10.9 禁用语义:enabled.yaml 禁
|
|
227
|
+
// core:credentials ⇒ plan 行 disabled ⇒ secrets 面整体缺席诚实缺席律;
|
|
228
|
+
// plan 含禁用行〔loadPlugins 前置过滤〕,判 !row.disabled)
|
|
229
|
+
const secretsWiring = options.secrets;
|
|
230
|
+
const secretsSeatActive = secretsWiring !== undefined && plan.some((row) => row.id === 'core:credentials' && !row.disabled);
|
|
231
|
+
// 注入腿席位接线(c-4——03 §10.9 注入腿):与 ctx.secrets 席位门**同一双
|
|
232
|
+
// 条件**(secrets 注入在场 × plan 行 core:credentials 未禁)⇒ 共享根供
|
|
233
|
+
// 'credentials-env-ref' 展开器——exec 件 apply 期拾取进 spawn 管道(本
|
|
234
|
+
// provide 先于 loadPlugins 全程,装载序无关)。缺席 ⇒ 服务缺席 ⇒ env
|
|
235
|
+
// 引用形 fail-loud(exec 侧拒以字面值注入——单一名册语义:件在 = 凭证
|
|
236
|
+
// 代管全腿在场,件去 = 全腿缺席,无半开态)
|
|
237
|
+
if (secretsWiring !== undefined && secretsSeatActive) {
|
|
238
|
+
// owner 位 = 装配位标识(同主刷新律——04 §6 定形注):/reload 换代重跑本
|
|
239
|
+
// 装配位 = 同主重供原位刷新,不再结构性撞名自炸
|
|
240
|
+
options.scope.provide('credentials-env-ref', createEnvRefResolver(secretsWiring.store), 'host:plugin-boot');
|
|
241
|
+
}
|
|
242
|
+
// 操控受理器席位判(e4-3——host 内建机制非 core 件,无件席门:在场即绑)
|
|
243
|
+
const controlSeatActive = options.sessionsControl !== undefined;
|
|
244
|
+
// 压缩席位容器判(U4-3——host 内建机制非 core 件,无件席门:在场即绑)
|
|
245
|
+
const compactionSeatActive = options.compaction !== undefined;
|
|
246
|
+
// 插件道路由受理器席位判(U5-2——03 §10.6 core:sdk 禁用降级:件禁用 ⇒
|
|
247
|
+
// sdk HTTP 面整体缺席 ⇒ 插件道路由随之全灭。判 plan 行 core:sdk 未禁用
|
|
248
|
+
// ——secrets 席同构双条件〔受理器在场 × 件席在场〕)
|
|
249
|
+
const sdkRoutesSeatActive = options.sdkRoutes !== undefined && plan.some((row) => row.id === 'core:sdk' && !row.disabled);
|
|
250
|
+
const services = {
|
|
251
|
+
get: (name) =>
|
|
252
|
+
// 'secrets' 是 fork 级逐插件绑定面(本插件独见——createContext 落真身
|
|
253
|
+
// 绑定),共享根结构性无此名。Kahn 可满足判以标记位应答:声明
|
|
254
|
+
// inject: ['secrets'] 的磁盘行合法可解(服务自装载前即在场)。
|
|
255
|
+
// 标记不外泄——ServiceBag 装载器私有,插件消费面走 fork 绑定真身。
|
|
256
|
+
name === 'secrets' && secretsSeatActive
|
|
257
|
+
? SECRETS_SEAT_MARKER
|
|
258
|
+
: name === SESSIONS_CONTROL_SERVICE && controlSeatActive
|
|
259
|
+
? SESSIONS_CONTROL_SEAT_MARKER
|
|
260
|
+
: name === 'compaction' && compactionSeatActive
|
|
261
|
+
? COMPACTION_SEAT_MARKER
|
|
262
|
+
: name === SDK_ROUTES_SERVICE && sdkRoutesSeatActive
|
|
263
|
+
? SDK_ROUTES_SEAT_MARKER
|
|
264
|
+
: options.scope.tryGet(name),
|
|
265
|
+
// owner 位透传(同主刷新律——04 §6 定形注):委派面带主直达共享根
|
|
266
|
+
provide: (name, value, owner) => options.scope.provide(name, value, owner),
|
|
267
|
+
};
|
|
268
|
+
// per-plugin 工具名账(装载史批 h-3——05 §9 世代行 tools 列真值源):本
|
|
269
|
+
// boot 周期单实例,createContext 逐插件透传(ctx.tools.register 包壳层记
|
|
270
|
+
// 账——注册成功入账、disposer 出账);/reload reapply 重跑本函数 = 换代
|
|
271
|
+
// 即新账,旧代 disposer 回卷只动旧代账(世代行已快照落笔,无害)
|
|
272
|
+
const toolLedger = createPluginToolLedger();
|
|
273
|
+
const createContext = (pluginId, opens) => {
|
|
274
|
+
const fork = options.scope.fork();
|
|
275
|
+
pluginScopes.push(fork);
|
|
276
|
+
const handle = createPluginContext({
|
|
277
|
+
pluginId,
|
|
278
|
+
scope: fork,
|
|
279
|
+
dispatch: options.dispatch,
|
|
280
|
+
tools,
|
|
281
|
+
// 工具名账透传(h-3——register 包壳层记账;缺席(本面不存在缺位)只
|
|
282
|
+
// 发生在 createPluginContext 直测形,bootPlugins 恒注)
|
|
283
|
+
toolLedger,
|
|
284
|
+
commands: options.commands,
|
|
285
|
+
llm: options.llm,
|
|
286
|
+
promptSections,
|
|
287
|
+
// 钩子派发段 guard 开合面透传(ca-3——全部插件 ctx 共享同一全局深度计数)
|
|
288
|
+
...(options.hookDispatchGuard !== undefined ? { hookDispatchGuard: options.hookDispatchGuard } : {}),
|
|
289
|
+
// ctx.ui 消费腿通道核窄面 + 降档 warn 出口透传(ix-2——fork 级联共享
|
|
290
|
+
// 单真身;缺席时阻塞三件/notify/hasAudience 响亮缺位、单向原语降档)
|
|
291
|
+
...(options.channelsUi !== undefined ? { channelsUi: options.channelsUi } : {}),
|
|
292
|
+
...(options.uiWarn !== undefined ? { uiWarn: options.uiWarn } : {}),
|
|
293
|
+
// ctx.provide 委派共享根(§2.2 表行——跨插件可见)。owner 恒 = 本插件 id
|
|
294
|
+
// 由本包装闭包注入(owner 真源 = 装载器非自报——插件面签名不变仍两参;
|
|
295
|
+
// /reload 换代重跑 = 同插件 id 同主重供原位刷新,04 §6 同主刷新律)
|
|
296
|
+
provide: (name, value) => services.provide(name, value, pluginId),
|
|
297
|
+
hostFace,
|
|
298
|
+
// 触发器注册表受局面透传(C 批 C-2——缺席时 ctx.triggers.register 响亮缺位)
|
|
299
|
+
...(options.triggers !== undefined ? { triggers: options.triggers } : {}),
|
|
300
|
+
// 子代理注册面受局面透传(D 批 D-2——缺席时 ctx.agent.registerSubagentProvider 响亮缺位)
|
|
301
|
+
...(options.subagents !== undefined ? { subagents: options.subagents } : {}),
|
|
302
|
+
// 程序化子代理物化回调透传(消费腿——遗漏审计批 G;缺席 = 动词只落 service 位)
|
|
303
|
+
...(subagentToolMaterializer !== undefined ? { subagentToolMaterializer } : {}),
|
|
304
|
+
// 界面后端注册面受局面透传(U3 批 U3-4——缺席时 ctx.channels.registerUiBackend 响亮缺位)
|
|
305
|
+
...(options.uiBackends !== undefined ? { uiBackends: options.uiBackends } : {}),
|
|
306
|
+
// 会话血缘判定面透传(e2-4——缺席时 ctx.events.subscribeSessionLifecycle
|
|
307
|
+
// tree 档响亮缺位;self/all 档不消费)
|
|
308
|
+
...(options.sessionLineage !== undefined ? { sessionLineage: options.sessionLineage } : {}),
|
|
309
|
+
// 审计流写入位透传(U3 批 U3-5——AuditFace append 窄面结构兼容 auditSink)
|
|
310
|
+
...(options.audit !== undefined ? { auditSink: options.audit } : {}),
|
|
311
|
+
// 高危面开门授予集(03 §4.6 批 U2——磁盘行 opens 经 loader 透传至此)
|
|
312
|
+
...(opens !== undefined ? { opens } : {}),
|
|
313
|
+
// 进程级 doors 段活体取值器(开门制扩展批——observe-cross 专属分立判定位)
|
|
314
|
+
...(options.crossDoors !== undefined ? { crossDoors: options.crossDoors } : {}),
|
|
315
|
+
onHookTimeout: (id, hookName, err) => warn(`插件 ${id} 钩子 ${hookName} 超时:${err instanceof Error ? err.message : String(err)}`),
|
|
316
|
+
});
|
|
317
|
+
// secrets 面绑定(c-3——03 §2.2 第十面):fork 级提供 = 本插件独见(共享
|
|
318
|
+
// 根单槽容不下逐插件身份绑定——fork 遮蔽合法)。getOpens/inWriteWindow
|
|
319
|
+
// 晚绑 handle 真源(装载代内授予面快照 / 回调窗深度计数)
|
|
320
|
+
if (secretsSeatActive) {
|
|
321
|
+
// oauth 受局面合流(c-6):注册表来自 assembly(共享单真身),窗两源
|
|
322
|
+
// 绑本插件 handle(enterHostCallback 开窗器 + inLoadWindow 装载窗判定)
|
|
323
|
+
// ——registerOAuthFlow 的装载窗执法与 invoke 的回调窗包裹由此成完整形
|
|
324
|
+
const { oauthRegistry, ...restWiring } = secretsWiring;
|
|
325
|
+
fork.provide('secrets', createSecretsFace({
|
|
326
|
+
...restWiring,
|
|
327
|
+
pluginId,
|
|
328
|
+
getOpens: () => handle.grantedOpens,
|
|
329
|
+
inWriteWindow: () => handle.inHostCallback,
|
|
330
|
+
...(oauthRegistry !== undefined
|
|
331
|
+
? {
|
|
332
|
+
oauth: {
|
|
333
|
+
registry: oauthRegistry,
|
|
334
|
+
// 世代死域 fail-closed(03 §10.9 oauth 死域批 + §10.1 同律
|
|
335
|
+
// 第二实例):开窗器系闭包常驻物,换代/卸载后死代流 invoke
|
|
336
|
+
// 即拒——防 owner 归已死代的幽灵注册行;与 unloadAll 流摘除
|
|
337
|
+
// 清理面两道闸并存(纵深位,非替代)
|
|
338
|
+
openWriteWindow: () => {
|
|
339
|
+
if (generationDead) {
|
|
340
|
+
throw new BaseError('PLUGIN_WINDOW_CLOSED', `oauth 流开窗被拒:插件 ${pluginId} 所属装载代已卸载/已换代(03 §10.9——域已死即窗恒闭)`);
|
|
341
|
+
}
|
|
342
|
+
return handle.enterHostCallback();
|
|
343
|
+
},
|
|
344
|
+
inLoadWindow: () => handle.inLoadWindow,
|
|
345
|
+
},
|
|
346
|
+
}
|
|
347
|
+
: {}),
|
|
348
|
+
}));
|
|
349
|
+
}
|
|
350
|
+
// sessions-control 面绑定(e4-3——03 §2.2 第十一面):fork 级提供 =
|
|
351
|
+
// 本插件独见(caller 归因闭包铸造防冒名——传入面无 caller 位,覆写
|
|
352
|
+
// 单源)。缺席 = ctx.get 响亮 CONTEXT_SERVICE_MISSING(诚实缺席律)
|
|
353
|
+
if (controlSeatActive) {
|
|
354
|
+
fork.provide(SESSIONS_CONTROL_SERVICE, bindControlForPlugin(pluginId, options.sessionsControl));
|
|
355
|
+
}
|
|
356
|
+
// compaction 面绑定(U4-3——03 §2.2 第十二面):fork 级提供 = 本插件
|
|
357
|
+
// 独见(席位执法在 slots 容器——两动词装载窗 only 严于通律,窗真源绑
|
|
358
|
+
// 本插件 handle.inLoadWindow 晚绑真源);fork.effect 兜底卸载回收
|
|
359
|
+
// (动词 disposer 是手动面——双保险;effect 回卷随 fork dispose 序,
|
|
360
|
+
// 晚于插件 apply disposer 的 LIFO 回卷)
|
|
361
|
+
if (compactionSeatActive) {
|
|
362
|
+
fork.provide('compaction', options.compaction.bindForPlugin({ pluginId, inLoadWindow: () => handle.inLoadWindow }));
|
|
363
|
+
const slots = options.compaction;
|
|
364
|
+
fork.effect(() => () => slots.releaseFor(pluginId));
|
|
365
|
+
}
|
|
366
|
+
// sdk-routes 面绑定(U5-2——03 §2.2 第十三面):fork 级提供 = 本插件
|
|
367
|
+
// 独见(受理序窗/门真源绑本插件 handle——inLoadWindow/grantedOpens
|
|
368
|
+
// 晚绑真源;/reload 换代重跑本函数 = 新 handle 新授予面,门复检自然成
|
|
369
|
+
// ——撤授予位后旧路由经下方 effect 回收不残留);fork.effect 兜底卸载
|
|
370
|
+
// 回收(register 摘除 fn 是手动面——双保险,compaction 同律)
|
|
371
|
+
if (sdkRoutesSeatActive) {
|
|
372
|
+
const registry = options.sdkRoutes;
|
|
373
|
+
fork.provide(SDK_ROUTES_SERVICE, registry.bindForPlugin({
|
|
374
|
+
pluginId,
|
|
375
|
+
getOpens: () => handle.grantedOpens,
|
|
376
|
+
inLoadWindow: () => handle.inLoadWindow,
|
|
377
|
+
}));
|
|
378
|
+
fork.effect(() => () => registry.releaseFor(pluginId));
|
|
379
|
+
}
|
|
380
|
+
handles.set(pluginId, handle);
|
|
381
|
+
return handle.ctx;
|
|
382
|
+
};
|
|
383
|
+
const loaded = await loadPlugins({
|
|
384
|
+
plan,
|
|
385
|
+
services,
|
|
386
|
+
createContext,
|
|
387
|
+
warn,
|
|
388
|
+
// 插件配置 secret 读面(ix-3——03 §1.2 合成序⑤):宿主装载序从凭证盒直取
|
|
389
|
+
// plugin:<id> 域 config:<key> 注回合成 config(secrets 面缺席 = 诊断/替身形
|
|
390
|
+
// secret 恒缺席,required secret 拒载照常——诚实缺席律)
|
|
391
|
+
...(options.secrets !== undefined
|
|
392
|
+
? {
|
|
393
|
+
getConfigSecret: (pluginId, key) => options.secrets?.store.getCredential(pluginNamespace(pluginId), `config:${key}`)?.apiKey,
|
|
394
|
+
}
|
|
395
|
+
: {}),
|
|
396
|
+
// required-secret 诊断豁免(07 §5 :memory: 同构纪律):诊断形凭证盒结构性
|
|
397
|
+
// 恒空——缺席拒降级 warn 提示行装载照走(真实装载形不豁免,拒载照常)
|
|
398
|
+
...(options.runtime.memory ? { allowMissingRequiredSecret: true } : {}),
|
|
399
|
+
onApplySettled: (pluginId) => handles.get(pluginId)?.closeWindow(), // 行收口即关窗(finally 语义)
|
|
400
|
+
// 官方件宿主面铸造(2026-09-13 真模型四轮 C 组——03 §2.1 官方件异步续段
|
|
401
|
+
// 通道):core 行 apply 第三参 = 按本插件 handle 铸开窗器(enterHostCallback
|
|
402
|
+
// 重入计数——与钩子派发/工具执行期同一窗真源);core 件 apply 发起的宿主
|
|
403
|
+
// 编排异步任务(MCP 发现续段等)完成时点属宿主回调时点,续段内注册经此
|
|
404
|
+
// 开窗合法。磁盘行结构性不传(第三方插件无此通道——有意禁区)
|
|
405
|
+
coreHostChannel: (pluginId) => {
|
|
406
|
+
const handle = handles.get(pluginId);
|
|
407
|
+
return handle === undefined
|
|
408
|
+
? undefined
|
|
409
|
+
: {
|
|
410
|
+
// 开窗时查活(03 §10.1——铸造发生在 apply 期必活,铸造时单查无
|
|
411
|
+
// 意义):世代死域开窗即抛 PLUGIN_WINDOW_CLOSED——与窗闸同码
|
|
412
|
+
// 同语义(域已死即窗恒闭),报文区分域死成因(core 件捕获后降
|
|
413
|
+
// warn 收口,与发现腿失败收口同律)
|
|
414
|
+
openHostCallback: () => {
|
|
415
|
+
if (generationDead) {
|
|
416
|
+
throw new BaseError('PLUGIN_WINDOW_CLOSED', `官方件宿主面开窗被拒:插件 ${pluginId} 所属装载代已卸载/已换代(03 §10.1——域已死即窗恒闭,防 owner 归已死代的幽灵注册行)`);
|
|
417
|
+
}
|
|
418
|
+
return handle.enterHostCallback();
|
|
419
|
+
},
|
|
420
|
+
};
|
|
421
|
+
},
|
|
422
|
+
...(bookkeepingPath === null
|
|
423
|
+
? {}
|
|
424
|
+
: {
|
|
425
|
+
onBootFailure: (id, version, failure) => {
|
|
426
|
+
// 试件行不进持久诊断账(不变式 1 零落盘面):残账无清名时机(下次
|
|
427
|
+
// boot 无此行)→ 横幅会误报已消失的幽灵行——这里过滤而非事后清
|
|
428
|
+
if (id === QUICK_TEST_ROW_ID)
|
|
429
|
+
return;
|
|
430
|
+
recordBootFailure(bookkeepingPath, id, version, failure, bookkeepingFs);
|
|
431
|
+
},
|
|
432
|
+
}),
|
|
433
|
+
});
|
|
434
|
+
// 装载成功行清名(横幅只报仍坏行——报捷即抹账)
|
|
435
|
+
if (bookkeepingPath !== null) {
|
|
436
|
+
for (const a of loaded.activated)
|
|
437
|
+
clearBootFailure(bookkeepingPath, a.id, bookkeepingFs);
|
|
438
|
+
}
|
|
439
|
+
// —— 启动横幅聚合 warn(03 §5.7② obs-a 兑现):装载序尾对本 boot loader
|
|
440
|
+
// 行失败(jiti 装载/apply/模块 inject 执法——合成失败行已在上方点名)
|
|
441
|
+
// 逐行 warn,与合成失败行同形 `插件装载失败(id):[码] 报文`。修前本
|
|
442
|
+
// 横幅仅存于注释与规范文本、从未落码:loader 行失败只静默进 failed 面,
|
|
443
|
+
// 人面零错误呈现。memory 形照 warn(warn 面自身诊断安全)。
|
|
444
|
+
for (const f of loaded.failed) {
|
|
445
|
+
warn(`插件装载失败(${f.id}):${formatPluginFailureText(f)}`);
|
|
446
|
+
}
|
|
447
|
+
// —— plugin/opens 幂等落(05 §1.1 开门/关门审计腿——U3 批 U3-5):boot
|
|
448
|
+
// 装载序以计划面磁盘行授予面 diff 审计流尾条,有变才落(撤位空数组形收
|
|
449
|
+
// 口——开门/关门全链可对账);audit 面缺席 = 诊断形不落账(诚实缺席律)
|
|
450
|
+
if (options.audit !== undefined) {
|
|
451
|
+
recordPluginOpensDiff(options.audit, plan.flatMap((row) => (row.kind === 'disk' ? [{ id: row.id, opens: row.opens ?? [] }] : [])));
|
|
452
|
+
// —— doors/updated 幂等落(开门制扩展批 2026-09-09——05 §1.1 行 72 进程级
|
|
453
|
+
// 开门位授予面切换事实):boot 装载序以 doors 段现值与审计流尾最近一条本词
|
|
454
|
+
// diff,有变才落(幂等同 plugin/opens 律;撤位空数组形收口;空面首记不落)
|
|
455
|
+
recordDoorsDiff(options.audit, doors);
|
|
456
|
+
// —— 生命周期五词 boot diff 补播(05 §1.1 生命周期归因面行——audit 落账
|
|
457
|
+
// 批写点②):手编 enabled.yaml 漂移检测——「用户手改文件」与「用户按
|
|
458
|
+
// 命令」同链可审计;对审计流尾最近态 diff、有变才落(幂等同律)。本账
|
|
459
|
+
// 含 core: 行(mount/unmount/toggle 对 core: 是合法 overlay 动作——与
|
|
460
|
+
// opens diff 排除 core: 分立)
|
|
461
|
+
recordPluginLifecycleDiff(options.audit, plan.map((row) => ({ id: row.id, disabled: row.disabled === true })));
|
|
462
|
+
}
|
|
463
|
+
// ⑪ 生命周期事件批量补发(合成失败行并入 failed 面——收口一致真相)
|
|
464
|
+
const failedAll = [...synthesisFailures, ...loaded.failed];
|
|
465
|
+
for (const a of loaded.activated)
|
|
466
|
+
await options.dispatch.emit('plugin/activated', { id: a.id });
|
|
467
|
+
for (const f of failedAll)
|
|
468
|
+
await options.dispatch.emit('plugin/failed', { id: f.id, code: f.code });
|
|
469
|
+
for (const s of loaded.skipped)
|
|
470
|
+
await options.dispatch.emit('plugin/skipped', { id: s.id, reason: s.reason });
|
|
471
|
+
await options.dispatch.emit('composition/reloaded', {
|
|
472
|
+
activated: loaded.activated.map((a) => a.id),
|
|
473
|
+
failed: failedAll.map((f) => f.id),
|
|
474
|
+
skipped: loaded.skipped.map((s) => s.id),
|
|
475
|
+
});
|
|
476
|
+
// ⑨ closer:apply disposer LIFO 后 fork 逆序 dispose(drain 序 = 注册序);
|
|
477
|
+
// Job 归属围栏收口腿(Job 消费面批桥二)在 disposer 回卷后对 activated
|
|
478
|
+
// 逐插件 closeOwner——插件侧事件源先停(不再新 fire),余在飞 Job 两拍
|
|
479
|
+
// 收口(协作中止路由 + 兜底 killed——04 §10 定形)。
|
|
480
|
+
// 换代槽双形态(/reload 批):unloadRef 在场改写槽(shutdown 恒跑最新代,
|
|
481
|
+
// reload 换代不累积重复 closer);缺席维持直注册(单次 boot 形)。
|
|
482
|
+
const unloadAll = async () => {
|
|
483
|
+
generationDead = true; // 世代死域置位(03 §10.1——回卷最前即封窗,拆除期零新开窗)
|
|
484
|
+
const receipt = await loaded.unload();
|
|
485
|
+
if (options.jobs !== undefined) {
|
|
486
|
+
for (const a of loaded.activated)
|
|
487
|
+
await options.jobs.closeOwner(a.id);
|
|
488
|
+
}
|
|
489
|
+
for (const fork of pluginScopes.reverse())
|
|
490
|
+
await fork.dispose(); // ctx.effect 回卷
|
|
491
|
+
// oauth 流条目随代摘除(03 §10.9 oauth 死域批——流注册随代生死):防死代
|
|
492
|
+
// 开窗器幽灵窗 / 刷新链为已禁用已卸载插件续轮换 / 人面解析到死代流。
|
|
493
|
+
// 库条目不随代删——token 数据是用户资产,流注册是插件运行时面(两分立)。
|
|
494
|
+
// 成功换代重注册走「后写胜出」自然覆盖;本腿兜住缺席重注册的残留面
|
|
495
|
+
const oauthRegistry = options.secrets?.oauthRegistry;
|
|
496
|
+
if (oauthRegistry !== undefined) {
|
|
497
|
+
for (const a of loaded.activated)
|
|
498
|
+
oauthRegistry.unregisterForPlugin(a.id);
|
|
499
|
+
}
|
|
500
|
+
return receipt; // 回卷回执(/reload 档③聚合报告数据源——换代槽消费)
|
|
501
|
+
};
|
|
502
|
+
if (options.unloadRef !== undefined) {
|
|
503
|
+
options.unloadRef.current = unloadAll;
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
// 直注册形回执丢弃(HostCloser 契约 void 形——单次 boot 无消费面)
|
|
507
|
+
options.runtime.registerCloser({ label: 'plugin-unload', fn: async () => void (await unloadAll()) });
|
|
508
|
+
}
|
|
509
|
+
// failed 面合并后交付(单真相——消费方不见两源)
|
|
510
|
+
const report = { ...loaded, failed: failedAll };
|
|
511
|
+
// —— 世代快照落账(装载史批 h-3——05 §9 写点):boot 完成点 = 世代生效
|
|
512
|
+
// 点(/reload reapply 重跑本函数 = 同点双覆盖——boot 与 reload 换代单写
|
|
513
|
+
// 点同源);三分区全录、判据恒 activated;tools 名账 = 本代 register
|
|
514
|
+
// 包壳层收口时点在册集(disposer 出账不留残影)。写失败 fail-loud 拒启
|
|
515
|
+
// (世代账静默缺失比 boot 失败更糟——与开库失败同档);装载失败到不了
|
|
516
|
+
// 本点 = 不落行不换代(05 §9 边沿定形)。face 缺席 = 诊断形不落行
|
|
517
|
+
if (options.loadHistory !== undefined) {
|
|
518
|
+
options.loadHistory.recordLoadGeneration({
|
|
519
|
+
activated: report.activated.map((a) => ({ id: a.id, tools: toolLedger.toolsOf(a.id) })),
|
|
520
|
+
skipped: report.skipped.map((s) => ({ id: s.id, reason: s.reason })),
|
|
521
|
+
failed: report.failed.map((f) => ({ id: f.id, code: f.code })),
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
report,
|
|
526
|
+
counts: {
|
|
527
|
+
total: plan.length + synthesisFailures.length,
|
|
528
|
+
enabled: loaded.activated.length,
|
|
529
|
+
failed: failedAll.length,
|
|
530
|
+
},
|
|
531
|
+
tools,
|
|
532
|
+
toolsOf: toolLedger.toolsOf,
|
|
533
|
+
// 配置声明面取值器(ix-3):plan 行直取双轨——core: 引用形 / 磁盘 manifest;
|
|
534
|
+
// 换代取值器闭包(/reload 后即新代声明面)
|
|
535
|
+
configFaceOf: (pluginId) => {
|
|
536
|
+
const row = plan.find((r) => r.id === pluginId);
|
|
537
|
+
if (row === undefined)
|
|
538
|
+
return undefined;
|
|
539
|
+
const fields = row.kind === 'core' ? row.reference.configSchema : row.manifest.configSchema;
|
|
540
|
+
if (fields === undefined)
|
|
541
|
+
return undefined;
|
|
542
|
+
return { fields, hostDefaults: row.kind === 'core' ? row.reference.config : row.manifest.config };
|
|
543
|
+
},
|
|
544
|
+
promptSections,
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* boot 序 plugin/opens 幂等落(05 §1.1 开门/关门审计腿——U3 批 U3-5):
|
|
549
|
+
* 以计划面磁盘行 opens 为当前授予面,与审计流尾最近一条本词(per 插件——
|
|
550
|
+
* fold = 尾条 = 该插件当前有效授予面)diff,**有变才落**(幂等不重复记账);
|
|
551
|
+
* 曾记账而今不在计划面(行删除/换装新 id)= 撤位,落 `opens: []` 空数组形
|
|
552
|
+
* 收口。core: 行结构性无 opens(03 §5.3),不进本账。单写者 = 装配根装载
|
|
553
|
+
* 序(audit 流单写者律——插件面零写入位)。
|
|
554
|
+
*
|
|
555
|
+
* 集合语义比对(排序后逐位)——enabled.yaml 行序漂移不触发假记账。空面
|
|
556
|
+
* 首记不落:无记录 ≡ 空面(fold 语义一致),`[]` 笔恒为撤位收口形而非首记
|
|
557
|
+
* 基线(零授予零事实——不为从未开门的插件造基线噪声)。历史尾条只扫近期窗
|
|
558
|
+
* (listRecent 帽 100):审计流里本词只增不删且每变才落,稳态下在册插件数
|
|
559
|
+
* << 帽;超帽的极端态 = 最旧撤位事实滑出窗口(收口笔不重放,尾条语义不受
|
|
560
|
+
* 损——非 durable 损失面)。
|
|
561
|
+
* @param audit 审计流面(读写两用——尾读建 per 插件最新态,diff 后落账)
|
|
562
|
+
* @param rows 计划面磁盘行(含禁用行——授予面真源是 enabled.yaml 行本身)
|
|
563
|
+
*/
|
|
564
|
+
export function recordPluginOpensDiff(audit, rows) {
|
|
565
|
+
// 尾读 → per 插件最新授予面(listRecent id 降序——首见即最新)
|
|
566
|
+
const latest = new Map();
|
|
567
|
+
for (const row of audit.listRecent()) {
|
|
568
|
+
if (row.type !== 'plugin/opens')
|
|
569
|
+
continue;
|
|
570
|
+
const pluginId = row.data['pluginId'];
|
|
571
|
+
const opens = row.data['opens'];
|
|
572
|
+
// 词形防御(值域已过 parseEnabledRows 行校验——此处只防库被手编)
|
|
573
|
+
if (typeof pluginId !== 'string' || !Array.isArray(opens))
|
|
574
|
+
continue;
|
|
575
|
+
if (!latest.has(pluginId)) {
|
|
576
|
+
latest.set(pluginId, opens.filter((o) => typeof o === 'string').sort());
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
// 有变才落:当前面(去重排序——尾条形稳定)vs 尾条
|
|
580
|
+
const current = new Map(rows.map((r) => [r.id, [...new Set(r.opens)].sort()]));
|
|
581
|
+
for (const [pluginId, opens] of current) {
|
|
582
|
+
const prev = latest.get(pluginId) ?? [];
|
|
583
|
+
if (prev.length !== opens.length || opens.some((o, i) => prev[i] !== o)) {
|
|
584
|
+
audit.append('plugin/opens', { pluginId, opens });
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
// 撤位收口:曾记账而今不在计划面,且尾条非空(已空则幂等不再落)
|
|
588
|
+
for (const [pluginId, prev] of latest) {
|
|
589
|
+
if (!current.has(pluginId) && prev.length > 0) {
|
|
590
|
+
audit.append('plugin/opens', { pluginId, opens: [] });
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* boot 序 doors/updated 幂等落(开门制扩展批 2026-09-09——05 §1.1 行 72):
|
|
596
|
+
* 以 enabled.yaml 顶层 doors 段现值为当前开门面,与审计流尾最近一条本词
|
|
597
|
+
* diff,**有变才落**(幂等不重复记账——recordPluginOpensDiff 同律);撤位
|
|
598
|
+
* (尾条非空 → 现空)落 `doors: []` 空数组形收口;空面首记不落(无记录 ≡
|
|
599
|
+
* 空面——不为从未开门的进程造基线噪声)。载荷 = 全量清单快照 + 编辑道归因
|
|
600
|
+
* 键 origin(boot 序恒 'boot-diff'——'tui-cmd' 位 /doors 人面命令执行尾,
|
|
601
|
+
* 落位于 assembly doors 注册块包装内、g-2 已收口)。单写者 = 装配根装载
|
|
602
|
+
* 序(audit 流单写者律)。
|
|
603
|
+
*
|
|
604
|
+
* 尾读走 lastOf(单键 fold——doors 段进程级单键,无 per 插件多键面)。
|
|
605
|
+
* @param audit 审计流面(读写两用——尾读建现值,diff 后落账)
|
|
606
|
+
* @param doors boot 读侧 doors 段现值(已过 parseEnabledRows 值域校验)
|
|
607
|
+
*/
|
|
608
|
+
export function recordDoorsDiff(audit, doors) {
|
|
609
|
+
const priorRow = audit.lastOf('doors/updated');
|
|
610
|
+
const prior = priorRow === undefined ? [] : priorRow.data['doors'];
|
|
611
|
+
// 词形防御(值域已过 parseEnabledRows 段校验——此处只防库被手编)
|
|
612
|
+
const prev = Array.isArray(prior) ? [...new Set(prior.filter((d) => typeof d === 'string'))].sort() : [];
|
|
613
|
+
const current = [...new Set(doors)].sort();
|
|
614
|
+
if (prev.length === current.length && current.every((d, i) => prev[i] === d))
|
|
615
|
+
return; // 有变才落
|
|
616
|
+
audit.append('doors/updated', { doors: current, origin: 'boot-diff' });
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* 生命周期五词 boot diff 补播(05 §1.1 生命周期归因面行——audit 落账批
|
|
620
|
+
* 写点②):对计划面全行(含 core: 行)以审计流尾最近态 diff,有变才落。
|
|
621
|
+
*
|
|
622
|
+
* 「用户手改 enabled.yaml」与「用户按 CLI 命令」同链可审计——CLI 人面
|
|
623
|
+
* 成功尾已落账(写点①),本补播只兜文件面漂移(手编/外部工具改行);
|
|
624
|
+
* 与 plugin/opens diff 同幂等律:稳态零落,重启不重放。
|
|
625
|
+
*
|
|
626
|
+
* 态域三值:enabled / disabled / absent——
|
|
627
|
+
* - 审计尾态 fold:listRecent(id 降序)per id 三词(mounted/unmounted/
|
|
628
|
+
* toggled)首见即最新:mounted→enabled、unmounted→absent、toggled→
|
|
629
|
+
* disabled 位判别(true|false 双态——审计词形独立于行形);
|
|
630
|
+
* - 计划面现态:行在场按 disabled === true 判、行缺席 = absent;
|
|
631
|
+
* - **core: 基线态**:无记录的 id——core: 前缀按 enabled 算(内置全启,
|
|
632
|
+
* 防首启全 core: 件落 mounted 噪声——与 opens diff「不为从未开门的
|
|
633
|
+
* 插件造基线噪声」同哲学);非 core 按未上场算。
|
|
634
|
+
*
|
|
635
|
+
* diff 五形(动作序列还原):
|
|
636
|
+
* absent→enabled 落 mounted;absent→disabled 落 mounted + toggled{true}
|
|
637
|
+
* (手编一步到位 = 命令两步的序列等价);enabled↔disabled 落 toggled
|
|
638
|
+
* 双态;在场→absent 落 unmounted;一致零落。core: 件计划面缺席(宿主
|
|
639
|
+
* 侧官件裁撤——结构性恒在计划面,缺席非用户动作)不落账防噪声。
|
|
640
|
+
* @param audit 审计流面(读写两用——尾读建 per id 最近态,diff 后落账)
|
|
641
|
+
* @param rows 计划面全行(含 core: 行——本账与 opens diff 排除 core: 分立)
|
|
642
|
+
*/
|
|
643
|
+
export function recordPluginLifecycleDiff(audit, rows) {
|
|
644
|
+
// 尾读 → per id 三词最近态(首见即最新;坏形条目无视——同 opens diff 词形防御律)
|
|
645
|
+
const latest = new Map();
|
|
646
|
+
for (const row of audit.listRecent()) {
|
|
647
|
+
if (row.type !== 'plugin/mounted' && row.type !== 'plugin/unmounted' && row.type !== 'plugin/toggled') {
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
const id = row.data['id'];
|
|
651
|
+
if (typeof id !== 'string' || latest.has(id))
|
|
652
|
+
continue;
|
|
653
|
+
if (row.type === 'plugin/unmounted')
|
|
654
|
+
latest.set(id, 'absent');
|
|
655
|
+
else if (row.type === 'plugin/mounted')
|
|
656
|
+
latest.set(id, 'enabled');
|
|
657
|
+
else {
|
|
658
|
+
const disabled = row.data['disabled'];
|
|
659
|
+
if (disabled !== true && disabled !== false)
|
|
660
|
+
continue; // 坏形跳过(不建态)
|
|
661
|
+
latest.set(id, disabled ? 'disabled' : 'enabled');
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
// 计划面现态 + diff 落账(循环域 = 计划面 ∪ 尾态 id 集——两侧单边差都覆盖)
|
|
665
|
+
const planState = new Map(rows.map((r) => [r.id, r.disabled ? 'disabled' : 'enabled']));
|
|
666
|
+
for (const id of new Set([...planState.keys(), ...latest.keys()])) {
|
|
667
|
+
const auditState = latest.get(id) ?? (id.startsWith('core:') ? 'enabled' : 'absent');
|
|
668
|
+
const current = planState.get(id);
|
|
669
|
+
if (current === undefined) {
|
|
670
|
+
if (!id.startsWith('core:') && auditState !== 'absent') {
|
|
671
|
+
audit.append('plugin/unmounted', { id });
|
|
672
|
+
}
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
if (auditState === current)
|
|
676
|
+
continue; // 一致零落(幂等)
|
|
677
|
+
if (auditState === 'absent') {
|
|
678
|
+
audit.append('plugin/mounted', { id });
|
|
679
|
+
if (current === 'disabled')
|
|
680
|
+
audit.append('plugin/toggled', { id, disabled: true });
|
|
681
|
+
}
|
|
682
|
+
else {
|
|
683
|
+
audit.append('plugin/toggled', { id, disabled: current === 'disabled' });
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* enabled.yaml 读侧(§5.3)。缺席 = 全 core: 内置态(含 memory 形——无数据
|
|
689
|
+
* 目录同缺席语义);损坏 = fail-loud 拒启给修复指引(删除文件即回内置态)。
|
|
690
|
+
*
|
|
691
|
+
* 返回行集 + 顶层 doors 段(开门制扩展批 2026-09-09——模型道高危面授予段;
|
|
692
|
+
* 缺席 = 空数组)。导出消费位 = /reload 预检(03 §5.7 档①——plugin-reload
|
|
693
|
+
* preflight 真源):同一函数先于回卷跑一遍,校验失败即拒换——预检与装载读侧
|
|
694
|
+
* 恒一致(单源,结构性不存在「预检过装载拒」的第二判据)。
|
|
695
|
+
*/
|
|
696
|
+
export function readEnabledRows(dataDir, fs) {
|
|
697
|
+
if (dataDir === null)
|
|
698
|
+
return { rows: [], doors: [] };
|
|
699
|
+
const path = enabledYamlPath(dataDir);
|
|
700
|
+
const text = fs.read(path);
|
|
701
|
+
if (text === null)
|
|
702
|
+
return { rows: [], doors: [] }; // 缺席 = 全 core: 内置态
|
|
703
|
+
let doc;
|
|
704
|
+
try {
|
|
705
|
+
doc = parseYaml(text);
|
|
706
|
+
}
|
|
707
|
+
catch (err) {
|
|
708
|
+
throw new BaseError('PLUGIN_ROW_INVALID', `启用清单损坏(${path}):${err instanceof Error ? err.message : String(err)}——修复或删除该文件后重启(删除即回全 core: 内置态;03 §5.3 损坏 fail-loud)`, { cause: err });
|
|
709
|
+
}
|
|
710
|
+
const result = parseEnabledRows(doc);
|
|
711
|
+
if (!result.ok) {
|
|
712
|
+
throw new BaseError('PLUGIN_ROW_INVALID', `启用清单校验失败(${path}):${result.message}——修复指引:顶层 { plugins: [{ id, config?, disabled?, opens? }], doors?: [...] };删除文件即回全 core: 内置态`);
|
|
713
|
+
}
|
|
714
|
+
return { rows: result.rows, doors: result.doors };
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* 装机账本读侧(§5.4 尾:损坏 = warn 点名 + 空账本降级——账本是装机面非
|
|
718
|
+
* 真相源,残缺不拦启动)。容器形宽容两式:条目数组形〔示例形——条目自带
|
|
719
|
+
* id 字段〕/ id 键映射形;写侧定形归 install 批。
|
|
720
|
+
*/
|
|
721
|
+
function readLedger(dataDir, fs, warn) {
|
|
722
|
+
if (dataDir === null)
|
|
723
|
+
return {};
|
|
724
|
+
const path = join(dataDir, 'plugins', 'ledger.json');
|
|
725
|
+
const text = fs.read(path);
|
|
726
|
+
if (text === null)
|
|
727
|
+
return {};
|
|
728
|
+
const degrade = (reason) => {
|
|
729
|
+
warn(`装机账本损坏(${path}):${reason}——空账本降级(03 §5.4:warn 不 brick 装机面)`);
|
|
730
|
+
return {};
|
|
731
|
+
};
|
|
732
|
+
let doc;
|
|
733
|
+
try {
|
|
734
|
+
doc = JSON.parse(text);
|
|
735
|
+
}
|
|
736
|
+
catch (err) {
|
|
737
|
+
return degrade(`坏 JSON(${err instanceof Error ? err.message : String(err)})`);
|
|
738
|
+
}
|
|
739
|
+
const out = {};
|
|
740
|
+
if (Array.isArray(doc)) {
|
|
741
|
+
for (const entry of doc) {
|
|
742
|
+
if (typeof entry !== 'object' || entry === null || typeof entry.id !== 'string') {
|
|
743
|
+
return degrade('数组条目形含非带 id 对象');
|
|
744
|
+
}
|
|
745
|
+
out[entry.id] = entry;
|
|
746
|
+
}
|
|
747
|
+
return out;
|
|
748
|
+
}
|
|
749
|
+
if (typeof doc === 'object' && doc !== null) {
|
|
750
|
+
for (const [id, entry] of Object.entries(doc)) {
|
|
751
|
+
if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {
|
|
752
|
+
return degrade(`条目 ${id} 非对象`);
|
|
753
|
+
}
|
|
754
|
+
out[id] = entry;
|
|
755
|
+
}
|
|
756
|
+
return out;
|
|
757
|
+
}
|
|
758
|
+
return degrade('顶层非数组/对象');
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* 计划合成(§5.3 合成纪律):core 注册表全进计划(内置全启不占用户行);
|
|
762
|
+
* 用户行 core: 同名行字段级后写胜出;用户磁盘行走账本解析。core: 行无注册
|
|
763
|
+
* 表成员 = 合成失败(引用了未编入件——版本不符提示)。
|
|
764
|
+
*/
|
|
765
|
+
function synthesizePlan(input) {
|
|
766
|
+
const plan = [];
|
|
767
|
+
const synthesisFailures = [];
|
|
768
|
+
const coreByName = new Map();
|
|
769
|
+
for (const ref of input.corePlugins)
|
|
770
|
+
coreByName.set(`core:${ref.name}`, ref);
|
|
771
|
+
const overlaid = new Set(); // 用户行已覆盖的 core: 行 id
|
|
772
|
+
// core: 内置全启(注册序即计划序)+ 用户行字段级 overlay
|
|
773
|
+
for (const [id, ref] of coreByName) {
|
|
774
|
+
const overlay = input.rows.find((row) => row.id === id);
|
|
775
|
+
if (overlay === undefined) {
|
|
776
|
+
plan.push({ kind: 'core', id, reference: ref });
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
overlaid.add(id);
|
|
780
|
+
// 字段级后写胜出(§5.3):用户行省略的字段沿用内置值;config 整值替换非合并
|
|
781
|
+
plan.push({
|
|
782
|
+
kind: 'core',
|
|
783
|
+
id,
|
|
784
|
+
reference: ref,
|
|
785
|
+
...(overlay.config !== undefined ? { config: overlay.config } : {}),
|
|
786
|
+
...(overlay.disabled !== undefined ? { disabled: overlay.disabled } : {}),
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
// 用户行:core: 未注册名拒;磁盘行走账本解析
|
|
790
|
+
for (const row of input.rows) {
|
|
791
|
+
if (overlaid.has(row.id))
|
|
792
|
+
continue; // 已并入 core overlay 腿
|
|
793
|
+
if (row.id.startsWith('core:')) {
|
|
794
|
+
synthesisFailures.push({
|
|
795
|
+
id: row.id,
|
|
796
|
+
code: 'PLUGIN_LOAD_FAILED',
|
|
797
|
+
message: `官方件 ${row.id} 不在本构建 core: 注册表(版本不符或该件未编入)——移除该行或核对件名`,
|
|
798
|
+
});
|
|
799
|
+
continue;
|
|
800
|
+
}
|
|
801
|
+
const resolved = resolveDiskRow(row, input.dataDir, input.ledger, input.fs);
|
|
802
|
+
if ('failure' in resolved)
|
|
803
|
+
synthesisFailures.push(resolved.failure);
|
|
804
|
+
else
|
|
805
|
+
plan.push(resolved.spec);
|
|
806
|
+
}
|
|
807
|
+
return { plan, synthesisFailures };
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* 快速试件合成行 id(--plugin-file——03 §7):装载计划**保留字**、非插件
|
|
811
|
+
* 身份位——合法插件 id 字符集(小写字母起头)结构性不含下划线起头形,恒
|
|
812
|
+
* 不与任何已装/内置插件撞名(单文件形的隐式清单 id 即此值,结构性免检)。
|
|
813
|
+
*/
|
|
814
|
+
export const QUICK_TEST_ROW_ID = '_quick_test';
|
|
815
|
+
/**
|
|
816
|
+
* 快速试件行解析(03 §7 八不变式之 3/6 的机器落点——两形路径 + 撞名拒启)。
|
|
817
|
+
*
|
|
818
|
+
* 目录形 = 路径下有 package.json:真清单过 parseManifest(official:false——
|
|
819
|
+
* 与磁盘行同判据同律,坏形 fail-loud 拒启不静默隔离;试件是显式指定物,
|
|
820
|
+
* 坏形当场红比行级隔离更指向根因)。撞名检查对象 = 试件清单声明 id vs
|
|
821
|
+
* 计划面全体行 id ∪ 磁盘行清单 id(装载身份位与呈现位两锚都护——冒名
|
|
822
|
+
* 顶替拒启)。单文件形 = 路径本身可读的裸入口:宿主合成隐式清单(entry
|
|
823
|
+
* 定死该文件、id = 保留字——合法插件结构性不可能持此名)。两形皆不中 =
|
|
824
|
+
* fail-loud(message 指路两形)。
|
|
825
|
+
*/
|
|
826
|
+
function resolveQuickTestRow(pluginFile, plan, fs) {
|
|
827
|
+
const abs = resolve(pluginFile);
|
|
828
|
+
const pkgText = fs.read(join(abs, 'package.json'));
|
|
829
|
+
if (pkgText !== null) {
|
|
830
|
+
// 目录形:真清单判据(未知键/坏形/secret 明文拒同磁盘行全套执法)
|
|
831
|
+
let pkg;
|
|
832
|
+
try {
|
|
833
|
+
pkg = JSON.parse(pkgText);
|
|
834
|
+
}
|
|
835
|
+
catch (err) {
|
|
836
|
+
throw new BaseError('PLUGIN_ROW_INVALID', `--plugin-file 试件 package.json 非合法 JSON(${abs}):${err instanceof Error ? err.message : String(err)}`);
|
|
837
|
+
}
|
|
838
|
+
const parsed = parseManifest(pkg, { official: false });
|
|
839
|
+
if (!parsed.ok) {
|
|
840
|
+
throw new BaseError('PLUGIN_ROW_INVALID', `--plugin-file 试件清单坏形(${abs}):${parsed.message}`);
|
|
841
|
+
}
|
|
842
|
+
const manifest = parsed.manifest;
|
|
843
|
+
const takenIds = new Set([
|
|
844
|
+
...plan.map((row) => row.id),
|
|
845
|
+
...plan.flatMap((row) => (row.kind === 'disk' ? [row.manifest.id] : [])),
|
|
846
|
+
]);
|
|
847
|
+
if (takenIds.has(manifest.id)) {
|
|
848
|
+
throw new BaseError('PLUGIN_ROW_INVALID', `--plugin-file 试件撞名:清单声明 id "${manifest.id}" 撞已装/内置插件(装载计划在场同 id)——改 package.json 的 name/berryAgent.id 后重试(冒名顶替拒启)`);
|
|
849
|
+
}
|
|
850
|
+
return { kind: 'disk', id: QUICK_TEST_ROW_ID, manifest, pluginDir: abs };
|
|
851
|
+
}
|
|
852
|
+
// 单文件形:路径本身可读 = 裸入口文件(宿主合成隐式清单——装载全程身份
|
|
853
|
+
// 仍 row.id 保留字,manifest.id 仅合成件内位)
|
|
854
|
+
if (fs.read(abs) !== null) {
|
|
855
|
+
const entry = basename(abs);
|
|
856
|
+
const manifest = {
|
|
857
|
+
id: QUICK_TEST_ROW_ID,
|
|
858
|
+
label: QUICK_TEST_ROW_ID,
|
|
859
|
+
entry,
|
|
860
|
+
entryPlan: { kind: 'entry-file', entry },
|
|
861
|
+
};
|
|
862
|
+
return { kind: 'disk', id: QUICK_TEST_ROW_ID, manifest, pluginDir: dirname(abs) };
|
|
863
|
+
}
|
|
864
|
+
throw new BaseError('PLUGIN_ROW_INVALID', `--plugin-file 路径不存在(${abs}):应为真实存在的插件目录(含 package.json)或单文件入口(.js/.mjs/.ts)`);
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* 磁盘行账本解析(§5.4 boot 读侧消费):installPath(绝对直用/相对 join
|
|
868
|
+
* 数据目录)→ 装机目录 package.json 过清单校验(official:false)。三失败态
|
|
869
|
+
* (账本缺席/目录不可读/清单坏形)皆行级隔离降级——码沿用 PLUGIN_LOAD_
|
|
870
|
+
* FAILED/PLUGIN_SHAPE_INVALID 分流,新码语义挂账生命周期批。
|
|
871
|
+
*/
|
|
872
|
+
function resolveDiskRow(row, dataDir, ledger, fs) {
|
|
873
|
+
if (dataDir === null) {
|
|
874
|
+
return {
|
|
875
|
+
failure: {
|
|
876
|
+
id: row.id,
|
|
877
|
+
code: 'PLUGIN_LOAD_FAILED',
|
|
878
|
+
message: 'memory 形无装机账本——用户磁盘行不可解析(:memory: 诊断同构只装 core:)',
|
|
879
|
+
},
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
const entry = ledger[row.id];
|
|
883
|
+
if (entry === undefined) {
|
|
884
|
+
return {
|
|
885
|
+
failure: {
|
|
886
|
+
id: row.id,
|
|
887
|
+
code: 'PLUGIN_LOAD_FAILED',
|
|
888
|
+
message: '装机账本无此 id(03 §5.4)——先 install 再启用,或从启用清单移除该行',
|
|
889
|
+
},
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
const rawPath = entry.installPath;
|
|
893
|
+
if (typeof rawPath !== 'string' || rawPath.length === 0) {
|
|
894
|
+
return {
|
|
895
|
+
failure: {
|
|
896
|
+
id: row.id,
|
|
897
|
+
code: 'PLUGIN_LOAD_FAILED',
|
|
898
|
+
message: '装机账本条目缺归一路径 installPath(03 §5.4)——账本坏形,重装修机可重建',
|
|
899
|
+
},
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
// 解析序:绝对直用(local 源表示);相对 join 数据目录(npm/git 源表示)
|
|
903
|
+
const pluginDir = isAbsolute(rawPath) ? rawPath : join(dataDir, rawPath);
|
|
904
|
+
const pkgText = fs.read(join(pluginDir, 'package.json'));
|
|
905
|
+
if (pkgText === null) {
|
|
906
|
+
return {
|
|
907
|
+
failure: {
|
|
908
|
+
id: row.id,
|
|
909
|
+
code: 'PLUGIN_LOAD_FAILED',
|
|
910
|
+
message: `装机目录不可读(${pluginDir} 无 package.json)——重装可修复`,
|
|
911
|
+
},
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
let pkg;
|
|
915
|
+
try {
|
|
916
|
+
pkg = JSON.parse(pkgText);
|
|
917
|
+
}
|
|
918
|
+
catch (err) {
|
|
919
|
+
return {
|
|
920
|
+
failure: {
|
|
921
|
+
id: row.id,
|
|
922
|
+
code: 'PLUGIN_LOAD_FAILED',
|
|
923
|
+
message: `装机目录 package.json 坏 JSON(${pluginDir}):${err instanceof Error ? err.message : String(err)}`,
|
|
924
|
+
},
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
const manifestResult = parseManifest(pkg); // 用户插件缺省 official:false
|
|
928
|
+
if (!manifestResult.ok) {
|
|
929
|
+
return {
|
|
930
|
+
failure: {
|
|
931
|
+
id: row.id,
|
|
932
|
+
code: manifestResult.code,
|
|
933
|
+
message: `清单校验失败(${pluginDir}):${manifestResult.message}`,
|
|
934
|
+
},
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
const spec = {
|
|
938
|
+
kind: 'disk',
|
|
939
|
+
id: row.id,
|
|
940
|
+
manifest: manifestResult.manifest,
|
|
941
|
+
pluginDir,
|
|
942
|
+
...(row.config !== undefined ? { config: row.config } : {}),
|
|
943
|
+
...(row.disabled !== undefined ? { disabled: row.disabled } : {}),
|
|
944
|
+
// 开门授予位透传(批 U2 读侧——值域已过 parseEnabledRows 行校验,此处零复验)
|
|
945
|
+
...(row.opens !== undefined ? { opens: row.opens } : {}),
|
|
946
|
+
};
|
|
947
|
+
return { spec };
|
|
948
|
+
}
|
|
949
|
+
/** fs 面适配(PluginBootFs → BootFailuresFs——同构双方法直转) */
|
|
950
|
+
function toBootFailuresFs(fs) {
|
|
951
|
+
return { read: fs.read, write: fs.write };
|
|
952
|
+
}
|