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,462 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* host/plugin-store — 装机面写侧存储件(03 §5.3/§5.4/§5.5——成熟度缺口 #10
|
|
3
|
+
* 装机面落码批 10a)。
|
|
4
|
+
*
|
|
5
|
+
* 职责四块(纯逻辑 + fs 注入零 spawn——三源执行器归 plugin-install,四段
|
|
6
|
+
* 清算编舞归 plugin-uninstall,本件只供两件共用的存储基座):
|
|
7
|
+
* - **装机账本 CRUD**(§5.4):完整条目形(写侧单源——示例全字段);读侧
|
|
8
|
+
* 宽容两式(条目数组形 + id 键映射形——与 boot 读侧同律);写侧恒出
|
|
9
|
+
* **条目数组形**(§5.4 写侧容器形定形——不产第二种表示);原子写
|
|
10
|
+
* tmp+rename(§5.4 账本写面条款)。
|
|
11
|
+
* - **enabled.yaml 行编辑**(§5.3):mount append / unmount 删行 / toggle
|
|
12
|
+
* 翻 disabled——读-改-写整文件(yaml 序列化输出顶层 { plugins: [...] }),
|
|
13
|
+
* 原子写同律。行校验复用 manifest.parseEnabledRows(拒绝式单源)。
|
|
14
|
+
* - **installPath 推导**(§5.4 归一路径载体——写侧执法):npm 相对
|
|
15
|
+
* `plugins/node_modules/<包名>`、git 相对 `plugins/git/<host>/<首段>/<repo>`、
|
|
16
|
+
* local 绝对 canonical 化值;三源不产第二种表示。
|
|
17
|
+
* - **清算双路径断言**(§5.5 段②③防线):assertInsideInstallSubtree(删除
|
|
18
|
+
* 目标必须落 `plugins/` 装机子树内)/ assertInsidePluginData(purge 目标
|
|
19
|
+
* 必须解析落在 `data/<id>/` 该插件子目录内)——逃逸即拒,防账本/参数
|
|
20
|
+
* 手编坏形的越界删除。
|
|
21
|
+
*
|
|
22
|
+
* CLI 短命进程形态(零装配直开库同款纪律):本件不开运行时、不装配装载器
|
|
23
|
+
* ——写侧动作的生效时点是下次启动装载(「下次启动生效」诚实回执归
|
|
24
|
+
* plugins-cmd 编排)。
|
|
25
|
+
*/
|
|
26
|
+
import { mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
27
|
+
import { dirname, isAbsolute, join, relative, sep } from 'node:path';
|
|
28
|
+
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
29
|
+
import { BaseError } from '../contracts/index.js';
|
|
30
|
+
import { DOORS_SEGMENT_V1_DOMAIN } from '../contracts/api.js';
|
|
31
|
+
import { canonicalPath } from '../safety/index.js';
|
|
32
|
+
import { enabledYamlPath, parseEnabledRows } from './manifest.js';
|
|
33
|
+
/** 缺省真盘实现 */
|
|
34
|
+
function defaultStoreFs() {
|
|
35
|
+
const sizeOf = (path) => {
|
|
36
|
+
let st;
|
|
37
|
+
try {
|
|
38
|
+
st = statSync(path);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
if (!st.isDirectory())
|
|
44
|
+
return st.size;
|
|
45
|
+
let total = 0;
|
|
46
|
+
for (const name of readdirSync(path))
|
|
47
|
+
total += sizeOf(join(path, name)) ?? 0;
|
|
48
|
+
return total;
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
read: (path) => {
|
|
52
|
+
try {
|
|
53
|
+
return readFileSync(path, 'utf8');
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
write: (path, text) => writeFileSync(path, text),
|
|
60
|
+
rename: (from, to) => renameSync(from, to),
|
|
61
|
+
mkdir: (path, options) => mkdirSync(path, options),
|
|
62
|
+
rm: (path, options) => rmSync(path, options),
|
|
63
|
+
readdir: (path) => {
|
|
64
|
+
try {
|
|
65
|
+
return readdirSync(path);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
size: sizeOf,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** 原子写(tmp+rename——§5.4 账本写面条款;同目录 tmp 保 rename 同卷原子性) */
|
|
75
|
+
function atomicWrite(fs, path, text) {
|
|
76
|
+
const tmp = `${path}.tmp-${process.pid}`;
|
|
77
|
+
fs.write(tmp, text);
|
|
78
|
+
fs.rename(tmp, path);
|
|
79
|
+
}
|
|
80
|
+
/** 账本文件路径(数据目录 plugins/ 子树——与 boot 读侧/check 读侧同源) */
|
|
81
|
+
export function ledgerPath(dataDir) {
|
|
82
|
+
return join(dataDir, 'plugins', 'ledger.json');
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 账本读侧(宽容两式与 boot 读侧同律:条目数组形 / id 键映射形——读宽容是
|
|
86
|
+
* 历史文件兼容面;boot 对损坏 warn 降级,本面返回 invalid 让 CLI 决定降级
|
|
87
|
+
* 还是拒写——写动词对坏账本 fail-loud 拒绝防覆盖装机真相)。
|
|
88
|
+
*/
|
|
89
|
+
export function readLedger(dataDir, fs) {
|
|
90
|
+
const text = fs.read(ledgerPath(dataDir));
|
|
91
|
+
if (text === null)
|
|
92
|
+
return { ok: true, entries: [] }; // 缺席 = 空账本
|
|
93
|
+
const invalid = (reason) => ({ ok: false, reason });
|
|
94
|
+
let doc;
|
|
95
|
+
try {
|
|
96
|
+
doc = JSON.parse(text);
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
return invalid(`坏 JSON(${err instanceof Error ? err.message : String(err)})`);
|
|
100
|
+
}
|
|
101
|
+
const out = [];
|
|
102
|
+
if (Array.isArray(doc)) {
|
|
103
|
+
for (const entry of doc) {
|
|
104
|
+
const checked = checkEntryShape(entry);
|
|
105
|
+
if (checked === null)
|
|
106
|
+
return invalid('数组条目形含坏形条目');
|
|
107
|
+
out.push(checked);
|
|
108
|
+
}
|
|
109
|
+
return { ok: true, entries: dedupeById(out) };
|
|
110
|
+
}
|
|
111
|
+
if (typeof doc === 'object' && doc !== null) {
|
|
112
|
+
for (const [, entry] of Object.entries(doc)) {
|
|
113
|
+
const checked = checkEntryShape(entry);
|
|
114
|
+
if (checked === null)
|
|
115
|
+
return invalid('键映射形条目坏形');
|
|
116
|
+
out.push(checked);
|
|
117
|
+
}
|
|
118
|
+
return { ok: true, entries: dedupeById(out) };
|
|
119
|
+
}
|
|
120
|
+
return invalid('顶层非数组/对象');
|
|
121
|
+
}
|
|
122
|
+
/** 条目形状校验(最小必填面——id/source/ref/installedAt/installPath 恒在) */
|
|
123
|
+
function checkEntryShape(entry) {
|
|
124
|
+
if (typeof entry !== 'object' || entry === null)
|
|
125
|
+
return null;
|
|
126
|
+
const r = entry;
|
|
127
|
+
if (typeof r['id'] !== 'string' || r['id'].length === 0)
|
|
128
|
+
return null;
|
|
129
|
+
if (r['source'] !== 'npm' && r['source'] !== 'git' && r['source'] !== 'local')
|
|
130
|
+
return null;
|
|
131
|
+
if (typeof r['ref'] !== 'string' || r['ref'].length === 0)
|
|
132
|
+
return null;
|
|
133
|
+
if (typeof r['installedAt'] !== 'string')
|
|
134
|
+
return null;
|
|
135
|
+
if (typeof r['installPath'] !== 'string' || r['installPath'].length === 0)
|
|
136
|
+
return null;
|
|
137
|
+
return {
|
|
138
|
+
id: r['id'],
|
|
139
|
+
source: r['source'],
|
|
140
|
+
ref: r['ref'],
|
|
141
|
+
version: typeof r['version'] === 'string' ? r['version'] : undefined,
|
|
142
|
+
integrity: typeof r['integrity'] === 'string' ? r['integrity'] : undefined,
|
|
143
|
+
commit: typeof r['commit'] === 'string' ? r['commit'] : undefined,
|
|
144
|
+
installedAt: r['installedAt'],
|
|
145
|
+
installPath: r['installPath'],
|
|
146
|
+
declaredEvents: Array.isArray(r['declaredEvents'])
|
|
147
|
+
? r['declaredEvents'].filter((e) => typeof e === 'string')
|
|
148
|
+
: [],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/** 同 id 去重(后见胜出——账本手编残影容错,键映射形语义等价) */
|
|
152
|
+
function dedupeById(entries) {
|
|
153
|
+
const byId = new Map();
|
|
154
|
+
for (const e of entries)
|
|
155
|
+
byId.set(e.id, e);
|
|
156
|
+
return [...byId.values()];
|
|
157
|
+
}
|
|
158
|
+
/** 账本原子写(条目数组形——写侧恒出单一规范形) */
|
|
159
|
+
export function writeLedger(dataDir, entries, fs) {
|
|
160
|
+
fs.mkdir(join(dataDir, 'plugins'), { recursive: true });
|
|
161
|
+
atomicWrite(fs, ledgerPath(dataDir), `${JSON.stringify(entries, null, 2)}\n`);
|
|
162
|
+
}
|
|
163
|
+
/** upsert(同 id 后见胜出——install/update 共用;保序:新条目尾追加) */
|
|
164
|
+
export function upsertLedgerEntry(dataDir, entry, fs) {
|
|
165
|
+
const read = readLedger(dataDir, fs);
|
|
166
|
+
// 写前置读已由调用方核过 ok(install/update 编舞先查撞名/查在场)——此处
|
|
167
|
+
// fail-loud 防双检竞速残影(坏账本拒写不覆盖)
|
|
168
|
+
if (!read.ok)
|
|
169
|
+
throw new BaseError('PLUGIN_INSTALL_FAILED', `装机账本损坏,拒写防覆盖:${read.reason}`);
|
|
170
|
+
const rest = read.entries.filter((e) => e.id !== entry.id);
|
|
171
|
+
writeLedger(dataDir, [...rest, entry], fs);
|
|
172
|
+
}
|
|
173
|
+
/** 删条目(uninstall 段②后账面收口;id 查无 = no-op 幂等) */
|
|
174
|
+
export function removeLedgerEntry(dataDir, id, fs) {
|
|
175
|
+
const read = readLedger(dataDir, fs);
|
|
176
|
+
if (!read.ok)
|
|
177
|
+
throw new BaseError('PLUGIN_UNINSTALL_REFUSED', `装机账本损坏,拒写防覆盖:${read.reason}`);
|
|
178
|
+
writeLedger(dataDir, read.entries.filter((e) => e.id !== id), fs);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* 读启用行集 + 顶层 doors 段(缺席 = 空集——§5.3 缺席语义;损坏 = fail-loud
|
|
182
|
+
* result 面:CLI 呈现修复指引退 1,boot 装载序同判据拒启——两律分立但判据
|
|
183
|
+
* 单源)。doors 段随行集一并读出(开门制扩展批 2026-09-09——行编辑腿全文件
|
|
184
|
+
* 形状往返保真:写侧必须原样携带 doors 段,静默抹段 = 静默收回用户授予)。
|
|
185
|
+
*/
|
|
186
|
+
export function readEnabledRowsForEdit(dataDir, fs) {
|
|
187
|
+
const text = fs.read(enabledYamlPath(dataDir));
|
|
188
|
+
if (text === null)
|
|
189
|
+
return { ok: true, rows: [], doors: undefined };
|
|
190
|
+
let doc;
|
|
191
|
+
try {
|
|
192
|
+
doc = parseYaml(text);
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
return {
|
|
196
|
+
ok: false,
|
|
197
|
+
message: `启用清单损坏:${err instanceof Error ? err.message : String(err)}——修复或删除 ${enabledYamlPath(dataDir)}(删除即回全 core: 内置态)`,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
const result = parseEnabledRows(doc);
|
|
201
|
+
if (!result.ok) {
|
|
202
|
+
return {
|
|
203
|
+
ok: false,
|
|
204
|
+
message: `启用清单校验失败:${result.message}——修复指引:顶层 { plugins: [{ id, config?, disabled?, opens? }], doors?: [...] }`,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
return { ok: true, rows: result.rows, doors: parseDoorsPresence(doc) };
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* doors 段在场性判别(往返保真位——parseEnabledRows 已把缺席归一为 [],本函数
|
|
211
|
+
* 从原文档辨「段缺席」与「段显式空」:显式 `doors: []` 编辑后仍写回显式空段
|
|
212
|
+
* (用户手编形状原样保真),段缺席则不凭空造段)。
|
|
213
|
+
*/
|
|
214
|
+
function parseDoorsPresence(doc) {
|
|
215
|
+
const raw = doc?.['doors'];
|
|
216
|
+
return raw === undefined ? undefined : Array.isArray(raw) ? raw : undefined;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* mount:append 启用行(§5.3——行不在场才合法;撞名拒由编舞层前置查账,
|
|
220
|
+
* 本面保底双检)。core: id 合法(overlay 用户行——覆盖内置默认态的主用例)。
|
|
221
|
+
* 成功尾落 plugin/mounted(生命周期归因账——本账含 core: 行:mount 对
|
|
222
|
+
* core: 行是 overlay 合法动作,与 opens diff 排除 core: 分立)。
|
|
223
|
+
*/
|
|
224
|
+
export function mountRow(dataDir, id, config, fs, onLifecycleAudit) {
|
|
225
|
+
const read = readEnabledRowsForEdit(dataDir, fs);
|
|
226
|
+
if (!read.ok)
|
|
227
|
+
return read;
|
|
228
|
+
if (read.rows.some((row) => row.id === id)) {
|
|
229
|
+
return { ok: false, message: `插件 ${id} 启用行已在场——mount 拒撞名(改配置 = unmount 后重 mount;03 §5.6)` };
|
|
230
|
+
}
|
|
231
|
+
const row = { id, ...(config !== undefined ? { config } : {}) };
|
|
232
|
+
writeEnabledRows(dataDir, [...read.rows, row], read.doors, fs);
|
|
233
|
+
onLifecycleAudit?.('plugin/mounted', { id });
|
|
234
|
+
return { ok: true };
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* unmount:删启用行保装机(§5.3)。行不在场:core: id = 内置态不可删(指路
|
|
238
|
+
* toggle——unmount 语义是移出启用面,core: 内置全启无行可删);用户 id =
|
|
239
|
+
* 幂等跳过(已不在启用面)。幂等跳过不落账(无变更不造账——与 update
|
|
240
|
+
* local no-op 同律);真删行成功尾落 plugin/unmounted。
|
|
241
|
+
*/
|
|
242
|
+
export function unmountRow(dataDir, id, fs, onLifecycleAudit) {
|
|
243
|
+
const read = readEnabledRowsForEdit(dataDir, fs);
|
|
244
|
+
if (!read.ok)
|
|
245
|
+
return read;
|
|
246
|
+
if (!read.rows.some((row) => row.id === id)) {
|
|
247
|
+
if (id.startsWith('core:')) {
|
|
248
|
+
return { ok: false, message: `官方件 ${id} 内置全启无启用行可删——临时停用走 toggle(03 §5.2 三态语义)` };
|
|
249
|
+
}
|
|
250
|
+
return { ok: true }; // 幂等(已不在启用面)
|
|
251
|
+
}
|
|
252
|
+
writeEnabledRows(dataDir, read.rows.filter((row) => row.id !== id), read.doors, fs);
|
|
253
|
+
onLifecycleAudit?.('plugin/unmounted', { id });
|
|
254
|
+
return { ok: true };
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* toggle:翻禁用旗标(§5.2 第三态)。行在场翻 disabled(absent ↔ true——
|
|
258
|
+
* 无 false 态:行缺 disabled 键即启用);行不在场写 `{id, disabled: true}`
|
|
259
|
+
* 行(disable 内置 core: 件的主路径;用户插件同形)。成功尾落
|
|
260
|
+
* plugin/toggled {id, disabled 终态}——审计词形双态 true|false 独立于
|
|
261
|
+
* enabled.yaml 行形(行内无 false 键,翻回启用需 false 位表达)。
|
|
262
|
+
*/
|
|
263
|
+
export function toggleRow(dataDir, id, fs, onLifecycleAudit) {
|
|
264
|
+
const read = readEnabledRowsForEdit(dataDir, fs);
|
|
265
|
+
if (!read.ok)
|
|
266
|
+
return read;
|
|
267
|
+
const rows = [...read.rows];
|
|
268
|
+
const index = rows.findIndex((row) => row.id === id);
|
|
269
|
+
let disabled;
|
|
270
|
+
if (index === -1) {
|
|
271
|
+
rows.push({ id, disabled: true });
|
|
272
|
+
disabled = true;
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
const current = rows[index];
|
|
276
|
+
if (current.disabled === true) {
|
|
277
|
+
// 已禁用 → 回启用(行保场、旗标撤——config/opens 等字段原样保留)
|
|
278
|
+
const { disabled: _drop, ...rest } = current;
|
|
279
|
+
rows[index] = rest;
|
|
280
|
+
disabled = false;
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
// 启用中 → 禁用(字段原样保留,只翻旗标)
|
|
284
|
+
rows[index] = { ...current, disabled: true };
|
|
285
|
+
disabled = true;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
writeEnabledRows(dataDir, rows, read.doors, fs);
|
|
289
|
+
onLifecycleAudit?.('plugin/toggled', { id, disabled });
|
|
290
|
+
return { ok: true };
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* setRowConfig:行 config 键整值替换(ix-3——03 §1.2 表单腿写盘位)。
|
|
294
|
+
* 保行内其余字段原样(disabled/opens——整值替换只及 config 键)+ doors 段
|
|
295
|
+
* 往返保真。行不在场:core: id = overlay 新行合法(内置态无行,写 config
|
|
296
|
+
* 即立 overlay 行);用户 id = 拒(未启用行编辑 = 先 mount——表单对不在
|
|
297
|
+
* 启用面的用户插件诚实指路,不凭空造行)。
|
|
298
|
+
*
|
|
299
|
+
* 审计词缺席注记:生命周期五词(05 §1.1)辖装机/挂载/卸下/翻转/更新五
|
|
300
|
+
* 动词——config 编辑不在六动词面(ix-1 规范笔未立词);secret 写凭证盒
|
|
301
|
+
* 有自有审计道(credentials/changed seam——表单腿装配位接线)。
|
|
302
|
+
*/
|
|
303
|
+
export function setRowConfig(dataDir, id, config, fs) {
|
|
304
|
+
const read = readEnabledRowsForEdit(dataDir, fs);
|
|
305
|
+
if (!read.ok)
|
|
306
|
+
return read;
|
|
307
|
+
if (typeof config !== 'object' || config === null || Array.isArray(config)) {
|
|
308
|
+
return { ok: false, message: 'config 须为对象(整值替换非合并)——表单腿产物恒对象,此处为防御位' };
|
|
309
|
+
}
|
|
310
|
+
const rows = [...read.rows];
|
|
311
|
+
const index = rows.findIndex((row) => row.id === id);
|
|
312
|
+
if (index === -1) {
|
|
313
|
+
if (!id.startsWith('core:')) {
|
|
314
|
+
return { ok: false, message: `插件 ${id} 不在启用面——config 编辑先走 /plugins mount(不凭空造行)` };
|
|
315
|
+
}
|
|
316
|
+
rows.push({ id, config });
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
rows[index] = { ...rows[index], config };
|
|
320
|
+
}
|
|
321
|
+
writeEnabledRows(dataDir, rows, read.doors, fs);
|
|
322
|
+
return { ok: true };
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* enabled.yaml 原子写(yaml 序列化——与读侧形状对偶)。doors 段原样携带
|
|
326
|
+
* (开门制扩展批 2026-09-09——03 §5.3 行编辑与段编辑全文件形状往返保真律:
|
|
327
|
+
* 行编辑腿〔mount/uninstall/toggle〕重写本文件时 doors 段必须保真,静默抹段
|
|
328
|
+
* = 静默收回用户授予,结构性禁止);doors 缺席(段不在场)不凭空造段。
|
|
329
|
+
*/
|
|
330
|
+
function writeEnabledRows(dataDir, rows, doors, fs) {
|
|
331
|
+
const doc = {
|
|
332
|
+
plugins: rows.map((row) => {
|
|
333
|
+
const out = { id: row.id };
|
|
334
|
+
if (row.config !== undefined)
|
|
335
|
+
out['config'] = row.config;
|
|
336
|
+
if (row.disabled !== undefined)
|
|
337
|
+
out['disabled'] = row.disabled;
|
|
338
|
+
if (row.opens !== undefined)
|
|
339
|
+
out['opens'] = [...row.opens];
|
|
340
|
+
return out;
|
|
341
|
+
}),
|
|
342
|
+
...(doors !== undefined ? { doors: [...doors] } : {}),
|
|
343
|
+
};
|
|
344
|
+
atomicWrite(fs, enabledYamlPath(dataDir), stringifyYaml(doc));
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* doors 段编辑(03 §4.6 /doors 人面命令 = enabled.yaml 的编辑腿——g-2 落码):
|
|
348
|
+
* open/close **只动段不动行**(行集原样携带——与行编辑腿全文件形状往返保真律
|
|
349
|
+
* 对偶方向);值域执法 door ∈ DOORS_SEGMENT_V1_DOMAIN(射程即值域——六枚
|
|
350
|
+
* 全宽将致三读分叉,拒绝式单源同 manifest 段校验)。
|
|
351
|
+
*
|
|
352
|
+
* 幂等律:open 已开 / close 未开 = no-op 成功**不写文件不落账**(无变更不
|
|
353
|
+
* 造账——与 mount 幂等跳过、update local no-op 同律);段缺席时 close 天然
|
|
354
|
+
* 幂等(现集空不含任何位)、open 造段(`doors: [<位>]`——用户开门动作即段
|
|
355
|
+
* 在场事实);close 到空集写回**显式空段** `doors: []`(保真 + boot diff
|
|
356
|
+
* `doors: []` 收口形的文件侧真相——03 §4.6「撤位 doors:[] 收口」)。
|
|
357
|
+
* 写回形 = 去重排序稳态形(手编任意序在首编辑后归一——审计快照排序形一致)。
|
|
358
|
+
*/
|
|
359
|
+
export function editDoorsSegment(dataDir, action, fs, onDoorsUpdated) {
|
|
360
|
+
// 值域执法(DOORS_SEGMENT_V1_DOMAIN 单源——与 manifest 段校验同源两执法位)
|
|
361
|
+
if (!DOORS_SEGMENT_V1_DOMAIN.includes(action.door)) {
|
|
362
|
+
return {
|
|
363
|
+
ok: false,
|
|
364
|
+
message: `doors 段值域外能力位 "${action.door}"——值域 v1 = 两门枚举(现役:${DOORS_SEGMENT_V1_DOMAIN.join('、')};§8.2 六枚高危面名单系上界,行 opens 位承载插件道四枚)`,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
const read = readEnabledRowsForEdit(dataDir, fs);
|
|
368
|
+
if (!read.ok)
|
|
369
|
+
return read;
|
|
370
|
+
const current = new Set(read.doors ?? []); // 段缺席 = 空集(§5.3 缺席语义)
|
|
371
|
+
const contains = current.has(action.door);
|
|
372
|
+
if (action.verb === 'open' ? contains : !contains) {
|
|
373
|
+
return { ok: true }; // 幂等 no-op——不写文件不落账(无变更不造账)
|
|
374
|
+
}
|
|
375
|
+
if (action.verb === 'open')
|
|
376
|
+
current.add(action.door);
|
|
377
|
+
else
|
|
378
|
+
current.delete(action.door);
|
|
379
|
+
const doors = [...current].sort(); // 去重排序稳态形(Set 天然去重)
|
|
380
|
+
writeEnabledRows(dataDir, read.rows, doors, fs);
|
|
381
|
+
onDoorsUpdated?.(doors);
|
|
382
|
+
return { ok: true };
|
|
383
|
+
}
|
|
384
|
+
/* ---------------- installPath 推导(§5.4 归一路径载体——写侧执法) ---------------- */
|
|
385
|
+
/**
|
|
386
|
+
* npm 源归一路径(相对数据目录):`plugins/node_modules/<包名>`——scoped 包
|
|
387
|
+
* 照包名原形嵌套 `plugins/node_modules/@scope/pkg/`(§5.4 物理布局——账本
|
|
388
|
+
* id 与目录名解耦,无隐式映射变换)。
|
|
389
|
+
*/
|
|
390
|
+
export function installPathForNpm(pkgName) {
|
|
391
|
+
return join('plugins', 'node_modules', ...pkgName.split('/'));
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* git 源归一路径(相对数据目录):`plugins/git/<host>/<首段>/<repo 名>`——
|
|
395
|
+
* 分层防撞名(§5.4 物理布局)。url 解析:剥 scheme 后取 host 与 path 首两段;
|
|
396
|
+
* `.git` 尾缀剥除。解析失败(无 host/段不足)抛 BaseError——ref 坏形属
|
|
397
|
+
* CLI 解析层前置校验射程,此处保底。
|
|
398
|
+
*/
|
|
399
|
+
export function installPathForGit(url) {
|
|
400
|
+
const stripped = url.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, ''); // ssh:// 或 https:// 形
|
|
401
|
+
const [hostPart, ...pathParts] = stripped.split('/');
|
|
402
|
+
if (hostPart === undefined || hostPart.length === 0 || pathParts.length < 2) {
|
|
403
|
+
throw new BaseError('PLUGIN_INSTALL_FAILED', `git url 坏形(${url})——期望 https://<host>/<首段>/<repo> 形`);
|
|
404
|
+
}
|
|
405
|
+
const first = pathParts[0];
|
|
406
|
+
const repoRaw = pathParts[pathParts.length - 1];
|
|
407
|
+
const repo = repoRaw.endsWith('.git') ? repoRaw.slice(0, -'.git'.length) : repoRaw;
|
|
408
|
+
if (first.length === 0 || repo.length === 0) {
|
|
409
|
+
throw new BaseError('PLUGIN_INSTALL_FAILED', `git url 坏形(${url})——首段或仓段为空`);
|
|
410
|
+
}
|
|
411
|
+
return join('plugins', 'git', hostPart, first, repo);
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* local 源归一路径(绝对——canonical 化值;§5.4 表示法:直引源即真相)。
|
|
415
|
+
* canonicalPath 解析失败(病理形态)抛——install 编舞 catch 归 ref 坏形档。
|
|
416
|
+
*/
|
|
417
|
+
export function installPathForLocal(absPath) {
|
|
418
|
+
return canonicalPath(absPath);
|
|
419
|
+
}
|
|
420
|
+
/* ---------------- 清算双路径断言(§5.5 段②③防线) ---------------- */
|
|
421
|
+
/**
|
|
422
|
+
* 段②防线(§5.5):删除目标必须落在 `plugins/` 装机子树内。canonical 解析
|
|
423
|
+
* (符号链归一——防 alias 逃逸)后前缀判;恰等装机树根本身亦合法(幂等重跑
|
|
424
|
+
* 的空树形)。逃逸抛 PLUGIN_UNINSTALL_REFUSED(防线拒绝档——非业务失败)。
|
|
425
|
+
*/
|
|
426
|
+
export function assertInsideInstallSubtree(dataDir, target) {
|
|
427
|
+
const subtree = canonicalPath(join(dataDir, 'plugins'));
|
|
428
|
+
const resolved = canonicalPath(target);
|
|
429
|
+
if (resolved !== subtree && !resolved.startsWith(`${subtree}${sep}`)) {
|
|
430
|
+
throw new BaseError('PLUGIN_UNINSTALL_REFUSED', `清算防线:删除目标 ${target}(解析 ${resolved})逸出装机子树 ${subtree}——账本坏形,拒删(03 §5.5 段②)`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* 段③防线(§5.5):purge 目标必须解析落在 `data/<id>/` 该插件子目录内(恰
|
|
435
|
+
* 等子目录本身或其内路径——幂等重跑兼容)。逃逸抛 PLUGIN_UNINSTALL_REFUSED。
|
|
436
|
+
*/
|
|
437
|
+
export function assertInsidePluginData(dataDir, id, target) {
|
|
438
|
+
const own = canonicalPath(join(dataDir, 'data', id));
|
|
439
|
+
const resolved = canonicalPath(target);
|
|
440
|
+
if (resolved !== own && !resolved.startsWith(`${own}${sep}`)) {
|
|
441
|
+
throw new BaseError('PLUGIN_UNINSTALL_REFUSED', `purge 防线:删除目标 ${target}(解析 ${resolved})逸出插件数据子目录 ${own}——拒删(03 §5.5 段③)`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
/** 数据域子目录路径构造(purge 物理动作唯一合法目标——构造即受 §5.5 断言保护) */
|
|
445
|
+
export function pluginDataDir(dataDir, id) {
|
|
446
|
+
return join(dataDir, 'data', id);
|
|
447
|
+
}
|
|
448
|
+
/** installPath 解析为绝对目录(读侧解析序单源:绝对直用/相对 join 数据目录) */
|
|
449
|
+
export function resolveInstallPath(dataDir, installPath) {
|
|
450
|
+
return isAbsolute(installPath) ? installPath : join(dataDir, installPath);
|
|
451
|
+
}
|
|
452
|
+
/** 相对数据目录表示(写侧反向——诊断/呈现用;不在数据目录内 = 原样绝对) */
|
|
453
|
+
export function relativizeAgainst(dataDir, absPath) {
|
|
454
|
+
const rel = relative(dataDir, absPath);
|
|
455
|
+
return rel.startsWith('..') ? absPath : rel;
|
|
456
|
+
}
|
|
457
|
+
/** dirname 导出(调用方目录准备用——mkdir 装机树父目录) */
|
|
458
|
+
export { dirname as parentDir };
|
|
459
|
+
/** 缺省真盘 fs 单例工厂(plugins-cmd 直开真盘;测试注内存形) */
|
|
460
|
+
export function createPluginStoreFs() {
|
|
461
|
+
return defaultStoreFs();
|
|
462
|
+
}
|