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,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* testkit 假宿主装配件(03 §9.5——生态启动批 eco-3c)。
|
|
3
|
+
*
|
|
4
|
+
* 铁律(真装载器——deepseek-harness「真入口路径」教训照搬):一切断言经
|
|
5
|
+
* 真装载整体走真——installPlugin/mountRow/toggleRow/bootPlugins 全是真身
|
|
6
|
+
* 直调(真 jiti 求值、真审计词、真 enabled.yaml/账本落盘),本件只提供
|
|
7
|
+
* 假宿主受局面(runtime 替身 + 注入式注册账 + 内存审计汇)。手工拼
|
|
8
|
+
* ctx.plugin 的套件不算数。
|
|
9
|
+
*
|
|
10
|
+
* 消费位:作者测试文件经 `berry-agent/testkit` 子路径导入(作者侧 devDep
|
|
11
|
+
* 消费——非装载器 jiti 注入面,03 §8.2 第七真相源「宿主主包子路径导出面」)。
|
|
12
|
+
*/
|
|
13
|
+
import { execFile } from 'node:child_process';
|
|
14
|
+
import { mkdirSync, mkdtempSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from 'node:fs';
|
|
15
|
+
import { tmpdir } from 'node:os';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
import { promisify } from 'node:util';
|
|
18
|
+
import { parseManifest } from '../manifest.js';
|
|
19
|
+
import { installPlugin } from '../plugin-install.js';
|
|
20
|
+
import { bootPlugins } from '../plugin-boot.js';
|
|
21
|
+
import { mountRow, readEnabledRowsForEdit, toggleRow } from '../plugin-store.js';
|
|
22
|
+
import { EventDispatch, Scope } from '../../context/index.js';
|
|
23
|
+
import { stringify as stringifyYaml } from 'yaml';
|
|
24
|
+
/** 内存审计汇(AuditFace 真形实现——append/lastOf/listRecent 三法 + 断言器扩展读面) */
|
|
25
|
+
export class MemoryAuditSink {
|
|
26
|
+
seq = 0;
|
|
27
|
+
rows = [];
|
|
28
|
+
/** 直写落账(真词面由调用方保证——本汇不做词汇闸,断言器锁真词) */
|
|
29
|
+
append(type, data) {
|
|
30
|
+
this.rows.push({ id: ++this.seq, time: Date.now(), type, data: { ...data } });
|
|
31
|
+
}
|
|
32
|
+
/** 尾读:该词最近一条(从未落过 = undefined) */
|
|
33
|
+
lastOf(type) {
|
|
34
|
+
for (let i = this.rows.length - 1; i >= 0; i--) {
|
|
35
|
+
if (this.rows[i].type === type)
|
|
36
|
+
return this.rows[i];
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
/** 逆序近期清单(id 降序——与真源同序律) */
|
|
41
|
+
listRecent(limit = 100) {
|
|
42
|
+
return [...this.rows].reverse().slice(0, limit);
|
|
43
|
+
}
|
|
44
|
+
/* ---------------- 断言器扩展读面(testkit 专属——不进 AuditFace 契约) ---------------- */
|
|
45
|
+
/** 全量快照(正序——残留检查/白名单对账的数据源) */
|
|
46
|
+
all() {
|
|
47
|
+
return [...this.rows];
|
|
48
|
+
}
|
|
49
|
+
/** 词计数(可选拘载荷谓词——「恰一笔」断言的数据源) */
|
|
50
|
+
countOf(type, matches) {
|
|
51
|
+
return this.rows.filter((r) => r.type === type && (matches === undefined || matches(r.data))).length;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** 命令注册账铸造(disposer 出账——换代 dispose 后单注可证) */
|
|
55
|
+
export function createCommandAccount() {
|
|
56
|
+
const counts = new Map();
|
|
57
|
+
return {
|
|
58
|
+
registry: {
|
|
59
|
+
register: (name, _handler, _description) => {
|
|
60
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
61
|
+
return () => counts.set(name, (counts.get(name) ?? 0) - 1);
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
counts: () => new Map(counts),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** 分派器监听器探针铸造(on 注册 +1、退订 −1;onWaterfall 腿 v1 未观察——注释明示) */
|
|
68
|
+
export function instrumentDispatchListeners(dispatch) {
|
|
69
|
+
const net = new Map();
|
|
70
|
+
const origOn = dispatch.on.bind(dispatch);
|
|
71
|
+
// 实例属性包壳(真身在原型——不替换派发语义,只在注册/退订两点记账)
|
|
72
|
+
dispatch.on = ((name, listener) => {
|
|
73
|
+
net.set(name, (net.get(name) ?? 0) + 1);
|
|
74
|
+
const off = origOn(name, listener);
|
|
75
|
+
return () => {
|
|
76
|
+
off();
|
|
77
|
+
net.set(name, (net.get(name) ?? 0) - 1);
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
return { netCounts: () => new Map(net) };
|
|
81
|
+
}
|
|
82
|
+
/** 假宿主真盘数据面(PluginStoreFs 真身适配——node:fs 直连,无内存仿真) */
|
|
83
|
+
export function realStoreFs() {
|
|
84
|
+
return {
|
|
85
|
+
read: (path) => {
|
|
86
|
+
try {
|
|
87
|
+
return readFileSync(path, 'utf8');
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
write: (path, text) => writeFileSync(path, text, 'utf8'),
|
|
94
|
+
rename: (from, to) => renameSync(from, to),
|
|
95
|
+
mkdir: (path, options) => mkdirSync(path, options),
|
|
96
|
+
rm: (path, options) => rmSync(path, options),
|
|
97
|
+
readdir: (path) => {
|
|
98
|
+
try {
|
|
99
|
+
return readdirSync(path);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
size: (path) => {
|
|
106
|
+
try {
|
|
107
|
+
return statSync(path).size;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/** HostRuntime 结构化替身(装配序只消费 dataDir/registerCloser——同 plugin-boot.test 形) */
|
|
116
|
+
function createHarnessRuntime(dataDir) {
|
|
117
|
+
return {
|
|
118
|
+
memory: false,
|
|
119
|
+
dataDir,
|
|
120
|
+
persistence: {},
|
|
121
|
+
abortSignal: new AbortController().signal,
|
|
122
|
+
disclosure: () => null,
|
|
123
|
+
registerCloser: () => undefined,
|
|
124
|
+
registerShutdownHook: () => undefined,
|
|
125
|
+
registerDisposer: () => undefined,
|
|
126
|
+
shutdown: async () => undefined,
|
|
127
|
+
writeCrashLog: () => undefined,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/** llm 注册账(Pick 受局面最小形——provider 注册动词真达即净计;出账可证) */
|
|
131
|
+
function createLlmAccount() {
|
|
132
|
+
let net = 0;
|
|
133
|
+
return {
|
|
134
|
+
registerProvider: () => {
|
|
135
|
+
net += 1;
|
|
136
|
+
return () => {
|
|
137
|
+
net -= 1;
|
|
138
|
+
};
|
|
139
|
+
},
|
|
140
|
+
netCount: () => net,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/** 装配假宿主(构造即读清单定 id——坏清单构造即红,坏样本由矩阵层收面) */
|
|
144
|
+
export function createPluginHarness(options) {
|
|
145
|
+
const pluginDir = options.pluginDir;
|
|
146
|
+
const pkgText = realStoreFs().read(join(pluginDir, 'package.json'));
|
|
147
|
+
if (pkgText === null) {
|
|
148
|
+
throw new Error(`testkit:${pluginDir} 缺 package.json(插件清单缺席——非插件目录)`);
|
|
149
|
+
}
|
|
150
|
+
let pkgJson;
|
|
151
|
+
try {
|
|
152
|
+
pkgJson = JSON.parse(pkgText);
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
throw new Error(`testkit:package.json 非合法 JSON(${pluginDir}):${err instanceof Error ? err.message : String(err)}`);
|
|
156
|
+
}
|
|
157
|
+
const parsedManifest = parseManifest(pkgJson, { official: false });
|
|
158
|
+
if (!parsedManifest.ok) {
|
|
159
|
+
throw new Error(`testkit:清单校验失败(${pluginDir}):${parsedManifest.message}`);
|
|
160
|
+
}
|
|
161
|
+
const ownsDataDir = options.dataDir === undefined;
|
|
162
|
+
const dataDir = options.dataDir ?? mkdtempSync(join(tmpdir(), 'berry-testkit-'));
|
|
163
|
+
const storeFs = realStoreFs();
|
|
164
|
+
const audit = new MemoryAuditSink();
|
|
165
|
+
const commands = createCommandAccount();
|
|
166
|
+
const dispatch = new EventDispatch();
|
|
167
|
+
const listeners = instrumentDispatchListeners(dispatch);
|
|
168
|
+
const runtime = createHarnessRuntime(dataDir);
|
|
169
|
+
const scope = Scope.createRoot();
|
|
170
|
+
const lifecycleSink = (type, data) => audit.append(type, data);
|
|
171
|
+
let currentBoot = null;
|
|
172
|
+
// 本代完整卸载序(unloadRef 槽——apply disposer LIFO + fork 逆序 dispose
|
|
173
|
+
// 两腿全跑;report.unload() 只是 disposeStack 单腿,ctx.effect 注册回卷
|
|
174
|
+
// 在 fork 腿,故换代/收场必须走槽不走 report)
|
|
175
|
+
let currentUnload = null;
|
|
176
|
+
return {
|
|
177
|
+
dataDir,
|
|
178
|
+
pluginDir,
|
|
179
|
+
pluginId: parsedManifest.manifest.id,
|
|
180
|
+
manifest: parsedManifest.manifest,
|
|
181
|
+
packageJson: pkgJson,
|
|
182
|
+
audit,
|
|
183
|
+
commands,
|
|
184
|
+
dispatch,
|
|
185
|
+
listeners,
|
|
186
|
+
async install() {
|
|
187
|
+
const outcome = await installPlugin({
|
|
188
|
+
dataDir,
|
|
189
|
+
fs: storeFs,
|
|
190
|
+
// local: 源零 spawn 调用——占位 fail-loud(若未来装机编舞对 local
|
|
191
|
+
// 源触网,此占位当场红——testkit 断言面禁网)
|
|
192
|
+
spawn: {
|
|
193
|
+
run: async (cmd) => {
|
|
194
|
+
throw new Error(`testkit install 走 local: 源——不应触达执行器(${cmd})`);
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
onLifecycleAudit: lifecycleSink,
|
|
198
|
+
}, `local:${pluginDir}`);
|
|
199
|
+
return outcome.ok
|
|
200
|
+
? { ok: true, id: outcome.entry.id, installPath: outcome.entry.installPath }
|
|
201
|
+
: { ok: false, message: outcome.message };
|
|
202
|
+
},
|
|
203
|
+
mountRow() {
|
|
204
|
+
return mountRow(dataDir, parsedManifest.manifest.id, undefined, storeFs, lifecycleSink);
|
|
205
|
+
},
|
|
206
|
+
toggleRow() {
|
|
207
|
+
return toggleRow(dataDir, parsedManifest.manifest.id, storeFs, lifecycleSink);
|
|
208
|
+
},
|
|
209
|
+
async boot() {
|
|
210
|
+
// 换代前置回卷(/reload 同语义:先卸旧代再起新代——不并代;完整卸载
|
|
211
|
+
// 序含 fork dispose,ctx.effect 注册在换代时自动撤注)
|
|
212
|
+
if (currentUnload !== null)
|
|
213
|
+
await currentUnload();
|
|
214
|
+
// 每代独立槽(bootPlugins 重跑即覆写槽——旧代卸载序须先行持走)
|
|
215
|
+
const unloadRef = { current: null };
|
|
216
|
+
const bootOptions = {
|
|
217
|
+
runtime,
|
|
218
|
+
scope,
|
|
219
|
+
dispatch,
|
|
220
|
+
commands: commands.registry,
|
|
221
|
+
llm: createLlmAccount(),
|
|
222
|
+
version: options.hostVersion ?? '0.0.0-testkit',
|
|
223
|
+
warn: options.warn ?? (() => undefined),
|
|
224
|
+
audit,
|
|
225
|
+
unloadRef,
|
|
226
|
+
};
|
|
227
|
+
currentBoot = await bootPlugins(bootOptions);
|
|
228
|
+
currentUnload = unloadRef.current;
|
|
229
|
+
return currentBoot;
|
|
230
|
+
},
|
|
231
|
+
async unloadCurrent() {
|
|
232
|
+
const unload = currentUnload;
|
|
233
|
+
currentUnload = null;
|
|
234
|
+
currentBoot = null;
|
|
235
|
+
return unload === null ? null : await unload();
|
|
236
|
+
},
|
|
237
|
+
grantOpens(opens) {
|
|
238
|
+
// 手编 enabled.yaml 形(03 §5.3 用户可编辑面的文档形——行级 opens 授予
|
|
239
|
+
// 无 CLI 动词,宿主真源路径即手编;此处以同形 yaml 重写全文件)
|
|
240
|
+
const read = readEnabledRowsForEdit(dataDir, storeFs);
|
|
241
|
+
if (!read.ok)
|
|
242
|
+
throw new Error(`testkit:enabled.yaml 坏形(${read.message})`);
|
|
243
|
+
const rows = read.rows.map((row) => row.id === parsedManifest.manifest.id ? { ...row, opens: [...opens] } : row);
|
|
244
|
+
storeFs.write(join(dataDir, 'enabled.yaml'), stringifyYaml({ plugins: rows, ...((read.doors ?? []).length > 0 ? { doors: read.doors } : {}) }));
|
|
245
|
+
},
|
|
246
|
+
dataFiles() {
|
|
247
|
+
const top = (storeFs.readdir(dataDir) ?? []).filter((name) => name !== '.DS_Store');
|
|
248
|
+
const under = [];
|
|
249
|
+
for (const name of top) {
|
|
250
|
+
if (name === 'plugins') {
|
|
251
|
+
for (const child of storeFs.readdir(join(dataDir, 'plugins')) ?? []) {
|
|
252
|
+
under.push(`plugins/${child}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
under.push(name);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return under;
|
|
260
|
+
},
|
|
261
|
+
async dispose() {
|
|
262
|
+
try {
|
|
263
|
+
if (currentUnload !== null)
|
|
264
|
+
await currentUnload();
|
|
265
|
+
}
|
|
266
|
+
finally {
|
|
267
|
+
if (ownsDataDir)
|
|
268
|
+
rmSync(dataDir, { recursive: true, force: true });
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
/** npm pack 文件清单探测(--dry-run 零 tarball 落盘;60s 帽防挂) */
|
|
274
|
+
export async function npmPackFiles(pluginDir) {
|
|
275
|
+
const run = promisify(execFile);
|
|
276
|
+
const { stdout } = await run('npm', ['pack', '--dry-run', '--json'], {
|
|
277
|
+
cwd: pluginDir,
|
|
278
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
279
|
+
timeout: 60_000,
|
|
280
|
+
});
|
|
281
|
+
const parsed = JSON.parse(stdout);
|
|
282
|
+
return (parsed[0]?.files ?? []).map((f) => f.path);
|
|
283
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `berry-agent/testkit` 主包子路径公开面(03 §9.5——生态启动批 eco-3c)。
|
|
3
|
+
*
|
|
4
|
+
* 消费位:插件作者的 devDep 测试文件(`import { proveLifecycleMatrix } from
|
|
5
|
+
* 'berry-agent/testkit'`)——作者侧消费,非装载器 jiti 注入进插件沙箱
|
|
6
|
+
* (故不入 VIRTUAL_API_KEYS 六键表——03 §8.2 第七真相源独立子路径域)。
|
|
7
|
+
*
|
|
8
|
+
* 首版导出面:
|
|
9
|
+
* - proveLifecycleMatrix / formatMatrixReceipt —— 生命周期证明矩阵 + README 回执;
|
|
10
|
+
* - createPluginHarness 及配套件 —— 矩阵底座(假宿主装配),作者自定义
|
|
11
|
+
* 断言场景亦可直用(全部动词真身直调)。
|
|
12
|
+
*/
|
|
13
|
+
export { createPluginHarness, createCommandAccount, instrumentDispatchListeners, MemoryAuditSink, npmPackFiles, realStoreFs, } from './harness.js';
|
|
14
|
+
export { proveLifecycleMatrix, formatMatrixReceipt } from './matrix.js';
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* testkit 生命周期证明矩阵(03 §9.5 首版断言矩阵——生态启动批 eco-3c)。
|
|
3
|
+
*
|
|
4
|
+
* 列 = 纯函数断言 / 假宿主断言(真宿主层延后随金样轨并轨——首版锁假宿主
|
|
5
|
+
* 层 + 真装载器两件防范围膨胀)。行 = install/mount/unmount/事件面必进;
|
|
6
|
+
* toggle/开门面/幂等负向顺手进;update/审批面/真宿主层延后(规范表同序)。
|
|
7
|
+
*
|
|
8
|
+
* 全部断言经 harness 真装载整体走真(installPlugin/mountRow/toggleRow/
|
|
9
|
+
* bootPlugins/report.unload 真身直调)——手工拼 ctx.plugin 的证明不算数。
|
|
10
|
+
* 产出可贴 README 的 markdown 回执(formatMatrixReceipt)。
|
|
11
|
+
*/
|
|
12
|
+
import { readdirSync, statSync } from 'node:fs';
|
|
13
|
+
import { join, posix } from 'node:path';
|
|
14
|
+
import { createJiti } from 'jiti';
|
|
15
|
+
import { createPluginHarness, npmPackFiles } from './harness.js';
|
|
16
|
+
/** 宿主记账方白名单(审计词面——宿主自身记账是合法残留,dsh 快照 diff 同律) */
|
|
17
|
+
const HOST_AUDIT_WHITELIST = new Set([
|
|
18
|
+
'plugin/installed',
|
|
19
|
+
'plugin/mounted',
|
|
20
|
+
'plugin/unmounted',
|
|
21
|
+
'plugin/toggled',
|
|
22
|
+
'plugin/updated',
|
|
23
|
+
'plugin/opens',
|
|
24
|
+
'doors/updated',
|
|
25
|
+
]);
|
|
26
|
+
/** skipped 行信号(runRow 识别后落 skipped 态——环境不适用非放行冒充) */
|
|
27
|
+
class SkippedRow extends Error {
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 数据面合法文件集(装机/启用/boot 记账三源落盘——其余即残留;local 源直引
|
|
31
|
+
* 不拷贝故无 node_modules)。boot-failures.json = 绿 boot 也触写(装载成功对
|
|
32
|
+
* activated 逐行 clearBootFailure)——宿主记账方合法残留,dsh 白名单同律。
|
|
33
|
+
*/
|
|
34
|
+
const HOST_DATAFILE_WHITELIST = new Set(['enabled.yaml', 'plugins/ledger.json', 'boot-failures.json']);
|
|
35
|
+
/** 开门面样本授予(USER_GRANTABLE_CAPABILITIES 首枚——行校验值域内样本) */
|
|
36
|
+
const OPENS_SAMPLE = 'channels.ui-backend';
|
|
37
|
+
/**
|
|
38
|
+
* 生命周期证明矩阵主入口——一站式可召唤(作者测试一行起跑):
|
|
39
|
+
*
|
|
40
|
+
* ```ts
|
|
41
|
+
* const report = await proveLifecycleMatrix({ pluginDir: __dirname });
|
|
42
|
+
* expect(report.ok).toBe(true);
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* 收场自动回卷在飞代 + 清理自建临时目录(报告先行返回——dispose 失败不折损证明)。
|
|
46
|
+
*/
|
|
47
|
+
export async function proveLifecycleMatrix(options) {
|
|
48
|
+
const rows = [];
|
|
49
|
+
const expectCommands = options.expect?.commands ?? [];
|
|
50
|
+
let harness = null;
|
|
51
|
+
/** 单行跑手(红 = 该行 fail;SkippedRow = 环境不适用;异常讯息即 detail) */
|
|
52
|
+
const runRow = async (row, fn) => {
|
|
53
|
+
try {
|
|
54
|
+
await fn();
|
|
55
|
+
rows.push({ row, status: 'pass' });
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
if (err instanceof SkippedRow) {
|
|
59
|
+
rows.push({ row, status: 'skipped', detail: err.message });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
rows.push({ row, status: 'fail', detail: err instanceof Error ? err.message : String(err) });
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
try {
|
|
66
|
+
try {
|
|
67
|
+
harness = createPluginHarness(options);
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
// 构造即红(缺清单/坏清单)——install 行收面,矩阵不整体炸
|
|
71
|
+
rows.push({
|
|
72
|
+
row: 'install(装机 + pack 完整性)',
|
|
73
|
+
status: 'fail',
|
|
74
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
75
|
+
});
|
|
76
|
+
return { pluginId: '(unknown)', ok: false, rows };
|
|
77
|
+
}
|
|
78
|
+
const h = harness;
|
|
79
|
+
/* ---------------- install(必进) ---------------- */
|
|
80
|
+
await runRow('install(装机 + pack 完整性)', async () => {
|
|
81
|
+
const outcome = await h.install();
|
|
82
|
+
if (!outcome.ok)
|
|
83
|
+
throw new Error(`装机拒:${outcome.message}`);
|
|
84
|
+
if (outcome.id !== h.pluginId)
|
|
85
|
+
throw new Error(`装机账本 id(${outcome.id})≠ 清单 id(${h.pluginId})`);
|
|
86
|
+
// default-export 陷阱正向探针(纯声明包 main 带 default export 函数 = 永不执行的死代码作者陷阱)
|
|
87
|
+
const mainFile = resolvePackageMain(h.pluginDir, h.packageJson);
|
|
88
|
+
if (h.manifest.entryPlan.kind === 'declared-payload' && mainFile !== null) {
|
|
89
|
+
const namespace = await jitiProbe(mainFile);
|
|
90
|
+
if (namespace !== null && typeof namespace.default === 'function') {
|
|
91
|
+
throw new Error(`纯声明包 main(${mainFile})带 default export 函数——declared-payload 形包主入口不执行(要么声明 berryAgent.entry 走代码插件,要么移除 default export)`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (options.packCheck === false)
|
|
95
|
+
return;
|
|
96
|
+
let packed;
|
|
97
|
+
try {
|
|
98
|
+
packed = await npmPackFiles(h.pluginDir);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
// npm 缺席(ENOENT)= 环境位 skipped;其余 npm 报错照红(fail-loud 不吞)
|
|
102
|
+
if (err instanceof Error && err.code === 'ENOENT') {
|
|
103
|
+
throw new SkippedRow('npm pack 探测不可用(环境无 npm)——pack 完整性未证');
|
|
104
|
+
}
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
107
|
+
const referenced = packReferencedFiles(h.pluginDir, h.manifest);
|
|
108
|
+
const missing = referenced.filter((ref) => !packed.includes(ref));
|
|
109
|
+
if (missing.length > 0) {
|
|
110
|
+
throw new Error(`npm pack 产物缺插件引用文件(files 白名单漏收):${missing.join('、')}——发布物将不含装载必需文件`);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
/* ---------------- mount(必进) ---------------- */
|
|
114
|
+
await runRow('mount(真装载 activated + 注册账在场)', async () => {
|
|
115
|
+
const mounted = h.mountRow();
|
|
116
|
+
if (!mounted.ok)
|
|
117
|
+
throw new Error(`启用行拒:${mounted.message}`);
|
|
118
|
+
const boot = await h.boot();
|
|
119
|
+
const activated = boot.report.activated.find((a) => a.id === h.pluginId);
|
|
120
|
+
if (activated === undefined) {
|
|
121
|
+
const failed = boot.report.failed.find((f) => f.id === h.pluginId);
|
|
122
|
+
throw new Error(failed === undefined
|
|
123
|
+
? `装载计划面缺席(activated/failed 皆无 ${h.pluginId})`
|
|
124
|
+
: `装载失败(${failed.code}):${failed.message}`);
|
|
125
|
+
}
|
|
126
|
+
// 注册账在场(作者声明面):命令名净计数 ≥ 1
|
|
127
|
+
for (const name of expectCommands) {
|
|
128
|
+
const count = h.commands.counts().get(name) ?? 0;
|
|
129
|
+
if (count < 1)
|
|
130
|
+
throw new Error(`期望命令 /${name} 未注册(注册账缺席)`);
|
|
131
|
+
}
|
|
132
|
+
// 纯声明形:技能/agents 目录账与清单声明同长
|
|
133
|
+
if (h.manifest.entryPlan.kind === 'declared-payload') {
|
|
134
|
+
const declaredSkills = h.manifest.skills?.length ?? 0;
|
|
135
|
+
if (activated.skillDirs.length !== declaredSkills) {
|
|
136
|
+
throw new Error(`技能目录账(${activated.skillDirs.length})≠ 清单声明(${declaredSkills})`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
/* ---------------- 事件面(必进——审计不 mock 真词面) ---------------- */
|
|
141
|
+
await runRow('事件面(plugin/mounted 恰一笔)', async () => {
|
|
142
|
+
const mountedCount = h.audit.countOf('plugin/mounted', (data) => data['id'] === h.pluginId);
|
|
143
|
+
if (mountedCount !== 1) {
|
|
144
|
+
throw new Error(`plugin/mounted 笔数 = ${mountedCount}(期 1——mountRow 一笔、boot diff 不重放)`);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
/* ---------------- 幂等/负向(顺手进) ---------------- */
|
|
148
|
+
await runRow('幂等/负向(换代不双注 + mountRow 撞名拒)', async () => {
|
|
149
|
+
await h.boot(); // 换代(harness 内先卸旧代再起新代——/reload 同语义)
|
|
150
|
+
const mountedCount = h.audit.countOf('plugin/mounted', (data) => data['id'] === h.pluginId);
|
|
151
|
+
if (mountedCount !== 1)
|
|
152
|
+
throw new Error(`换代后 plugin/mounted 笔数 = ${mountedCount}(期 1——幂等 diff 不重放)`);
|
|
153
|
+
for (const name of expectCommands) {
|
|
154
|
+
const count = h.commands.counts().get(name) ?? 0;
|
|
155
|
+
if (count !== 1)
|
|
156
|
+
throw new Error(`换代后命令 /${name} 净计数 = ${count}(期 1——旧代出账后新代单注)`);
|
|
157
|
+
}
|
|
158
|
+
const dup = h.mountRow();
|
|
159
|
+
if (dup.ok)
|
|
160
|
+
throw new Error('mountRow 重复调用未被拒(撞名拒缺席——幂等执法失守)');
|
|
161
|
+
});
|
|
162
|
+
/* ---------------- toggle(顺手进——行翻转两断言) ---------------- */
|
|
163
|
+
await runRow('toggle(行翻转两断言)', async () => {
|
|
164
|
+
const off = h.toggleRow();
|
|
165
|
+
if (!off.ok)
|
|
166
|
+
throw new Error(`禁用翻转拒:${off.message}`);
|
|
167
|
+
const offBoot = await h.boot();
|
|
168
|
+
if (!offBoot.report.skipped.some((s) => s.id === h.pluginId)) {
|
|
169
|
+
throw new Error('禁用行不在 skipped 面(disabled 旗标未生效)');
|
|
170
|
+
}
|
|
171
|
+
// boot diff 五形:enabled→disabled 时尾态已同(toggleRow 笔在前)→ 一致零落
|
|
172
|
+
const offToggled = h.audit.countOf('plugin/toggled', (data) => data['id'] === h.pluginId && data['disabled'] === true);
|
|
173
|
+
if (offToggled !== 1)
|
|
174
|
+
throw new Error(`禁用后 plugin/toggled{true} 笔数 = ${offToggled}(期 1)`);
|
|
175
|
+
const on = h.toggleRow();
|
|
176
|
+
if (!on.ok)
|
|
177
|
+
throw new Error(`回启用翻转拒:${on.message}`);
|
|
178
|
+
const onBoot = await h.boot();
|
|
179
|
+
if (!onBoot.report.activated.some((a) => a.id === h.pluginId)) {
|
|
180
|
+
throw new Error('回启用行不在 activated 面(旗标撤除未生效)');
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
/* ---------------- 开门面(顺手进——幂等 diff) ---------------- */
|
|
184
|
+
await runRow('开门面(plugin/opens 幂等 diff)', async () => {
|
|
185
|
+
h.grantOpens([OPENS_SAMPLE]);
|
|
186
|
+
await h.boot();
|
|
187
|
+
const opensRows = h.audit.all().filter((r) => r.type === 'plugin/opens' && r.data['pluginId'] === h.pluginId);
|
|
188
|
+
if (opensRows.length !== 1)
|
|
189
|
+
throw new Error(`plugin/opens 笔数 = ${opensRows.length}(期 1)`);
|
|
190
|
+
const recorded = opensRows[0].data['opens'];
|
|
191
|
+
if (JSON.stringify(recorded) !== JSON.stringify([OPENS_SAMPLE])) {
|
|
192
|
+
throw new Error(`plugin/opens 载荷 ${JSON.stringify(recorded)} ≠ 授予面 [${OPENS_SAMPLE}]`);
|
|
193
|
+
}
|
|
194
|
+
await h.boot(); // 幂等:同面再 boot 不重放
|
|
195
|
+
const after = h.audit.countOf('plugin/opens', (data) => data['pluginId'] === h.pluginId);
|
|
196
|
+
if (after !== 1)
|
|
197
|
+
throw new Error(`幂等再 boot 后 plugin/opens 笔数 = ${after}(期 1——有变才落失守)`);
|
|
198
|
+
});
|
|
199
|
+
/* ---------------- unmount(必进——absent 与 present 同重) ---------------- */
|
|
200
|
+
await runRow('unmount(disposer 回卷 + 注册账缺席)', async () => {
|
|
201
|
+
const receipt = await h.unloadCurrent();
|
|
202
|
+
// 回执两查:有在飞代可回卷(null = 无代可卸——装载从未成)+ 回卷零失败
|
|
203
|
+
//(receipt.disposed 只列 apply 返回清理函数的插件——ctx.effect 形注册
|
|
204
|
+
// 走 fork dispose 腿不进 disposed 清单,故「已卸载」的真证明在下方账面)
|
|
205
|
+
if (receipt === null)
|
|
206
|
+
throw new Error('回卷回执 null(无在飞代可卸——装载从未成)');
|
|
207
|
+
if (receipt.failed.length > 0) {
|
|
208
|
+
throw new Error(`回卷失败面非空:${receipt.failed.map((f) => f.id).join('、')}`);
|
|
209
|
+
}
|
|
210
|
+
for (const name of expectCommands) {
|
|
211
|
+
const count = h.commands.counts().get(name) ?? 0;
|
|
212
|
+
if (count !== 0)
|
|
213
|
+
throw new Error(`回卷后命令 /${name} 净计数 = ${count}(期 0——absent 断言同重)`);
|
|
214
|
+
}
|
|
215
|
+
for (const [word, net] of h.listeners.netCounts()) {
|
|
216
|
+
if (net !== 0)
|
|
217
|
+
throw new Error(`回卷后事件词 ${word} 净监听数 = ${net}(期 0——零注册残留)`);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
/* ---------------- 残留检查(白名单豁免——宿主记账方是合法残留) ---------------- */
|
|
221
|
+
await runRow('残留检查(审计词 + 数据面双白名单)', async () => {
|
|
222
|
+
const strayWords = [...new Set(h.audit.all().map((r) => r.type))].filter((t) => !HOST_AUDIT_WHITELIST.has(t));
|
|
223
|
+
if (strayWords.length > 0)
|
|
224
|
+
throw new Error(`审计流出现白名单外词:${strayWords.join('、')}`);
|
|
225
|
+
const strayFiles = h.dataFiles().filter((f) => !HOST_DATAFILE_WHITELIST.has(f));
|
|
226
|
+
if (strayFiles.length > 0)
|
|
227
|
+
throw new Error(`数据目录出现白名单外文件:${strayFiles.join('、')}`);
|
|
228
|
+
});
|
|
229
|
+
return { pluginId: h.pluginId, ok: rows.every((r) => r.status !== 'fail'), rows };
|
|
230
|
+
}
|
|
231
|
+
finally {
|
|
232
|
+
// 收场恒跑(证明先行——dispose 异常不折损已得报告)
|
|
233
|
+
await harness?.dispose().catch(() => undefined);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* pack 引用集(装载必需文件的清单侧全集):entry-file 入口 + skills/agents
|
|
238
|
+
* 声明目录下全部文件(相对包根 posix 形——npm pack 产物路径同形比对)。
|
|
239
|
+
*/
|
|
240
|
+
function packReferencedFiles(pluginDir, manifest) {
|
|
241
|
+
const refs = [];
|
|
242
|
+
// entry-file 形入口在场(entryPlan 判据同源——entry 字段类型可选故判空防)
|
|
243
|
+
if (manifest.entryPlan.kind === 'entry-file' && manifest.entry !== undefined) {
|
|
244
|
+
refs.push(posix.normalize(manifest.entry));
|
|
245
|
+
}
|
|
246
|
+
for (const dir of [...(manifest.skills ?? []), ...(manifest.agents ?? [])]) {
|
|
247
|
+
const abs = join(pluginDir, dir);
|
|
248
|
+
if (!statSync(abs).isDirectory())
|
|
249
|
+
throw new Error(`声明载荷目录缺席:${dir}`);
|
|
250
|
+
// 基 = 包根(npm pack 产物路径相对包根——目录基会推成目录内相对形比对永错)
|
|
251
|
+
collectFiles(pluginDir, abs, refs);
|
|
252
|
+
}
|
|
253
|
+
return refs;
|
|
254
|
+
}
|
|
255
|
+
/** 目录递归收文件(rel 基 = 包根——与 npm pack 产物路径同形) */
|
|
256
|
+
function collectFiles(base, dir, out) {
|
|
257
|
+
for (const name of readdirSync(dir)) {
|
|
258
|
+
const abs = join(dir, name);
|
|
259
|
+
if (statSync(abs).isDirectory())
|
|
260
|
+
collectFiles(base, abs, out);
|
|
261
|
+
else
|
|
262
|
+
out.push(posix.normalize(abs.slice(base.length + 1)));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/** 包主入口解析(main/module 字段 → 存在的文件;缺席 = null) */
|
|
266
|
+
function resolvePackageMain(pluginDir, pkg) {
|
|
267
|
+
const candidate = typeof pkg['main'] === 'string' ? pkg['main'] : typeof pkg['module'] === 'string' ? pkg['module'] : null;
|
|
268
|
+
if (candidate === null)
|
|
269
|
+
return null;
|
|
270
|
+
const abs = join(pluginDir, candidate);
|
|
271
|
+
try {
|
|
272
|
+
return statSync(abs).isFile() ? abs : null;
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/** main 模块求值探针(求值失败 = null 非红——失败证据缺席不冒充在场) */
|
|
279
|
+
async function jitiProbe(mainPath) {
|
|
280
|
+
try {
|
|
281
|
+
const jiti = createJiti(mainPath);
|
|
282
|
+
return (await jiti.import(mainPath));
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* 可贴 README 的 markdown 回执(「产出可贴 README 的证明」——03 §9.5 定位段)。
|
|
290
|
+
*/
|
|
291
|
+
export function formatMatrixReceipt(report) {
|
|
292
|
+
const lines = [
|
|
293
|
+
`## berry-agent 插件生命周期证明 —— ${report.pluginId}`,
|
|
294
|
+
'',
|
|
295
|
+
'| 检查行 | 结果 |',
|
|
296
|
+
'| --- | --- |',
|
|
297
|
+
...report.rows.map((r) => `| ${r.row} | ${r.status === 'pass' ? '✅' : r.status === 'skipped' ? '⏭️' : '❌'}${r.detail === undefined ? '' : `(${r.detail})`} |`),
|
|
298
|
+
'',
|
|
299
|
+
` berry-agent testkit · 共 ${report.rows.length} 行`,
|
|
300
|
+
];
|
|
301
|
+
return lines.join('\n');
|
|
302
|
+
}
|