@pi-harness/pi-harness 0.1.13 → 0.1.14
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/README.md +678 -2
- package/apps/web/dist/assets/index-BVI_IS-R.css +2 -0
- package/apps/web/dist/assets/index-C98SJjJd.js +22 -0
- package/apps/web/dist/index.html +2 -2
- package/apps/web/package.json +6 -6
- package/node_modules/@pi-harness/api-gateway/dist/index.d.ts.map +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/index.js +97 -9
- package/node_modules/@pi-harness/api-gateway/dist/index.js.map +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/agent-teams.json +6 -6
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/anchored-standard.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/annotation.json +1 -2
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/archify.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/at-file.json +11 -5
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/auto-mode.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/better-sidebar.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/browser-fetch.json +12 -5
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/browser-session.json +14 -8
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/canvas-draw.json +3 -4
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/change-verifier.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cleaner.json +11 -6
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cli-notifier.json +5 -5
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/code2skill.json +5 -5
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/colleague-skill.json +1 -2
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/context-doctor.json +12 -7
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/context.json +10 -5
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cordis-group.json +2 -2
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cordis-logger-console.json +4 -4
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cordis-timer.json +3 -3
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cost-meter.json +9 -6
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/dependency-checker.json +13 -10
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/docker-sandbox.json +10 -5
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/fail-logger.json +11 -6
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/genui.json +7 -5
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/git-time-capsule.json +13 -8
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/graph-memory.json +13 -7
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/history-compressor.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/hol-guard.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/i18n-pair.json +9 -6
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/image-compressor.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/llm-verifier.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/mcp-client.json +12 -7
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/mcp-panel.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/memory.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/mirage-bridge.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/mock-server.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/modlens.json +4 -4
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/module-search.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/obsidian-sync.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/openpets.json +9 -5
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plan-execute.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-check.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-dev.json +8 -6
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-finder.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-radar.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-stars.json +15 -7
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/prompt-guard.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/prompt-library.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/readme-gen.json +12 -8
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/recall-unread.json +11 -6
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/reverse-skill.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/reviewer-bot.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/runtime-doctor.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/secure-audit.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-bookmarks.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-bridge.json +13 -8
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-compare.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-export.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-insights.json +12 -6
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-search.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/skill-catalog.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/skill-guard.json +9 -5
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/sql-lens.json +13 -7
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/synapse.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/tab-manager.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/taskboard.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/telemetry-blocker.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/test-harness.json +14 -7
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/theme-studio.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/token-guard.json +12 -6
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/turn-rewind.json +11 -6
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/undo-savepoint.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/vision-toolkit.json +8 -5
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/web-research.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/workspace-navigator.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/workspace-search.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/yaml-validator.json +8 -5
- package/node_modules/@pi-harness/api-gateway/package.json +1 -1
- package/node_modules/@pi-harness/cli/dist/node-stdio.d.ts.map +1 -1
- package/node_modules/@pi-harness/cli/dist/node-stdio.js +83 -20
- package/node_modules/@pi-harness/cli/dist/node-stdio.js.map +1 -1
- package/node_modules/@pi-harness/cli/package.json +1 -1
- package/node_modules/@pi-harness/core/dist/atomic-write.d.ts +8 -0
- package/node_modules/@pi-harness/core/dist/atomic-write.d.ts.map +1 -0
- package/node_modules/@pi-harness/core/dist/atomic-write.js +40 -0
- package/node_modules/@pi-harness/core/dist/atomic-write.js.map +1 -0
- package/node_modules/@pi-harness/core/dist/boot.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/boot.js +132 -10
- package/node_modules/@pi-harness/core/dist/boot.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/bounded-file.d.ts +7 -0
- package/node_modules/@pi-harness/core/dist/bounded-file.d.ts.map +1 -0
- package/node_modules/@pi-harness/core/dist/bounded-file.js +50 -0
- package/node_modules/@pi-harness/core/dist/bounded-file.js.map +1 -0
- package/node_modules/@pi-harness/core/dist/config.d.ts +2 -0
- package/node_modules/@pi-harness/core/dist/config.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/config.js +21 -0
- package/node_modules/@pi-harness/core/dist/config.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/cordis-timer.d.ts +15 -0
- package/node_modules/@pi-harness/core/dist/cordis-timer.d.ts.map +1 -0
- package/node_modules/@pi-harness/core/dist/cordis-timer.js +99 -0
- package/node_modules/@pi-harness/core/dist/cordis-timer.js.map +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/agent-teams.d.ts +5 -1
- package/node_modules/@pi-harness/core/dist/plugins/agent-teams.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/agent-teams.js +986 -105
- package/node_modules/@pi-harness/core/dist/plugins/agent-teams.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/anchored-standard.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/anchored-standard.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/anchored-standard.js +39 -11
- package/node_modules/@pi-harness/core/dist/plugins/anchored-standard.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/annotation.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/annotation.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/annotation.js +35 -17
- package/node_modules/@pi-harness/core/dist/plugins/annotation.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/archify.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/archify.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/archify.js +69 -22
- package/node_modules/@pi-harness/core/dist/plugins/archify.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/at-file.d.ts +5 -1
- package/node_modules/@pi-harness/core/dist/plugins/at-file.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/at-file.js +120 -29
- package/node_modules/@pi-harness/core/dist/plugins/at-file.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/auto-mode.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/auto-mode.js +89 -20
- package/node_modules/@pi-harness/core/dist/plugins/auto-mode.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/better-sidebar.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/better-sidebar.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/better-sidebar.js +14 -4
- package/node_modules/@pi-harness/core/dist/plugins/better-sidebar.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/browser-fetch.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/browser-fetch.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/browser-fetch.js +288 -96
- package/node_modules/@pi-harness/core/dist/plugins/browser-fetch.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/browser-session.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/browser-session.js +445 -106
- package/node_modules/@pi-harness/core/dist/plugins/browser-session.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/canvas-draw.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/canvas-draw.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/canvas-draw.js +71 -19
- package/node_modules/@pi-harness/core/dist/plugins/canvas-draw.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/change-verifier.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/change-verifier.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/change-verifier.js +30 -9
- package/node_modules/@pi-harness/core/dist/plugins/change-verifier.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/cleaner.d.ts +5 -1
- package/node_modules/@pi-harness/core/dist/plugins/cleaner.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/cleaner.js +337 -40
- package/node_modules/@pi-harness/core/dist/plugins/cleaner.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/cli-notifier.d.ts +2 -1
- package/node_modules/@pi-harness/core/dist/plugins/cli-notifier.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/cli-notifier.js +99 -42
- package/node_modules/@pi-harness/core/dist/plugins/cli-notifier.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/code2skill.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/code2skill.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/code2skill.js +178 -38
- package/node_modules/@pi-harness/core/dist/plugins/code2skill.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/colleague-skill.d.ts +2 -1
- package/node_modules/@pi-harness/core/dist/plugins/colleague-skill.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/colleague-skill.js +79 -49
- package/node_modules/@pi-harness/core/dist/plugins/colleague-skill.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/context-doctor.d.ts +6 -1
- package/node_modules/@pi-harness/core/dist/plugins/context-doctor.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/context-doctor.js +403 -39
- package/node_modules/@pi-harness/core/dist/plugins/context-doctor.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/context.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/context.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/context.js +169 -42
- package/node_modules/@pi-harness/core/dist/plugins/context.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/cost-meter.d.ts +4 -0
- package/node_modules/@pi-harness/core/dist/plugins/cost-meter.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/cost-meter.js +319 -93
- package/node_modules/@pi-harness/core/dist/plugins/cost-meter.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/dependency-checker.d.ts +11 -2
- package/node_modules/@pi-harness/core/dist/plugins/dependency-checker.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/dependency-checker.js +463 -114
- package/node_modules/@pi-harness/core/dist/plugins/dependency-checker.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/docker-sandbox.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/docker-sandbox.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/docker-sandbox.js +322 -80
- package/node_modules/@pi-harness/core/dist/plugins/docker-sandbox.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/fail-logger.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/fail-logger.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/fail-logger.js +187 -20
- package/node_modules/@pi-harness/core/dist/plugins/fail-logger.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/genui.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/genui.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/genui.js +103 -38
- package/node_modules/@pi-harness/core/dist/plugins/genui.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/git-time-capsule.d.ts +8 -2
- package/node_modules/@pi-harness/core/dist/plugins/git-time-capsule.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/git-time-capsule.js +400 -54
- package/node_modules/@pi-harness/core/dist/plugins/git-time-capsule.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/graph-memory.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/graph-memory.js +329 -42
- package/node_modules/@pi-harness/core/dist/plugins/graph-memory.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/history-compressor.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/history-compressor.js +31 -12
- package/node_modules/@pi-harness/core/dist/plugins/history-compressor.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/hol-guard.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/hol-guard.js +33 -13
- package/node_modules/@pi-harness/core/dist/plugins/hol-guard.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/i18n-pair.d.ts +5 -1
- package/node_modules/@pi-harness/core/dist/plugins/i18n-pair.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/i18n-pair.js +308 -52
- package/node_modules/@pi-harness/core/dist/plugins/i18n-pair.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/image-compressor.d.ts +7 -0
- package/node_modules/@pi-harness/core/dist/plugins/image-compressor.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/image-compressor.js +98 -25
- package/node_modules/@pi-harness/core/dist/plugins/image-compressor.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/llm-verifier.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/llm-verifier.js +21 -11
- package/node_modules/@pi-harness/core/dist/plugins/llm-verifier.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/mcp-client.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/mcp-client.js +986 -232
- package/node_modules/@pi-harness/core/dist/plugins/mcp-client.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/mcp-panel.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/mcp-panel.js +30 -16
- package/node_modules/@pi-harness/core/dist/plugins/mcp-panel.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/memory.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/memory.js +292 -59
- package/node_modules/@pi-harness/core/dist/plugins/memory.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/mirage-bridge.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/mirage-bridge.js +20 -10
- package/node_modules/@pi-harness/core/dist/plugins/mirage-bridge.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/mock-server.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/mock-server.js +85 -54
- package/node_modules/@pi-harness/core/dist/plugins/mock-server.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/model.d.ts +3 -0
- package/node_modules/@pi-harness/core/dist/plugins/model.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/model.js +3 -0
- package/node_modules/@pi-harness/core/dist/plugins/model.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/models.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/models.js +10 -2
- package/node_modules/@pi-harness/core/dist/plugins/models.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/modlens.d.ts +8 -1
- package/node_modules/@pi-harness/core/dist/plugins/modlens.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/modlens.js +438 -31
- package/node_modules/@pi-harness/core/dist/plugins/modlens.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/module-search.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/module-search.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/module-search.js +35 -16
- package/node_modules/@pi-harness/core/dist/plugins/module-search.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/obsidian-sync.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/obsidian-sync.js +20 -11
- package/node_modules/@pi-harness/core/dist/plugins/obsidian-sync.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/openpets.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/openpets.js +203 -44
- package/node_modules/@pi-harness/core/dist/plugins/openpets.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/plan-execute.d.ts +4 -1
- package/node_modules/@pi-harness/core/dist/plugins/plan-execute.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/plan-execute.js +42 -21
- package/node_modules/@pi-harness/core/dist/plugins/plan-execute.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/plugin-check.d.ts +12 -3
- package/node_modules/@pi-harness/core/dist/plugins/plugin-check.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/plugin-check.js +83 -12
- package/node_modules/@pi-harness/core/dist/plugins/plugin-check.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/plugin-dev.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/plugin-dev.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/plugin-dev.js +156 -23
- package/node_modules/@pi-harness/core/dist/plugins/plugin-dev.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/plugin-finder.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/plugin-finder.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/plugin-finder.js +131 -13
- package/node_modules/@pi-harness/core/dist/plugins/plugin-finder.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/plugin-radar.d.ts +4 -0
- package/node_modules/@pi-harness/core/dist/plugins/plugin-radar.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/plugin-radar.js +137 -36
- package/node_modules/@pi-harness/core/dist/plugins/plugin-radar.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/plugin-stars.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/plugin-stars.js +282 -81
- package/node_modules/@pi-harness/core/dist/plugins/plugin-stars.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/prompt-guard.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/prompt-guard.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/prompt-guard.js +60 -9
- package/node_modules/@pi-harness/core/dist/plugins/prompt-guard.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/prompt-library.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/prompt-library.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/prompt-library.js +4 -1
- package/node_modules/@pi-harness/core/dist/plugins/prompt-library.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/readme-gen.d.ts +6 -2
- package/node_modules/@pi-harness/core/dist/plugins/readme-gen.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/readme-gen.js +514 -73
- package/node_modules/@pi-harness/core/dist/plugins/readme-gen.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/recall-unread.d.ts +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/recall-unread.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/recall-unread.js +411 -68
- package/node_modules/@pi-harness/core/dist/plugins/recall-unread.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/resources.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/resources.js +66 -3
- package/node_modules/@pi-harness/core/dist/plugins/resources.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/reverse-skill.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/reverse-skill.js +2 -1
- package/node_modules/@pi-harness/core/dist/plugins/reverse-skill.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/reviewer-bot.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/reviewer-bot.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/reviewer-bot.js +35 -16
- package/node_modules/@pi-harness/core/dist/plugins/reviewer-bot.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/runtime-doctor.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/runtime-doctor.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/runtime-doctor.js +4 -1
- package/node_modules/@pi-harness/core/dist/plugins/runtime-doctor.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/runtime.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/runtime.js +104 -70
- package/node_modules/@pi-harness/core/dist/plugins/runtime.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/secure-audit.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/secure-audit.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/secure-audit.js +30 -12
- package/node_modules/@pi-harness/core/dist/plugins/secure-audit.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/session-bookmarks.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/session-bookmarks.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/session-bookmarks.js +4 -1
- package/node_modules/@pi-harness/core/dist/plugins/session-bookmarks.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/session-bridge.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/session-bridge.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/session-bridge.js +401 -88
- package/node_modules/@pi-harness/core/dist/plugins/session-bridge.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/session-compare.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/session-compare.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/session-compare.js +4 -1
- package/node_modules/@pi-harness/core/dist/plugins/session-compare.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/session-export.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/session-export.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/session-export.js +6 -6
- package/node_modules/@pi-harness/core/dist/plugins/session-export.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/session-insights.d.ts +37 -1
- package/node_modules/@pi-harness/core/dist/plugins/session-insights.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/session-insights.js +406 -21
- package/node_modules/@pi-harness/core/dist/plugins/session-insights.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/session-search.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/session-search.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/session-search.js +4 -1
- package/node_modules/@pi-harness/core/dist/plugins/session-search.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/session.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/session.js +8 -1
- package/node_modules/@pi-harness/core/dist/plugins/session.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/skill-catalog.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/skill-catalog.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/skill-catalog.js +6 -5
- package/node_modules/@pi-harness/core/dist/plugins/skill-catalog.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/skill-guard.d.ts +2 -1
- package/node_modules/@pi-harness/core/dist/plugins/skill-guard.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/skill-guard.js +241 -61
- package/node_modules/@pi-harness/core/dist/plugins/skill-guard.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/sql-lens-worker.d.ts +2 -0
- package/node_modules/@pi-harness/core/dist/plugins/sql-lens-worker.d.ts.map +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/sql-lens-worker.js +206 -0
- package/node_modules/@pi-harness/core/dist/plugins/sql-lens-worker.js.map +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/sql-lens.d.ts +7 -1
- package/node_modules/@pi-harness/core/dist/plugins/sql-lens.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/sql-lens.js +301 -68
- package/node_modules/@pi-harness/core/dist/plugins/sql-lens.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/stdio.d.ts +3 -0
- package/node_modules/@pi-harness/core/dist/plugins/stdio.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/stdio.js +62 -4
- package/node_modules/@pi-harness/core/dist/plugins/stdio.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/synapse.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/synapse.js +17 -9
- package/node_modules/@pi-harness/core/dist/plugins/synapse.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/tab-manager.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/tab-manager.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/tab-manager.js +167 -62
- package/node_modules/@pi-harness/core/dist/plugins/tab-manager.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/taskboard.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/taskboard.js +50 -22
- package/node_modules/@pi-harness/core/dist/plugins/taskboard.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/telemetry-blocker.d.ts +4 -1
- package/node_modules/@pi-harness/core/dist/plugins/telemetry-blocker.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/telemetry-blocker.js +30 -11
- package/node_modules/@pi-harness/core/dist/plugins/telemetry-blocker.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/test-harness.d.ts +7 -1
- package/node_modules/@pi-harness/core/dist/plugins/test-harness.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/test-harness.js +255 -43
- package/node_modules/@pi-harness/core/dist/plugins/test-harness.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/theme-studio.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/theme-studio.js +4 -2
- package/node_modules/@pi-harness/core/dist/plugins/theme-studio.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/token-guard.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/token-guard.js +164 -32
- package/node_modules/@pi-harness/core/dist/plugins/token-guard.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/tools.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/tools.js +4 -1
- package/node_modules/@pi-harness/core/dist/plugins/tools.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/turn-rewind.d.ts +17 -0
- package/node_modules/@pi-harness/core/dist/plugins/turn-rewind.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/turn-rewind.js +340 -36
- package/node_modules/@pi-harness/core/dist/plugins/turn-rewind.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/undo-savepoint.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/undo-savepoint.js +157 -51
- package/node_modules/@pi-harness/core/dist/plugins/undo-savepoint.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/vision-toolkit.d.ts +22 -3
- package/node_modules/@pi-harness/core/dist/plugins/vision-toolkit.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/vision-toolkit.js +389 -66
- package/node_modules/@pi-harness/core/dist/plugins/vision-toolkit.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/web-research.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/web-research.js +13 -4
- package/node_modules/@pi-harness/core/dist/plugins/web-research.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/workspace-navigator.d.ts +8 -2
- package/node_modules/@pi-harness/core/dist/plugins/workspace-navigator.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/workspace-navigator.js +28 -10
- package/node_modules/@pi-harness/core/dist/plugins/workspace-navigator.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/workspace-search.d.ts +7 -0
- package/node_modules/@pi-harness/core/dist/plugins/workspace-search.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/workspace-search.js +67 -34
- package/node_modules/@pi-harness/core/dist/plugins/workspace-search.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/yaml-validator.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/plugins/yaml-validator.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/plugins/yaml-validator.js +156 -47
- package/node_modules/@pi-harness/core/dist/plugins/yaml-validator.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/runtime.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/runtime.js +8 -3
- package/node_modules/@pi-harness/core/dist/runtime.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/services.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/services.js +22 -2
- package/node_modules/@pi-harness/core/dist/services.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/stdio.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/stdio.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/stdio.js +241 -25
- package/node_modules/@pi-harness/core/dist/stdio.js.map +1 -1
- package/node_modules/@pi-harness/core/dist/workspace-path.d.ts +1 -0
- package/node_modules/@pi-harness/core/dist/workspace-path.d.ts.map +1 -1
- package/node_modules/@pi-harness/core/dist/workspace-path.js +9 -0
- package/node_modules/@pi-harness/core/dist/workspace-path.js.map +1 -1
- package/node_modules/@pi-harness/core/package.json +1 -1
- package/node_modules/@pi-harness/core/profiles/default/cordis.yml +4 -2
- package/node_modules/@pi-harness/host-webserver/package.json +1 -1
- package/node_modules/@pi-harness/web-app/package.json +1 -1
- package/package.json +12 -7
- package/packages/api-gateway/dist/index.d.ts.map +1 -1
- package/packages/api-gateway/dist/index.js +97 -9
- package/packages/api-gateway/dist/index.js.map +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/agent-teams.json +6 -6
- package/packages/api-gateway/dist/marketplace-entries/official/anchored-standard.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/annotation.json +1 -2
- package/packages/api-gateway/dist/marketplace-entries/official/archify.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/at-file.json +11 -5
- package/packages/api-gateway/dist/marketplace-entries/official/auto-mode.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/better-sidebar.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/browser-fetch.json +12 -5
- package/packages/api-gateway/dist/marketplace-entries/official/browser-session.json +14 -8
- package/packages/api-gateway/dist/marketplace-entries/official/canvas-draw.json +3 -4
- package/packages/api-gateway/dist/marketplace-entries/official/change-verifier.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/cleaner.json +11 -6
- package/packages/api-gateway/dist/marketplace-entries/official/cli-notifier.json +5 -5
- package/packages/api-gateway/dist/marketplace-entries/official/code2skill.json +5 -5
- package/packages/api-gateway/dist/marketplace-entries/official/colleague-skill.json +1 -2
- package/packages/api-gateway/dist/marketplace-entries/official/context-doctor.json +12 -7
- package/packages/api-gateway/dist/marketplace-entries/official/context.json +10 -5
- package/packages/api-gateway/dist/marketplace-entries/official/cordis-group.json +2 -2
- package/packages/api-gateway/dist/marketplace-entries/official/cordis-logger-console.json +4 -4
- package/packages/api-gateway/dist/marketplace-entries/official/cordis-timer.json +3 -3
- package/packages/api-gateway/dist/marketplace-entries/official/cost-meter.json +9 -6
- package/packages/api-gateway/dist/marketplace-entries/official/dependency-checker.json +13 -10
- package/packages/api-gateway/dist/marketplace-entries/official/docker-sandbox.json +10 -5
- package/packages/api-gateway/dist/marketplace-entries/official/fail-logger.json +11 -6
- package/packages/api-gateway/dist/marketplace-entries/official/genui.json +7 -5
- package/packages/api-gateway/dist/marketplace-entries/official/git-time-capsule.json +13 -8
- package/packages/api-gateway/dist/marketplace-entries/official/graph-memory.json +13 -7
- package/packages/api-gateway/dist/marketplace-entries/official/history-compressor.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/hol-guard.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/i18n-pair.json +9 -6
- package/packages/api-gateway/dist/marketplace-entries/official/image-compressor.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/llm-verifier.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/mcp-client.json +12 -7
- package/packages/api-gateway/dist/marketplace-entries/official/mcp-panel.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/memory.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/mirage-bridge.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/mock-server.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/modlens.json +4 -4
- package/packages/api-gateway/dist/marketplace-entries/official/module-search.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/obsidian-sync.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/openpets.json +9 -5
- package/packages/api-gateway/dist/marketplace-entries/official/plan-execute.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/plugin-check.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/plugin-dev.json +8 -6
- package/packages/api-gateway/dist/marketplace-entries/official/plugin-finder.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/plugin-radar.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/plugin-stars.json +15 -7
- package/packages/api-gateway/dist/marketplace-entries/official/prompt-guard.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/prompt-library.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/readme-gen.json +12 -8
- package/packages/api-gateway/dist/marketplace-entries/official/recall-unread.json +11 -6
- package/packages/api-gateway/dist/marketplace-entries/official/reverse-skill.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/reviewer-bot.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/runtime-doctor.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/secure-audit.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/session-bookmarks.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/session-bridge.json +13 -8
- package/packages/api-gateway/dist/marketplace-entries/official/session-compare.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/session-export.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/session-insights.json +12 -6
- package/packages/api-gateway/dist/marketplace-entries/official/session-search.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/skill-catalog.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/skill-guard.json +9 -5
- package/packages/api-gateway/dist/marketplace-entries/official/sql-lens.json +13 -7
- package/packages/api-gateway/dist/marketplace-entries/official/synapse.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/tab-manager.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/taskboard.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/telemetry-blocker.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/test-harness.json +14 -7
- package/packages/api-gateway/dist/marketplace-entries/official/theme-studio.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/token-guard.json +12 -6
- package/packages/api-gateway/dist/marketplace-entries/official/turn-rewind.json +11 -6
- package/packages/api-gateway/dist/marketplace-entries/official/undo-savepoint.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/vision-toolkit.json +8 -5
- package/packages/api-gateway/dist/marketplace-entries/official/web-research.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/workspace-navigator.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/workspace-search.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/yaml-validator.json +8 -5
- package/packages/api-gateway/package.json +1 -1
- package/packages/api-gateway/src/index.ts +90 -9
- package/packages/api-gateway/src/marketplace-entries/official/agent-teams.json +6 -6
- package/packages/api-gateway/src/marketplace-entries/official/anchored-standard.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/annotation.json +1 -2
- package/packages/api-gateway/src/marketplace-entries/official/archify.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/at-file.json +11 -5
- package/packages/api-gateway/src/marketplace-entries/official/auto-mode.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/better-sidebar.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/browser-fetch.json +12 -5
- package/packages/api-gateway/src/marketplace-entries/official/browser-session.json +14 -8
- package/packages/api-gateway/src/marketplace-entries/official/canvas-draw.json +3 -4
- package/packages/api-gateway/src/marketplace-entries/official/change-verifier.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/cleaner.json +11 -6
- package/packages/api-gateway/src/marketplace-entries/official/cli-notifier.json +5 -5
- package/packages/api-gateway/src/marketplace-entries/official/code2skill.json +5 -5
- package/packages/api-gateway/src/marketplace-entries/official/colleague-skill.json +1 -2
- package/packages/api-gateway/src/marketplace-entries/official/context-doctor.json +12 -7
- package/packages/api-gateway/src/marketplace-entries/official/context.json +10 -5
- package/packages/api-gateway/src/marketplace-entries/official/cordis-group.json +2 -2
- package/packages/api-gateway/src/marketplace-entries/official/cordis-logger-console.json +4 -4
- package/packages/api-gateway/src/marketplace-entries/official/cordis-timer.json +3 -3
- package/packages/api-gateway/src/marketplace-entries/official/cost-meter.json +9 -6
- package/packages/api-gateway/src/marketplace-entries/official/dependency-checker.json +13 -10
- package/packages/api-gateway/src/marketplace-entries/official/docker-sandbox.json +10 -5
- package/packages/api-gateway/src/marketplace-entries/official/fail-logger.json +11 -6
- package/packages/api-gateway/src/marketplace-entries/official/genui.json +7 -5
- package/packages/api-gateway/src/marketplace-entries/official/git-time-capsule.json +13 -8
- package/packages/api-gateway/src/marketplace-entries/official/graph-memory.json +13 -7
- package/packages/api-gateway/src/marketplace-entries/official/history-compressor.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/hol-guard.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/i18n-pair.json +9 -6
- package/packages/api-gateway/src/marketplace-entries/official/image-compressor.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/llm-verifier.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/mcp-client.json +12 -7
- package/packages/api-gateway/src/marketplace-entries/official/mcp-panel.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/memory.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/mirage-bridge.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/mock-server.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/modlens.json +4 -4
- package/packages/api-gateway/src/marketplace-entries/official/module-search.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/obsidian-sync.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/openpets.json +9 -5
- package/packages/api-gateway/src/marketplace-entries/official/plan-execute.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/plugin-check.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/plugin-dev.json +8 -6
- package/packages/api-gateway/src/marketplace-entries/official/plugin-finder.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/plugin-radar.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/plugin-stars.json +15 -7
- package/packages/api-gateway/src/marketplace-entries/official/prompt-guard.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/prompt-library.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/readme-gen.json +12 -8
- package/packages/api-gateway/src/marketplace-entries/official/recall-unread.json +11 -6
- package/packages/api-gateway/src/marketplace-entries/official/reverse-skill.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/reviewer-bot.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/runtime-doctor.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/secure-audit.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/session-bookmarks.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/session-bridge.json +13 -8
- package/packages/api-gateway/src/marketplace-entries/official/session-compare.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/session-export.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/session-insights.json +12 -6
- package/packages/api-gateway/src/marketplace-entries/official/session-search.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/skill-catalog.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/skill-guard.json +9 -5
- package/packages/api-gateway/src/marketplace-entries/official/sql-lens.json +13 -7
- package/packages/api-gateway/src/marketplace-entries/official/synapse.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/tab-manager.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/taskboard.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/telemetry-blocker.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/test-harness.json +14 -7
- package/packages/api-gateway/src/marketplace-entries/official/theme-studio.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/token-guard.json +12 -6
- package/packages/api-gateway/src/marketplace-entries/official/turn-rewind.json +11 -6
- package/packages/api-gateway/src/marketplace-entries/official/undo-savepoint.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/vision-toolkit.json +8 -5
- package/packages/api-gateway/src/marketplace-entries/official/web-research.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/workspace-navigator.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/workspace-search.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/yaml-validator.json +8 -5
- package/packages/api-gateway/test/index.test.ts +1594 -2
- package/packages/api-gateway/test/marketplace.test.ts +389 -2
- package/packages/api-gateway/test/release-package.test.ts +1 -0
- package/packages/bundle-web-app/package.json +1 -1
- package/packages/cli/dist/node-stdio.d.ts.map +1 -1
- package/packages/cli/dist/node-stdio.js +83 -20
- package/packages/cli/dist/node-stdio.js.map +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/cli/src/node-stdio.ts +77 -20
- package/packages/cli/test/node-stdio.test.ts +118 -2
- package/packages/client-web/dist/agent-teams-view.d.ts +70 -0
- package/packages/client-web/dist/agent-teams-view.d.ts.map +1 -0
- package/packages/client-web/dist/agent-teams-view.js +159 -0
- package/packages/client-web/dist/agent-teams-view.js.map +1 -0
- package/packages/client-web/dist/at-file-view.d.ts +10 -0
- package/packages/client-web/dist/at-file-view.d.ts.map +1 -0
- package/packages/client-web/dist/at-file-view.js +65 -0
- package/packages/client-web/dist/at-file-view.js.map +1 -0
- package/packages/client-web/dist/browser-fetch-view.d.ts +22 -0
- package/packages/client-web/dist/browser-fetch-view.d.ts.map +1 -0
- package/packages/client-web/dist/browser-fetch-view.js +101 -0
- package/packages/client-web/dist/browser-fetch-view.js.map +1 -0
- package/packages/client-web/dist/browser-session-view.d.ts +32 -0
- package/packages/client-web/dist/browser-session-view.d.ts.map +1 -1
- package/packages/client-web/dist/browser-session-view.js +206 -3
- package/packages/client-web/dist/browser-session-view.js.map +1 -1
- package/packages/client-web/dist/cleaner-view.d.ts +29 -0
- package/packages/client-web/dist/cleaner-view.d.ts.map +1 -0
- package/packages/client-web/dist/cleaner-view.js +191 -0
- package/packages/client-web/dist/cleaner-view.js.map +1 -0
- package/packages/client-web/dist/context-doctor-view.d.ts +35 -0
- package/packages/client-web/dist/context-doctor-view.d.ts.map +1 -0
- package/packages/client-web/dist/context-doctor-view.js +129 -0
- package/packages/client-web/dist/context-doctor-view.js.map +1 -0
- package/packages/client-web/dist/context-insights-view.d.ts +31 -0
- package/packages/client-web/dist/context-insights-view.d.ts.map +1 -0
- package/packages/client-web/dist/context-insights-view.js +90 -0
- package/packages/client-web/dist/context-insights-view.js.map +1 -0
- package/packages/client-web/dist/cost-meter-view.d.ts +16 -0
- package/packages/client-web/dist/cost-meter-view.d.ts.map +1 -0
- package/packages/client-web/dist/cost-meter-view.js +43 -0
- package/packages/client-web/dist/cost-meter-view.js.map +1 -0
- package/packages/client-web/dist/dependency-checker-view.d.ts +24 -0
- package/packages/client-web/dist/dependency-checker-view.d.ts.map +1 -0
- package/packages/client-web/dist/dependency-checker-view.js +195 -0
- package/packages/client-web/dist/dependency-checker-view.js.map +1 -0
- package/packages/client-web/dist/docker-sandbox-view.d.ts +28 -0
- package/packages/client-web/dist/docker-sandbox-view.d.ts.map +1 -0
- package/packages/client-web/dist/docker-sandbox-view.js +152 -0
- package/packages/client-web/dist/docker-sandbox-view.js.map +1 -0
- package/packages/client-web/dist/fail-logger-view.d.ts +16 -0
- package/packages/client-web/dist/fail-logger-view.d.ts.map +1 -0
- package/packages/client-web/dist/fail-logger-view.js +97 -0
- package/packages/client-web/dist/fail-logger-view.js.map +1 -0
- package/packages/client-web/dist/genui-view.d.ts +28 -0
- package/packages/client-web/dist/genui-view.d.ts.map +1 -0
- package/packages/client-web/dist/genui-view.js +62 -0
- package/packages/client-web/dist/genui-view.js.map +1 -0
- package/packages/client-web/dist/git-time-capsule-view.d.ts +31 -0
- package/packages/client-web/dist/git-time-capsule-view.d.ts.map +1 -0
- package/packages/client-web/dist/git-time-capsule-view.js +203 -0
- package/packages/client-web/dist/git-time-capsule-view.js.map +1 -0
- package/packages/client-web/dist/graph-memory-view.d.ts +39 -0
- package/packages/client-web/dist/graph-memory-view.d.ts.map +1 -0
- package/packages/client-web/dist/graph-memory-view.js +82 -0
- package/packages/client-web/dist/graph-memory-view.js.map +1 -0
- package/packages/client-web/dist/i18n-pair-view.d.ts +32 -0
- package/packages/client-web/dist/i18n-pair-view.d.ts.map +1 -0
- package/packages/client-web/dist/i18n-pair-view.js +182 -0
- package/packages/client-web/dist/i18n-pair-view.js.map +1 -0
- package/packages/client-web/dist/mcp-client-view.d.ts +58 -0
- package/packages/client-web/dist/mcp-client-view.d.ts.map +1 -0
- package/packages/client-web/dist/mcp-client-view.js +121 -0
- package/packages/client-web/dist/mcp-client-view.js.map +1 -0
- package/packages/client-web/dist/modlens-view.d.ts +43 -0
- package/packages/client-web/dist/modlens-view.d.ts.map +1 -0
- package/packages/client-web/dist/modlens-view.js +176 -0
- package/packages/client-web/dist/modlens-view.js.map +1 -0
- package/packages/client-web/dist/openpets-view.d.ts +26 -0
- package/packages/client-web/dist/openpets-view.d.ts.map +1 -0
- package/packages/client-web/dist/openpets-view.js +62 -0
- package/packages/client-web/dist/openpets-view.js.map +1 -0
- package/packages/client-web/dist/panel-safe.d.ts +5 -0
- package/packages/client-web/dist/panel-safe.d.ts.map +1 -0
- package/packages/client-web/dist/panel-safe.js +58 -0
- package/packages/client-web/dist/panel-safe.js.map +1 -0
- package/packages/client-web/dist/plugin-dev-view.d.ts +15 -0
- package/packages/client-web/dist/plugin-dev-view.d.ts.map +1 -0
- package/packages/client-web/dist/plugin-dev-view.js +33 -0
- package/packages/client-web/dist/plugin-dev-view.js.map +1 -0
- package/packages/client-web/dist/plugin-stars-view.d.ts +28 -0
- package/packages/client-web/dist/plugin-stars-view.d.ts.map +1 -1
- package/packages/client-web/dist/plugin-stars-view.js +209 -1
- package/packages/client-web/dist/plugin-stars-view.js.map +1 -1
- package/packages/client-web/dist/react-room.d.ts +5 -1
- package/packages/client-web/dist/react-room.d.ts.map +1 -1
- package/packages/client-web/dist/react-room.js +463 -219
- package/packages/client-web/dist/react-room.js.map +1 -1
- package/packages/client-web/dist/readme-gen-view.d.ts +30 -0
- package/packages/client-web/dist/readme-gen-view.d.ts.map +1 -0
- package/packages/client-web/dist/readme-gen-view.js +134 -0
- package/packages/client-web/dist/readme-gen-view.js.map +1 -0
- package/packages/client-web/dist/recall-unread-view.d.ts +46 -0
- package/packages/client-web/dist/recall-unread-view.d.ts.map +1 -0
- package/packages/client-web/dist/recall-unread-view.js +104 -0
- package/packages/client-web/dist/recall-unread-view.js.map +1 -0
- package/packages/client-web/dist/session-bridge-view.d.ts +50 -0
- package/packages/client-web/dist/session-bridge-view.d.ts.map +1 -0
- package/packages/client-web/dist/session-bridge-view.js +113 -0
- package/packages/client-web/dist/session-bridge-view.js.map +1 -0
- package/packages/client-web/dist/session-insights-view.d.ts +40 -0
- package/packages/client-web/dist/session-insights-view.d.ts.map +1 -0
- package/packages/client-web/dist/session-insights-view.js +157 -0
- package/packages/client-web/dist/session-insights-view.js.map +1 -0
- package/packages/client-web/dist/skill-guard-view.d.ts +47 -0
- package/packages/client-web/dist/skill-guard-view.d.ts.map +1 -0
- package/packages/client-web/dist/skill-guard-view.js +93 -0
- package/packages/client-web/dist/skill-guard-view.js.map +1 -0
- package/packages/client-web/dist/sql-lens-view.d.ts +37 -0
- package/packages/client-web/dist/sql-lens-view.d.ts.map +1 -0
- package/packages/client-web/dist/sql-lens-view.js +235 -0
- package/packages/client-web/dist/sql-lens-view.js.map +1 -0
- package/packages/client-web/dist/test-harness-view.d.ts +24 -0
- package/packages/client-web/dist/test-harness-view.d.ts.map +1 -0
- package/packages/client-web/dist/test-harness-view.js +149 -0
- package/packages/client-web/dist/test-harness-view.js.map +1 -0
- package/packages/client-web/dist/token-guard-view.d.ts +19 -0
- package/packages/client-web/dist/token-guard-view.d.ts.map +1 -0
- package/packages/client-web/dist/token-guard-view.js +56 -0
- package/packages/client-web/dist/token-guard-view.js.map +1 -0
- package/packages/client-web/dist/turn-rewind-view.d.ts +41 -0
- package/packages/client-web/dist/turn-rewind-view.d.ts.map +1 -0
- package/packages/client-web/dist/turn-rewind-view.js +117 -0
- package/packages/client-web/dist/turn-rewind-view.js.map +1 -0
- package/packages/client-web/dist/vision-toolkit-view.d.ts +66 -0
- package/packages/client-web/dist/vision-toolkit-view.d.ts.map +1 -0
- package/packages/client-web/dist/vision-toolkit-view.js +238 -0
- package/packages/client-web/dist/vision-toolkit-view.js.map +1 -0
- package/packages/client-web/dist/yaml-validator-view.d.ts +52 -0
- package/packages/client-web/dist/yaml-validator-view.d.ts.map +1 -0
- package/packages/client-web/dist/yaml-validator-view.js +96 -0
- package/packages/client-web/dist/yaml-validator-view.js.map +1 -0
- package/packages/client-web/package.json +1 -1
- package/packages/client-web/src/agent-teams-view.ts +189 -0
- package/packages/client-web/src/at-file-view.ts +69 -0
- package/packages/client-web/src/browser-fetch-view.ts +114 -0
- package/packages/client-web/src/browser-session-view.ts +222 -3
- package/packages/client-web/src/cleaner-view.ts +207 -0
- package/packages/client-web/src/context-doctor-view.ts +140 -0
- package/packages/client-web/src/context-insights-view.ts +125 -0
- package/packages/client-web/src/cost-meter-view.ts +63 -0
- package/packages/client-web/src/dependency-checker-view.ts +215 -0
- package/packages/client-web/src/docker-sandbox-view.ts +177 -0
- package/packages/client-web/src/fail-logger-view.ts +112 -0
- package/packages/client-web/src/genui-view.ts +88 -0
- package/packages/client-web/src/git-time-capsule-view.ts +215 -0
- package/packages/client-web/src/graph-memory-view.ts +91 -0
- package/packages/client-web/src/i18n-pair-view.ts +194 -0
- package/packages/client-web/src/mcp-client-view.ts +122 -0
- package/packages/client-web/src/modlens-view.ts +210 -0
- package/packages/client-web/src/openpets-view.ts +67 -0
- package/packages/client-web/src/panel-safe.ts +47 -0
- package/packages/client-web/src/plugin-dev-view.ts +39 -0
- package/packages/client-web/src/plugin-stars-view.ts +239 -1
- package/packages/client-web/src/react-room.tsx +1770 -900
- package/packages/client-web/src/readme-gen-view.ts +145 -0
- package/packages/client-web/src/recall-unread-view.ts +105 -0
- package/packages/client-web/src/session-bridge-view.ts +118 -0
- package/packages/client-web/src/session-insights-view.ts +205 -0
- package/packages/client-web/src/skill-guard-view.ts +94 -0
- package/packages/client-web/src/sql-lens-view.ts +247 -0
- package/packages/client-web/src/test-harness-view.ts +170 -0
- package/packages/client-web/src/token-guard-view.ts +75 -0
- package/packages/client-web/src/turn-rewind-view.ts +120 -0
- package/packages/client-web/src/vision-toolkit-view.ts +280 -0
- package/packages/client-web/src/yaml-validator-view.ts +102 -0
- package/packages/client-web/test/agent-teams-view.test.ts +198 -0
- package/packages/client-web/test/at-file-panel.test.ts +76 -0
- package/packages/client-web/test/browser-fetch-panel.test.ts +44 -0
- package/packages/client-web/test/browser-fetch-view.test.ts +89 -0
- package/packages/client-web/test/browser-session-panel.test.ts +55 -0
- package/packages/client-web/test/browser-session-view.test.ts +99 -1
- package/packages/client-web/test/cleaner-panel.test.ts +66 -0
- package/packages/client-web/test/cleaner-view.test.ts +143 -0
- package/packages/client-web/test/context-doctor-view.test.ts +169 -0
- package/packages/client-web/test/context-insights-view.test.ts +120 -0
- package/packages/client-web/test/cost-meter-view.test.ts +53 -0
- package/packages/client-web/test/dependency-checker-panel.test.ts +51 -0
- package/packages/client-web/test/dependency-checker-view.test.ts +266 -0
- package/packages/client-web/test/docker-sandbox-panel.test.ts +44 -0
- package/packages/client-web/test/docker-sandbox-view.test.ts +127 -0
- package/packages/client-web/test/fail-logger-view.test.ts +155 -0
- package/packages/client-web/test/genui-view.test.ts +55 -0
- package/packages/client-web/test/git-time-capsule-view.test.ts +252 -0
- package/packages/client-web/test/graph-memory-view.test.ts +67 -0
- package/packages/client-web/test/i18n-pair-panel.test.ts +72 -0
- package/packages/client-web/test/i18n-pair-view.test.ts +164 -0
- package/packages/client-web/test/mcp-client-view.test.ts +106 -0
- package/packages/client-web/test/modlens-view.test.ts +198 -0
- package/packages/client-web/test/openpets-view.test.ts +57 -0
- package/packages/client-web/test/plugin-dev-view.test.ts +63 -0
- package/packages/client-web/test/plugin-stars-panel.test.ts +44 -0
- package/packages/client-web/test/plugin-stars-view.test.ts +88 -1
- package/packages/client-web/test/readme-gen-panel.test.ts +81 -0
- package/packages/client-web/test/readme-gen-view.test.ts +91 -0
- package/packages/client-web/test/recall-unread-view.test.ts +153 -0
- package/packages/client-web/test/session-bridge-view.test.ts +102 -0
- package/packages/client-web/test/session-insights-panel.test.ts +85 -0
- package/packages/client-web/test/session-insights-view.test.ts +152 -0
- package/packages/client-web/test/skill-guard-view.test.ts +138 -0
- package/packages/client-web/test/sql-lens-panel.test.ts +52 -0
- package/packages/client-web/test/sql-lens-view.test.ts +134 -0
- package/packages/client-web/test/test-harness-panel.test.ts +228 -0
- package/packages/client-web/test/token-guard-view.test.ts +93 -0
- package/packages/client-web/test/turn-rewind-view.test.ts +159 -0
- package/packages/client-web/test/vision-toolkit-view.test.ts +152 -0
- package/packages/client-web/test/yaml-validator-view.test.ts +100 -0
- package/packages/core/dist/atomic-write.d.ts +8 -0
- package/packages/core/dist/atomic-write.d.ts.map +1 -0
- package/packages/core/dist/atomic-write.js +40 -0
- package/packages/core/dist/atomic-write.js.map +1 -0
- package/packages/core/dist/boot.d.ts.map +1 -1
- package/packages/core/dist/boot.js +132 -10
- package/packages/core/dist/boot.js.map +1 -1
- package/packages/core/dist/bounded-file.d.ts +7 -0
- package/packages/core/dist/bounded-file.d.ts.map +1 -0
- package/packages/core/dist/bounded-file.js +50 -0
- package/packages/core/dist/bounded-file.js.map +1 -0
- package/packages/core/dist/config.d.ts +2 -0
- package/packages/core/dist/config.d.ts.map +1 -1
- package/packages/core/dist/config.js +21 -0
- package/packages/core/dist/config.js.map +1 -1
- package/packages/core/dist/cordis-timer.d.ts +15 -0
- package/packages/core/dist/cordis-timer.d.ts.map +1 -0
- package/packages/core/dist/cordis-timer.js +99 -0
- package/packages/core/dist/cordis-timer.js.map +1 -0
- package/packages/core/dist/plugins/agent-teams.d.ts +5 -1
- package/packages/core/dist/plugins/agent-teams.d.ts.map +1 -1
- package/packages/core/dist/plugins/agent-teams.js +986 -105
- package/packages/core/dist/plugins/agent-teams.js.map +1 -1
- package/packages/core/dist/plugins/anchored-standard.d.ts +1 -0
- package/packages/core/dist/plugins/anchored-standard.d.ts.map +1 -1
- package/packages/core/dist/plugins/anchored-standard.js +39 -11
- package/packages/core/dist/plugins/anchored-standard.js.map +1 -1
- package/packages/core/dist/plugins/annotation.d.ts +1 -0
- package/packages/core/dist/plugins/annotation.d.ts.map +1 -1
- package/packages/core/dist/plugins/annotation.js +35 -17
- package/packages/core/dist/plugins/annotation.js.map +1 -1
- package/packages/core/dist/plugins/archify.d.ts +1 -0
- package/packages/core/dist/plugins/archify.d.ts.map +1 -1
- package/packages/core/dist/plugins/archify.js +69 -22
- package/packages/core/dist/plugins/archify.js.map +1 -1
- package/packages/core/dist/plugins/at-file.d.ts +5 -1
- package/packages/core/dist/plugins/at-file.d.ts.map +1 -1
- package/packages/core/dist/plugins/at-file.js +120 -29
- package/packages/core/dist/plugins/at-file.js.map +1 -1
- package/packages/core/dist/plugins/auto-mode.d.ts.map +1 -1
- package/packages/core/dist/plugins/auto-mode.js +89 -20
- package/packages/core/dist/plugins/auto-mode.js.map +1 -1
- package/packages/core/dist/plugins/better-sidebar.d.ts +1 -0
- package/packages/core/dist/plugins/better-sidebar.d.ts.map +1 -1
- package/packages/core/dist/plugins/better-sidebar.js +14 -4
- package/packages/core/dist/plugins/better-sidebar.js.map +1 -1
- package/packages/core/dist/plugins/browser-fetch.d.ts +1 -0
- package/packages/core/dist/plugins/browser-fetch.d.ts.map +1 -1
- package/packages/core/dist/plugins/browser-fetch.js +288 -96
- package/packages/core/dist/plugins/browser-fetch.js.map +1 -1
- package/packages/core/dist/plugins/browser-session.d.ts.map +1 -1
- package/packages/core/dist/plugins/browser-session.js +445 -106
- package/packages/core/dist/plugins/browser-session.js.map +1 -1
- package/packages/core/dist/plugins/canvas-draw.d.ts +1 -0
- package/packages/core/dist/plugins/canvas-draw.d.ts.map +1 -1
- package/packages/core/dist/plugins/canvas-draw.js +71 -19
- package/packages/core/dist/plugins/canvas-draw.js.map +1 -1
- package/packages/core/dist/plugins/change-verifier.d.ts +1 -0
- package/packages/core/dist/plugins/change-verifier.d.ts.map +1 -1
- package/packages/core/dist/plugins/change-verifier.js +30 -9
- package/packages/core/dist/plugins/change-verifier.js.map +1 -1
- package/packages/core/dist/plugins/cleaner.d.ts +5 -1
- package/packages/core/dist/plugins/cleaner.d.ts.map +1 -1
- package/packages/core/dist/plugins/cleaner.js +337 -40
- package/packages/core/dist/plugins/cleaner.js.map +1 -1
- package/packages/core/dist/plugins/cli-notifier.d.ts +2 -1
- package/packages/core/dist/plugins/cli-notifier.d.ts.map +1 -1
- package/packages/core/dist/plugins/cli-notifier.js +99 -42
- package/packages/core/dist/plugins/cli-notifier.js.map +1 -1
- package/packages/core/dist/plugins/code2skill.d.ts +1 -0
- package/packages/core/dist/plugins/code2skill.d.ts.map +1 -1
- package/packages/core/dist/plugins/code2skill.js +178 -38
- package/packages/core/dist/plugins/code2skill.js.map +1 -1
- package/packages/core/dist/plugins/colleague-skill.d.ts +2 -1
- package/packages/core/dist/plugins/colleague-skill.d.ts.map +1 -1
- package/packages/core/dist/plugins/colleague-skill.js +79 -49
- package/packages/core/dist/plugins/colleague-skill.js.map +1 -1
- package/packages/core/dist/plugins/context-doctor.d.ts +6 -1
- package/packages/core/dist/plugins/context-doctor.d.ts.map +1 -1
- package/packages/core/dist/plugins/context-doctor.js +403 -39
- package/packages/core/dist/plugins/context-doctor.js.map +1 -1
- package/packages/core/dist/plugins/context.d.ts +1 -0
- package/packages/core/dist/plugins/context.d.ts.map +1 -1
- package/packages/core/dist/plugins/context.js +169 -42
- package/packages/core/dist/plugins/context.js.map +1 -1
- package/packages/core/dist/plugins/cost-meter.d.ts +4 -0
- package/packages/core/dist/plugins/cost-meter.d.ts.map +1 -1
- package/packages/core/dist/plugins/cost-meter.js +319 -93
- package/packages/core/dist/plugins/cost-meter.js.map +1 -1
- package/packages/core/dist/plugins/dependency-checker.d.ts +11 -2
- package/packages/core/dist/plugins/dependency-checker.d.ts.map +1 -1
- package/packages/core/dist/plugins/dependency-checker.js +463 -114
- package/packages/core/dist/plugins/dependency-checker.js.map +1 -1
- package/packages/core/dist/plugins/docker-sandbox.d.ts +1 -0
- package/packages/core/dist/plugins/docker-sandbox.d.ts.map +1 -1
- package/packages/core/dist/plugins/docker-sandbox.js +322 -80
- package/packages/core/dist/plugins/docker-sandbox.js.map +1 -1
- package/packages/core/dist/plugins/fail-logger.d.ts +1 -0
- package/packages/core/dist/plugins/fail-logger.d.ts.map +1 -1
- package/packages/core/dist/plugins/fail-logger.js +187 -20
- package/packages/core/dist/plugins/fail-logger.js.map +1 -1
- package/packages/core/dist/plugins/genui.d.ts +1 -0
- package/packages/core/dist/plugins/genui.d.ts.map +1 -1
- package/packages/core/dist/plugins/genui.js +103 -38
- package/packages/core/dist/plugins/genui.js.map +1 -1
- package/packages/core/dist/plugins/git-time-capsule.d.ts +8 -2
- package/packages/core/dist/plugins/git-time-capsule.d.ts.map +1 -1
- package/packages/core/dist/plugins/git-time-capsule.js +400 -54
- package/packages/core/dist/plugins/git-time-capsule.js.map +1 -1
- package/packages/core/dist/plugins/graph-memory.d.ts.map +1 -1
- package/packages/core/dist/plugins/graph-memory.js +329 -42
- package/packages/core/dist/plugins/graph-memory.js.map +1 -1
- package/packages/core/dist/plugins/history-compressor.d.ts.map +1 -1
- package/packages/core/dist/plugins/history-compressor.js +31 -12
- package/packages/core/dist/plugins/history-compressor.js.map +1 -1
- package/packages/core/dist/plugins/hol-guard.d.ts.map +1 -1
- package/packages/core/dist/plugins/hol-guard.js +33 -13
- package/packages/core/dist/plugins/hol-guard.js.map +1 -1
- package/packages/core/dist/plugins/i18n-pair.d.ts +5 -1
- package/packages/core/dist/plugins/i18n-pair.d.ts.map +1 -1
- package/packages/core/dist/plugins/i18n-pair.js +308 -52
- package/packages/core/dist/plugins/i18n-pair.js.map +1 -1
- package/packages/core/dist/plugins/image-compressor.d.ts +7 -0
- package/packages/core/dist/plugins/image-compressor.d.ts.map +1 -1
- package/packages/core/dist/plugins/image-compressor.js +98 -25
- package/packages/core/dist/plugins/image-compressor.js.map +1 -1
- package/packages/core/dist/plugins/llm-verifier.d.ts.map +1 -1
- package/packages/core/dist/plugins/llm-verifier.js +21 -11
- package/packages/core/dist/plugins/llm-verifier.js.map +1 -1
- package/packages/core/dist/plugins/mcp-client.d.ts.map +1 -1
- package/packages/core/dist/plugins/mcp-client.js +986 -232
- package/packages/core/dist/plugins/mcp-client.js.map +1 -1
- package/packages/core/dist/plugins/mcp-panel.d.ts.map +1 -1
- package/packages/core/dist/plugins/mcp-panel.js +30 -16
- package/packages/core/dist/plugins/mcp-panel.js.map +1 -1
- package/packages/core/dist/plugins/memory.d.ts.map +1 -1
- package/packages/core/dist/plugins/memory.js +292 -59
- package/packages/core/dist/plugins/memory.js.map +1 -1
- package/packages/core/dist/plugins/mirage-bridge.d.ts.map +1 -1
- package/packages/core/dist/plugins/mirage-bridge.js +20 -10
- package/packages/core/dist/plugins/mirage-bridge.js.map +1 -1
- package/packages/core/dist/plugins/mock-server.d.ts.map +1 -1
- package/packages/core/dist/plugins/mock-server.js +85 -54
- package/packages/core/dist/plugins/mock-server.js.map +1 -1
- package/packages/core/dist/plugins/model.d.ts +3 -0
- package/packages/core/dist/plugins/model.d.ts.map +1 -1
- package/packages/core/dist/plugins/model.js +3 -0
- package/packages/core/dist/plugins/model.js.map +1 -1
- package/packages/core/dist/plugins/models.d.ts.map +1 -1
- package/packages/core/dist/plugins/models.js +10 -2
- package/packages/core/dist/plugins/models.js.map +1 -1
- package/packages/core/dist/plugins/modlens.d.ts +8 -1
- package/packages/core/dist/plugins/modlens.d.ts.map +1 -1
- package/packages/core/dist/plugins/modlens.js +438 -31
- package/packages/core/dist/plugins/modlens.js.map +1 -1
- package/packages/core/dist/plugins/module-search.d.ts +1 -0
- package/packages/core/dist/plugins/module-search.d.ts.map +1 -1
- package/packages/core/dist/plugins/module-search.js +35 -16
- package/packages/core/dist/plugins/module-search.js.map +1 -1
- package/packages/core/dist/plugins/obsidian-sync.d.ts.map +1 -1
- package/packages/core/dist/plugins/obsidian-sync.js +20 -11
- package/packages/core/dist/plugins/obsidian-sync.js.map +1 -1
- package/packages/core/dist/plugins/openpets.d.ts.map +1 -1
- package/packages/core/dist/plugins/openpets.js +203 -44
- package/packages/core/dist/plugins/openpets.js.map +1 -1
- package/packages/core/dist/plugins/plan-execute.d.ts +4 -1
- package/packages/core/dist/plugins/plan-execute.d.ts.map +1 -1
- package/packages/core/dist/plugins/plan-execute.js +42 -21
- package/packages/core/dist/plugins/plan-execute.js.map +1 -1
- package/packages/core/dist/plugins/plugin-check.d.ts +12 -3
- package/packages/core/dist/plugins/plugin-check.d.ts.map +1 -1
- package/packages/core/dist/plugins/plugin-check.js +83 -12
- package/packages/core/dist/plugins/plugin-check.js.map +1 -1
- package/packages/core/dist/plugins/plugin-dev.d.ts +1 -0
- package/packages/core/dist/plugins/plugin-dev.d.ts.map +1 -1
- package/packages/core/dist/plugins/plugin-dev.js +156 -23
- package/packages/core/dist/plugins/plugin-dev.js.map +1 -1
- package/packages/core/dist/plugins/plugin-finder.d.ts +1 -0
- package/packages/core/dist/plugins/plugin-finder.d.ts.map +1 -1
- package/packages/core/dist/plugins/plugin-finder.js +131 -13
- package/packages/core/dist/plugins/plugin-finder.js.map +1 -1
- package/packages/core/dist/plugins/plugin-radar.d.ts +4 -0
- package/packages/core/dist/plugins/plugin-radar.d.ts.map +1 -1
- package/packages/core/dist/plugins/plugin-radar.js +137 -36
- package/packages/core/dist/plugins/plugin-radar.js.map +1 -1
- package/packages/core/dist/plugins/plugin-stars.d.ts.map +1 -1
- package/packages/core/dist/plugins/plugin-stars.js +282 -81
- package/packages/core/dist/plugins/plugin-stars.js.map +1 -1
- package/packages/core/dist/plugins/prompt-guard.d.ts +1 -0
- package/packages/core/dist/plugins/prompt-guard.d.ts.map +1 -1
- package/packages/core/dist/plugins/prompt-guard.js +60 -9
- package/packages/core/dist/plugins/prompt-guard.js.map +1 -1
- package/packages/core/dist/plugins/prompt-library.d.ts +1 -0
- package/packages/core/dist/plugins/prompt-library.d.ts.map +1 -1
- package/packages/core/dist/plugins/prompt-library.js +4 -1
- package/packages/core/dist/plugins/prompt-library.js.map +1 -1
- package/packages/core/dist/plugins/readme-gen.d.ts +6 -2
- package/packages/core/dist/plugins/readme-gen.d.ts.map +1 -1
- package/packages/core/dist/plugins/readme-gen.js +514 -73
- package/packages/core/dist/plugins/readme-gen.js.map +1 -1
- package/packages/core/dist/plugins/recall-unread.d.ts +1 -1
- package/packages/core/dist/plugins/recall-unread.d.ts.map +1 -1
- package/packages/core/dist/plugins/recall-unread.js +411 -68
- package/packages/core/dist/plugins/recall-unread.js.map +1 -1
- package/packages/core/dist/plugins/resources.d.ts.map +1 -1
- package/packages/core/dist/plugins/resources.js +66 -3
- package/packages/core/dist/plugins/resources.js.map +1 -1
- package/packages/core/dist/plugins/reverse-skill.d.ts.map +1 -1
- package/packages/core/dist/plugins/reverse-skill.js +2 -1
- package/packages/core/dist/plugins/reverse-skill.js.map +1 -1
- package/packages/core/dist/plugins/reviewer-bot.d.ts +1 -0
- package/packages/core/dist/plugins/reviewer-bot.d.ts.map +1 -1
- package/packages/core/dist/plugins/reviewer-bot.js +35 -16
- package/packages/core/dist/plugins/reviewer-bot.js.map +1 -1
- package/packages/core/dist/plugins/runtime-doctor.d.ts +1 -0
- package/packages/core/dist/plugins/runtime-doctor.d.ts.map +1 -1
- package/packages/core/dist/plugins/runtime-doctor.js +4 -1
- package/packages/core/dist/plugins/runtime-doctor.js.map +1 -1
- package/packages/core/dist/plugins/runtime.d.ts.map +1 -1
- package/packages/core/dist/plugins/runtime.js +104 -70
- package/packages/core/dist/plugins/runtime.js.map +1 -1
- package/packages/core/dist/plugins/secure-audit.d.ts +1 -0
- package/packages/core/dist/plugins/secure-audit.d.ts.map +1 -1
- package/packages/core/dist/plugins/secure-audit.js +30 -12
- package/packages/core/dist/plugins/secure-audit.js.map +1 -1
- package/packages/core/dist/plugins/session-bookmarks.d.ts +1 -0
- package/packages/core/dist/plugins/session-bookmarks.d.ts.map +1 -1
- package/packages/core/dist/plugins/session-bookmarks.js +4 -1
- package/packages/core/dist/plugins/session-bookmarks.js.map +1 -1
- package/packages/core/dist/plugins/session-bridge.d.ts +1 -0
- package/packages/core/dist/plugins/session-bridge.d.ts.map +1 -1
- package/packages/core/dist/plugins/session-bridge.js +401 -88
- package/packages/core/dist/plugins/session-bridge.js.map +1 -1
- package/packages/core/dist/plugins/session-compare.d.ts +1 -0
- package/packages/core/dist/plugins/session-compare.d.ts.map +1 -1
- package/packages/core/dist/plugins/session-compare.js +4 -1
- package/packages/core/dist/plugins/session-compare.js.map +1 -1
- package/packages/core/dist/plugins/session-export.d.ts +1 -0
- package/packages/core/dist/plugins/session-export.d.ts.map +1 -1
- package/packages/core/dist/plugins/session-export.js +6 -6
- package/packages/core/dist/plugins/session-export.js.map +1 -1
- package/packages/core/dist/plugins/session-insights.d.ts +37 -1
- package/packages/core/dist/plugins/session-insights.d.ts.map +1 -1
- package/packages/core/dist/plugins/session-insights.js +406 -21
- package/packages/core/dist/plugins/session-insights.js.map +1 -1
- package/packages/core/dist/plugins/session-search.d.ts +1 -0
- package/packages/core/dist/plugins/session-search.d.ts.map +1 -1
- package/packages/core/dist/plugins/session-search.js +4 -1
- package/packages/core/dist/plugins/session-search.js.map +1 -1
- package/packages/core/dist/plugins/session.d.ts.map +1 -1
- package/packages/core/dist/plugins/session.js +8 -1
- package/packages/core/dist/plugins/session.js.map +1 -1
- package/packages/core/dist/plugins/skill-catalog.d.ts +1 -0
- package/packages/core/dist/plugins/skill-catalog.d.ts.map +1 -1
- package/packages/core/dist/plugins/skill-catalog.js +6 -5
- package/packages/core/dist/plugins/skill-catalog.js.map +1 -1
- package/packages/core/dist/plugins/skill-guard.d.ts +2 -1
- package/packages/core/dist/plugins/skill-guard.d.ts.map +1 -1
- package/packages/core/dist/plugins/skill-guard.js +241 -61
- package/packages/core/dist/plugins/skill-guard.js.map +1 -1
- package/packages/core/dist/plugins/sql-lens-worker.d.ts +2 -0
- package/packages/core/dist/plugins/sql-lens-worker.d.ts.map +1 -0
- package/packages/core/dist/plugins/sql-lens-worker.js +206 -0
- package/packages/core/dist/plugins/sql-lens-worker.js.map +1 -0
- package/packages/core/dist/plugins/sql-lens.d.ts +7 -1
- package/packages/core/dist/plugins/sql-lens.d.ts.map +1 -1
- package/packages/core/dist/plugins/sql-lens.js +301 -68
- package/packages/core/dist/plugins/sql-lens.js.map +1 -1
- package/packages/core/dist/plugins/stdio.d.ts +3 -0
- package/packages/core/dist/plugins/stdio.d.ts.map +1 -1
- package/packages/core/dist/plugins/stdio.js +62 -4
- package/packages/core/dist/plugins/stdio.js.map +1 -1
- package/packages/core/dist/plugins/synapse.d.ts.map +1 -1
- package/packages/core/dist/plugins/synapse.js +17 -9
- package/packages/core/dist/plugins/synapse.js.map +1 -1
- package/packages/core/dist/plugins/tab-manager.d.ts +1 -0
- package/packages/core/dist/plugins/tab-manager.d.ts.map +1 -1
- package/packages/core/dist/plugins/tab-manager.js +167 -62
- package/packages/core/dist/plugins/tab-manager.js.map +1 -1
- package/packages/core/dist/plugins/taskboard.d.ts.map +1 -1
- package/packages/core/dist/plugins/taskboard.js +50 -22
- package/packages/core/dist/plugins/taskboard.js.map +1 -1
- package/packages/core/dist/plugins/telemetry-blocker.d.ts +4 -1
- package/packages/core/dist/plugins/telemetry-blocker.d.ts.map +1 -1
- package/packages/core/dist/plugins/telemetry-blocker.js +30 -11
- package/packages/core/dist/plugins/telemetry-blocker.js.map +1 -1
- package/packages/core/dist/plugins/test-harness.d.ts +7 -1
- package/packages/core/dist/plugins/test-harness.d.ts.map +1 -1
- package/packages/core/dist/plugins/test-harness.js +255 -43
- package/packages/core/dist/plugins/test-harness.js.map +1 -1
- package/packages/core/dist/plugins/theme-studio.d.ts.map +1 -1
- package/packages/core/dist/plugins/theme-studio.js +4 -2
- package/packages/core/dist/plugins/theme-studio.js.map +1 -1
- package/packages/core/dist/plugins/token-guard.d.ts.map +1 -1
- package/packages/core/dist/plugins/token-guard.js +164 -32
- package/packages/core/dist/plugins/token-guard.js.map +1 -1
- package/packages/core/dist/plugins/tools.d.ts.map +1 -1
- package/packages/core/dist/plugins/tools.js +4 -1
- package/packages/core/dist/plugins/tools.js.map +1 -1
- package/packages/core/dist/plugins/turn-rewind.d.ts +17 -0
- package/packages/core/dist/plugins/turn-rewind.d.ts.map +1 -1
- package/packages/core/dist/plugins/turn-rewind.js +340 -36
- package/packages/core/dist/plugins/turn-rewind.js.map +1 -1
- package/packages/core/dist/plugins/undo-savepoint.d.ts.map +1 -1
- package/packages/core/dist/plugins/undo-savepoint.js +157 -51
- package/packages/core/dist/plugins/undo-savepoint.js.map +1 -1
- package/packages/core/dist/plugins/vision-toolkit.d.ts +22 -3
- package/packages/core/dist/plugins/vision-toolkit.d.ts.map +1 -1
- package/packages/core/dist/plugins/vision-toolkit.js +389 -66
- package/packages/core/dist/plugins/vision-toolkit.js.map +1 -1
- package/packages/core/dist/plugins/web-research.d.ts.map +1 -1
- package/packages/core/dist/plugins/web-research.js +13 -4
- package/packages/core/dist/plugins/web-research.js.map +1 -1
- package/packages/core/dist/plugins/workspace-navigator.d.ts +8 -2
- package/packages/core/dist/plugins/workspace-navigator.d.ts.map +1 -1
- package/packages/core/dist/plugins/workspace-navigator.js +28 -10
- package/packages/core/dist/plugins/workspace-navigator.js.map +1 -1
- package/packages/core/dist/plugins/workspace-search.d.ts +7 -0
- package/packages/core/dist/plugins/workspace-search.d.ts.map +1 -1
- package/packages/core/dist/plugins/workspace-search.js +67 -34
- package/packages/core/dist/plugins/workspace-search.js.map +1 -1
- package/packages/core/dist/plugins/yaml-validator.d.ts +1 -0
- package/packages/core/dist/plugins/yaml-validator.d.ts.map +1 -1
- package/packages/core/dist/plugins/yaml-validator.js +156 -47
- package/packages/core/dist/plugins/yaml-validator.js.map +1 -1
- package/packages/core/dist/runtime.d.ts.map +1 -1
- package/packages/core/dist/runtime.js +8 -3
- package/packages/core/dist/runtime.js.map +1 -1
- package/packages/core/dist/services.d.ts.map +1 -1
- package/packages/core/dist/services.js +22 -2
- package/packages/core/dist/services.js.map +1 -1
- package/packages/core/dist/stdio.d.ts +1 -0
- package/packages/core/dist/stdio.d.ts.map +1 -1
- package/packages/core/dist/stdio.js +241 -25
- package/packages/core/dist/stdio.js.map +1 -1
- package/packages/core/dist/workspace-path.d.ts +1 -0
- package/packages/core/dist/workspace-path.d.ts.map +1 -1
- package/packages/core/dist/workspace-path.js +9 -0
- package/packages/core/dist/workspace-path.js.map +1 -1
- package/packages/core/package.json +1 -1
- package/packages/core/profiles/default/cordis.yml +4 -2
- package/packages/core/src/atomic-write.ts +45 -0
- package/packages/core/src/boot.ts +123 -11
- package/packages/core/src/bounded-file.ts +41 -0
- package/packages/core/src/config.ts +18 -0
- package/packages/core/src/cordis-timer.ts +107 -0
- package/packages/core/src/plugins/agent-teams.ts +1003 -103
- package/packages/core/src/plugins/anchored-standard.ts +41 -14
- package/packages/core/src/plugins/annotation.ts +36 -19
- package/packages/core/src/plugins/archify.ts +80 -33
- package/packages/core/src/plugins/at-file.ts +110 -26
- package/packages/core/src/plugins/auto-mode.ts +91 -24
- package/packages/core/src/plugins/better-sidebar.ts +15 -4
- package/packages/core/src/plugins/browser-fetch.ts +286 -95
- package/packages/core/src/plugins/browser-session.ts +421 -102
- package/packages/core/src/plugins/canvas-draw.ts +74 -21
- package/packages/core/src/plugins/change-verifier.ts +33 -9
- package/packages/core/src/plugins/cleaner.ts +339 -37
- package/packages/core/src/plugins/cli-notifier.ts +110 -41
- package/packages/core/src/plugins/code2skill.ts +175 -34
- package/packages/core/src/plugins/colleague-skill.ts +72 -47
- package/packages/core/src/plugins/context-doctor.ts +410 -40
- package/packages/core/src/plugins/context.ts +164 -39
- package/packages/core/src/plugins/cost-meter.ts +360 -113
- package/packages/core/src/plugins/dependency-checker.ts +456 -119
- package/packages/core/src/plugins/docker-sandbox.ts +312 -76
- package/packages/core/src/plugins/fail-logger.ts +169 -18
- package/packages/core/src/plugins/genui.ts +104 -42
- package/packages/core/src/plugins/git-time-capsule.ts +424 -55
- package/packages/core/src/plugins/graph-memory.ts +333 -60
- package/packages/core/src/plugins/history-compressor.ts +29 -12
- package/packages/core/src/plugins/hol-guard.ts +31 -13
- package/packages/core/src/plugins/i18n-pair.ts +296 -52
- package/packages/core/src/plugins/image-compressor.ts +98 -25
- package/packages/core/src/plugins/llm-verifier.ts +39 -21
- package/packages/core/src/plugins/mcp-client.ts +1069 -249
- package/packages/core/src/plugins/mcp-panel.ts +37 -21
- package/packages/core/src/plugins/memory.ts +262 -57
- package/packages/core/src/plugins/mirage-bridge.ts +22 -10
- package/packages/core/src/plugins/mock-server.ts +91 -60
- package/packages/core/src/plugins/model.ts +4 -0
- package/packages/core/src/plugins/models.ts +10 -2
- package/packages/core/src/plugins/modlens.ts +463 -29
- package/packages/core/src/plugins/module-search.ts +40 -19
- package/packages/core/src/plugins/obsidian-sync.ts +26 -15
- package/packages/core/src/plugins/openpets.ts +193 -42
- package/packages/core/src/plugins/plan-execute.ts +49 -21
- package/packages/core/src/plugins/plugin-check.ts +100 -16
- package/packages/core/src/plugins/plugin-dev.ts +167 -23
- package/packages/core/src/plugins/plugin-finder.ts +119 -12
- package/packages/core/src/plugins/plugin-radar.ts +130 -39
- package/packages/core/src/plugins/plugin-stars.ts +252 -80
- package/packages/core/src/plugins/prompt-guard.ts +51 -12
- package/packages/core/src/plugins/prompt-library.ts +14 -8
- package/packages/core/src/plugins/readme-gen.ts +476 -79
- package/packages/core/src/plugins/recall-unread.ts +412 -68
- package/packages/core/src/plugins/resources.ts +70 -2
- package/packages/core/src/plugins/reverse-skill.ts +9 -5
- package/packages/core/src/plugins/reviewer-bot.ts +34 -16
- package/packages/core/src/plugins/runtime-doctor.ts +4 -1
- package/packages/core/src/plugins/runtime.ts +98 -68
- package/packages/core/src/plugins/secure-audit.ts +33 -12
- package/packages/core/src/plugins/session-bookmarks.ts +12 -6
- package/packages/core/src/plugins/session-bridge.ts +404 -89
- package/packages/core/src/plugins/session-compare.ts +10 -4
- package/packages/core/src/plugins/session-export.ts +12 -9
- package/packages/core/src/plugins/session-insights.ts +454 -31
- package/packages/core/src/plugins/session-search.ts +4 -1
- package/packages/core/src/plugins/session.ts +8 -1
- package/packages/core/src/plugins/skill-catalog.ts +13 -8
- package/packages/core/src/plugins/skill-guard.ts +254 -61
- package/packages/core/src/plugins/sql-lens-worker.ts +202 -0
- package/packages/core/src/plugins/sql-lens.ts +323 -71
- package/packages/core/src/plugins/stdio.ts +58 -3
- package/packages/core/src/plugins/synapse.ts +16 -9
- package/packages/core/src/plugins/tab-manager.ts +163 -65
- package/packages/core/src/plugins/taskboard.ts +81 -47
- package/packages/core/src/plugins/telemetry-blocker.ts +27 -11
- package/packages/core/src/plugins/test-harness.ts +275 -42
- package/packages/core/src/plugins/theme-studio.ts +7 -2
- package/packages/core/src/plugins/token-guard.ts +171 -31
- package/packages/core/src/plugins/tools.ts +4 -1
- package/packages/core/src/plugins/turn-rewind.ts +351 -37
- package/packages/core/src/plugins/undo-savepoint.ts +160 -59
- package/packages/core/src/plugins/vision-toolkit.ts +420 -63
- package/packages/core/src/plugins/web-research.ts +19 -7
- package/packages/core/src/plugins/workspace-navigator.ts +39 -14
- package/packages/core/src/plugins/workspace-search.ts +75 -37
- package/packages/core/src/plugins/yaml-validator.ts +165 -46
- package/packages/core/src/runtime.ts +8 -2
- package/packages/core/src/services.ts +22 -2
- package/packages/core/src/stdio.ts +220 -25
- package/packages/core/src/workspace-path.ts +10 -0
- package/packages/core/test/agent-teams.test.ts +495 -2
- package/packages/core/test/anchored-standard.test.ts +60 -0
- package/packages/core/test/annotation.test.ts +69 -0
- package/packages/core/test/archify.test.ts +212 -1
- package/packages/core/test/at-file.test.ts +666 -0
- package/packages/core/test/atomic-write.test.ts +52 -0
- package/packages/core/test/auto-mode.test.ts +629 -0
- package/packages/core/test/better-sidebar.test.ts +108 -1
- package/packages/core/test/boot.test.ts +129 -1
- package/packages/core/test/bounded-file.test.ts +55 -0
- package/packages/core/test/browser-fetch-rebinding.test.ts +93 -0
- package/packages/core/test/browser-fetch.test.ts +844 -0
- package/packages/core/test/browser-session.test.ts +1236 -0
- package/packages/core/test/canvas-draw.test.ts +164 -0
- package/packages/core/test/change-verifier.test.ts +200 -0
- package/packages/core/test/cleaner.test.ts +316 -0
- package/packages/core/test/cli-notifier.test.ts +235 -0
- package/packages/core/test/code2skill.test.ts +264 -0
- package/packages/core/test/colleague-skill.test.ts +167 -0
- package/packages/core/test/config.test.ts +8 -0
- package/packages/core/test/context-doctor.test.ts +597 -2
- package/packages/core/test/context-insights.test.ts +396 -0
- package/packages/core/test/cordis-timer.test.ts +147 -0
- package/packages/core/test/cost-meter.test.ts +362 -0
- package/packages/core/test/dependency-checker.test.ts +576 -3
- package/packages/core/test/docker-sandbox.test.ts +170 -0
- package/packages/core/test/fail-logger.test.ts +363 -0
- package/packages/core/test/genui.test.ts +162 -0
- package/packages/core/test/git-time-capsule.test.ts +1364 -3
- package/packages/core/test/graph-memory.test.ts +508 -0
- package/packages/core/test/history-compressor.test.ts +47 -0
- package/packages/core/test/hol-guard.test.ts +61 -0
- package/packages/core/test/i18n-pair.test.ts +367 -0
- package/packages/core/test/image-compressor.test.ts +59 -0
- package/packages/core/test/llm-verifier.test.ts +4 -0
- package/packages/core/test/mcp-client.test.ts +1037 -0
- package/packages/core/test/mcp-panel.test.ts +91 -0
- package/packages/core/test/memory.test.ts +125 -0
- package/packages/core/test/mirage-bridge.test.ts +4 -0
- package/packages/core/test/mock-server.test.ts +63 -0
- package/packages/core/test/model.test.ts +62 -0
- package/packages/core/test/models.test.ts +85 -0
- package/packages/core/test/modlens.test.ts +615 -0
- package/packages/core/test/module-search.test.ts +20 -0
- package/packages/core/test/obsidian-sync.test.ts +57 -0
- package/packages/core/test/openpets.test.ts +273 -0
- package/packages/core/test/plan-execute.test.ts +86 -0
- package/packages/core/test/plugin-dev.test.ts +246 -0
- package/packages/core/test/plugin-finder.test.ts +105 -0
- package/packages/core/test/plugin-radar.test.ts +87 -0
- package/packages/core/test/plugin-stars.test.ts +429 -4
- package/packages/core/test/profiles.integration.test.ts +451 -2
- package/packages/core/test/prompt-guard.test.ts +78 -0
- package/packages/core/test/readme-gen.test.ts +403 -3
- package/packages/core/test/recall-unread.test.ts +559 -2
- package/packages/core/test/registry.test.ts +16 -0
- package/packages/core/test/resources.test.ts +310 -0
- package/packages/core/test/reviewer-bot.test.ts +55 -0
- package/packages/core/test/runtime-fixture.ts +10 -1
- package/packages/core/test/runtime.test.ts +155 -2
- package/packages/core/test/secure-audit.test.ts +14 -1
- package/packages/core/test/services.test.ts +2772 -117
- package/packages/core/test/session-bridge.test.ts +309 -1
- package/packages/core/test/session-insights.test.ts +486 -0
- package/packages/core/test/session.test.ts +153 -0
- package/packages/core/test/skill-guard.test.ts +261 -2
- package/packages/core/test/sql-lens.test.ts +244 -0
- package/packages/core/test/stdio-args.test.ts +484 -1
- package/packages/core/test/stdio.test.ts +159 -0
- package/packages/core/test/synapse.test.ts +67 -0
- package/packages/core/test/tab-manager.test.ts +58 -0
- package/packages/core/test/taskboard.test.ts +73 -0
- package/packages/core/test/telemetry-blocker.test.ts +61 -0
- package/packages/core/test/test-harness.test.ts +297 -0
- package/packages/core/test/token-guard.test.ts +362 -1
- package/packages/core/test/tools.test.ts +118 -0
- package/packages/core/test/turn-rewind.test.ts +532 -2
- package/packages/core/test/undo-savepoint.test.ts +222 -0
- package/packages/core/test/vision-toolkit.test.ts +480 -7
- package/packages/core/test/web-research.test.ts +63 -0
- package/packages/core/test/workspace-git-status.test.ts +19 -2
- package/packages/core/test/workspace-navigator.test.ts +11 -1
- package/packages/core/test/workspace-search.test.ts +88 -0
- package/packages/core/test/yaml-validator.test.ts +195 -0
- package/packages/host-webserver/package.json +1 -1
- package/apps/web/dist/assets/index-C_AdODuM.js +0 -20
- package/apps/web/dist/assets/index-De8Rm01Z.css +0 -2
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { chmod, mkdir, mkdtemp, readFile, readdir, rm, stat, symlink, writeFile } from "node:fs/promises";
|
|
3
3
|
import { execFile, spawn, type ChildProcess } from "node:child_process";
|
|
4
|
-
import { createServer } from "node:http";
|
|
4
|
+
import { createServer, Server } from "node:http";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
|
-
import { dirname, join } from "node:path";
|
|
6
|
+
import { delimiter, dirname, join } from "node:path";
|
|
7
7
|
import { promisify } from "node:util";
|
|
8
|
+
import { deflateSync } from "node:zlib";
|
|
8
9
|
import { DatabaseSync } from "node:sqlite";
|
|
9
10
|
import { Context } from "@deepseek-ai/cordis";
|
|
10
11
|
import { Type } from "@earendil-works/pi-ai";
|
|
11
12
|
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
12
13
|
import { defineTool, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
13
|
-
import { afterEach, describe, expect, test } from "vitest";
|
|
14
|
+
import { afterEach, describe, expect, test, vi } from "vitest";
|
|
14
15
|
import { PiPluginUiRegistry, PiToolRegistry, provideLaunchContext } from "../src/services.js";
|
|
15
16
|
import modelPlugin from "../src/plugins/model.js";
|
|
16
17
|
import modelsPlugin from "../src/plugins/models.js";
|
|
@@ -66,10 +67,11 @@ import synapsePlugin from "../src/plugins/synapse.js";
|
|
|
66
67
|
import { inspectGuardInput } from "../src/plugins/hol-guard.js";
|
|
67
68
|
import holGuardPlugin from "../src/plugins/hol-guard.js";
|
|
68
69
|
import pluginRadarPlugin from "../src/plugins/plugin-radar.js";
|
|
69
|
-
import pluginCheckPlugin, { type PluginCheckScanReport } from "../src/plugins/plugin-check.js";
|
|
70
|
+
import pluginCheckPlugin, { type PluginCheckReport, type PluginCheckScanReport } from "../src/plugins/plugin-check.js";
|
|
70
71
|
import annotationPlugin from "../src/plugins/annotation.js";
|
|
71
72
|
import costMeterPlugin from "../src/plugins/cost-meter.js";
|
|
72
73
|
import skillCatalogPlugin from "../src/plugins/skill-catalog.js";
|
|
74
|
+
import skillGuardPlugin from "../src/plugins/skill-guard.js";
|
|
73
75
|
import undoSavepointPlugin from "../src/plugins/undo-savepoint.js";
|
|
74
76
|
import mcpPanelPlugin from "../src/plugins/mcp-panel.js";
|
|
75
77
|
|
|
@@ -165,6 +167,32 @@ async function createContext(): Promise<{ context: Context; cwd: string; agentDi
|
|
|
165
167
|
const modelConfig = { provider: "deepseek", model: "deepseek-v4-flash", refreshOnCreate: false } as const;
|
|
166
168
|
const isolatedResources = { noExtensions: true, noSkills: true, noPromptTemplates: true, noThemes: true, noContextFiles: true } as const;
|
|
167
169
|
|
|
170
|
+
function testPngChunk(type: string, data: Buffer): Buffer {
|
|
171
|
+
const typeBytes = Buffer.from(type, "ascii");
|
|
172
|
+
let crc = 0xffffffff;
|
|
173
|
+
for (const byte of Buffer.concat([typeBytes, data])) {
|
|
174
|
+
crc ^= byte;
|
|
175
|
+
for (let bit = 0; bit < 8; bit += 1) crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
|
|
176
|
+
}
|
|
177
|
+
const result = Buffer.allocUnsafe(12 + data.length);
|
|
178
|
+
result.writeUInt32BE(data.length, 0);
|
|
179
|
+
typeBytes.copy(result, 4);
|
|
180
|
+
data.copy(result, 8);
|
|
181
|
+
result.writeUInt32BE((crc ^ 0xffffffff) >>> 0, 8 + data.length);
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function testPng(header: Buffer, pixels: Buffer, beforeIdat: Buffer[] = [], afterIdat: Buffer[] = []): Buffer {
|
|
186
|
+
return Buffer.concat([
|
|
187
|
+
Buffer.from("89504e470d0a1a0a", "hex"),
|
|
188
|
+
testPngChunk("IHDR", header),
|
|
189
|
+
...beforeIdat,
|
|
190
|
+
testPngChunk("IDAT", deflateSync(pixels)),
|
|
191
|
+
...afterIdat,
|
|
192
|
+
testPngChunk("IEND", Buffer.alloc(0)),
|
|
193
|
+
]);
|
|
194
|
+
}
|
|
195
|
+
|
|
168
196
|
describe("Pi domain plugins", () => {
|
|
169
197
|
test("projects session lineage into an active graph without duplicating session history", () => {
|
|
170
198
|
const sessions = [
|
|
@@ -372,125 +400,1512 @@ describe("Pi domain plugins", () => {
|
|
|
372
400
|
read: () => ({ ready: true }),
|
|
373
401
|
});
|
|
374
402
|
|
|
375
|
-
await expect(panels.snapshot()).resolves.toEqual([{ id: "example-panel", pluginId: "example-plugin", title: "Example", data: { ready: true } }]);
|
|
376
|
-
dispose();
|
|
377
|
-
await expect(panels.snapshot()).resolves.toEqual([]);
|
|
403
|
+
await expect(panels.snapshot()).resolves.toEqual([{ id: "example-panel", pluginId: "example-plugin", title: "Example", data: { ready: true } }]);
|
|
404
|
+
dispose();
|
|
405
|
+
await expect(panels.snapshot()).resolves.toEqual([]);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
test("detaches panel data before returning snapshots", async () => {
|
|
409
|
+
const panels = new PiPluginUiRegistry();
|
|
410
|
+
const state = { nested: { value: 1 }, items: ["a"] };
|
|
411
|
+
panels.register({
|
|
412
|
+
id: "detached-panel",
|
|
413
|
+
pluginId: "example-plugin",
|
|
414
|
+
title: "Detached",
|
|
415
|
+
read: () => state,
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
const first = (await panels.snapshot())[0];
|
|
419
|
+
(first?.data as { nested: { value: number }; items: string[] }).nested.value = 99;
|
|
420
|
+
(first?.data as { nested: { value: number }; items: string[] }).items.push("mutated");
|
|
421
|
+
await expect(panels.snapshot()).resolves.toMatchObject([{ data: { nested: { value: 1 }, items: ["a"] } }]);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
test("publishes a live context insight panel from the session runtime", async () => {
|
|
425
|
+
const context = new Context();
|
|
426
|
+
contexts.push(context);
|
|
427
|
+
const panels = new PiPluginUiRegistry();
|
|
428
|
+
const toolRegistry = new PiToolRegistry();
|
|
429
|
+
context.provide("piRuntime", {
|
|
430
|
+
session: { messages: [{ role: "user" }], getContextUsage: () => ({ tokens: 1200, contextWindow: 8000, percent: 15 }) },
|
|
431
|
+
} as never);
|
|
432
|
+
context.provide("piPluginUi", panels);
|
|
433
|
+
context.provide("piTools", toolRegistry);
|
|
434
|
+
|
|
435
|
+
await context.plugin(contextPlugin);
|
|
436
|
+
|
|
437
|
+
context.emit("pi/session-event", { type: "message_start" } as never);
|
|
438
|
+
context.emit("pi/session-event", { type: "compaction_start" } as never);
|
|
439
|
+
await expect(panels.snapshot()).resolves.toMatchObject([
|
|
440
|
+
{
|
|
441
|
+
data: {
|
|
442
|
+
events: 2,
|
|
443
|
+
compactions: 1,
|
|
444
|
+
eventTypes: { message_start: 1, compaction_start: 1 },
|
|
445
|
+
recentEvents: [{ type: "message_start" }, { type: "compaction_start" }],
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
]);
|
|
449
|
+
|
|
450
|
+
await expect(panels.snapshot()).resolves.toEqual([
|
|
451
|
+
{
|
|
452
|
+
id: "context-insight-panel",
|
|
453
|
+
pluginId: "@pi-harness/core/plugins/context",
|
|
454
|
+
title: "上下文洞察",
|
|
455
|
+
description: "查看当前上下文占用、消息规模和压缩事件。",
|
|
456
|
+
icon: "◒",
|
|
457
|
+
data: {
|
|
458
|
+
tokens: 1200,
|
|
459
|
+
contextWindow: 8000,
|
|
460
|
+
percent: 15,
|
|
461
|
+
messages: 1,
|
|
462
|
+
scannedMessages: 1,
|
|
463
|
+
messagesTruncated: false,
|
|
464
|
+
events: 2,
|
|
465
|
+
compactions: 1,
|
|
466
|
+
composition: { user: 1, assistant: 0, toolResult: 0, system: 0, other: 0 },
|
|
467
|
+
eventTypes: { message_start: 1, compaction_start: 1 },
|
|
468
|
+
recentEvents: [expect.objectContaining({ type: "message_start" }), expect.objectContaining({ type: "compaction_start" })],
|
|
469
|
+
limits: { scannedMessages: 10_000, recentEvents: 50, eventTypes: 64, eventTypeCharacters: 128 },
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
]);
|
|
473
|
+
|
|
474
|
+
const inspect = toolRegistry.snapshot().customTools.find((entry) => entry.name === "context_inspect");
|
|
475
|
+
if (inspect === undefined) throw new Error("context_inspect was not registered");
|
|
476
|
+
await expect(inspect.execute("inspect-1", {}, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
477
|
+
details: { composition: { user: 1 }, messages: 1 },
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
test("persists agent team tasks and exposes a live collaboration panel", async () => {
|
|
482
|
+
const context = new Context();
|
|
483
|
+
contexts.push(context);
|
|
484
|
+
const panels = new PiPluginUiRegistry();
|
|
485
|
+
const tools = new PiToolRegistry();
|
|
486
|
+
const entries: unknown[] = [];
|
|
487
|
+
context.provide("piSession", {
|
|
488
|
+
manager: {
|
|
489
|
+
getEntries: () => entries,
|
|
490
|
+
getBranch: () => entries,
|
|
491
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
492
|
+
},
|
|
493
|
+
} as never);
|
|
494
|
+
context.provide("piTools", tools);
|
|
495
|
+
context.provide("piPluginUi", panels);
|
|
496
|
+
|
|
497
|
+
await context.plugin(agentTeamsPlugin);
|
|
498
|
+
const tool = firstTool(tools);
|
|
499
|
+
await expect(
|
|
500
|
+
tool.execute("invalid-status", { action: "add_task", title: "Invalid", status: "almost_done" }, undefined, undefined, {} as never),
|
|
501
|
+
).rejects.toThrow(/task status/iu);
|
|
502
|
+
await expect(tool.execute("invalid-member", { action: "add_member", name: "审阅者" }, undefined, undefined, {} as never)).rejects.toThrow(/member id/iu);
|
|
503
|
+
await expect(
|
|
504
|
+
tool.execute("invalid-member-status", { action: "add_member", id: "observer", name: "Observer", status: "offline" }, undefined, undefined, {} as never),
|
|
505
|
+
).rejects.toThrow(/member status/iu);
|
|
506
|
+
await expect(tool.execute("long-title", { action: "add_task", title: "x".repeat(201) }, undefined, undefined, {} as never)).rejects.toThrow(/title.*200/iu);
|
|
507
|
+
expect(entries).toEqual([]);
|
|
508
|
+
await expect(tool.execute("call-1", { action: "add_task", title: "Review plugin manifest" }, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
509
|
+
content: [{ text: "Task task-1 created." }],
|
|
510
|
+
});
|
|
511
|
+
await expect(
|
|
512
|
+
tool.execute("call-2", { action: "add_task", title: "Run integration checks", dependsOn: ["task-1"] }, undefined, undefined, {} as never),
|
|
513
|
+
).resolves.toMatchObject({ details: { item: { id: "task-2", status: "blocked", dependsOn: ["task-1"] } } });
|
|
514
|
+
await expect(tool.execute("cycle", { action: "update_task", id: "task-1", dependsOn: ["task-2"] }, undefined, undefined, {} as never)).rejects.toThrow(
|
|
515
|
+
/dependency cycle/iu,
|
|
516
|
+
);
|
|
517
|
+
await expect(tool.execute("call-3", { action: "update_task", id: "task-1", status: "done" }, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
518
|
+
details: { item: { id: "task-1", status: "done" } },
|
|
519
|
+
});
|
|
520
|
+
await expect(panels.snapshot()).resolves.toMatchObject([
|
|
521
|
+
{ id: "agent-teams-panel", data: { tasks: [{ status: "done" }, { id: "task-2", status: "todo" }] } },
|
|
522
|
+
]);
|
|
523
|
+
await expect(tool.execute("call-4", { action: "claim_task", assignee: "builder" }, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
524
|
+
details: { item: { id: "task-2", status: "in_progress", assignee: "builder" } },
|
|
525
|
+
});
|
|
526
|
+
await expect(
|
|
527
|
+
tool.execute("call-5", { action: "send_message", from: "builder", to: "reviewer", body: "实现已完成,请开始复核" }, undefined, undefined, {} as never),
|
|
528
|
+
).resolves.toMatchObject({ details: { item: { from: "builder", to: "reviewer", body: "实现已完成,请开始复核", read: false } } });
|
|
529
|
+
await expect(tool.execute("call-6", { action: "read_messages", to: "reviewer" }, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
530
|
+
details: { messages: [{ from: "builder", to: "reviewer", body: "实现已完成,请开始复核", read: true }] },
|
|
531
|
+
});
|
|
532
|
+
await expect(
|
|
533
|
+
tool.execute("call-7", { action: "read_messages", to: "reviewer", unreadOnly: true }, undefined, undefined, {} as never),
|
|
534
|
+
).resolves.toMatchObject({
|
|
535
|
+
details: { messages: [] },
|
|
536
|
+
});
|
|
537
|
+
await expect(
|
|
538
|
+
tool.execute("call-8", { action: "add_member", id: "observer", name: "观察员", role: "发布观察", status: "idle" }, undefined, undefined, {} as never),
|
|
539
|
+
).resolves.toMatchObject({ details: { kind: "member", item: { id: "observer", name: "观察员", role: "发布观察", status: "idle" } } });
|
|
540
|
+
await expect(panels.snapshot()).resolves.toMatchObject([
|
|
541
|
+
{
|
|
542
|
+
id: "agent-teams-panel",
|
|
543
|
+
data: {
|
|
544
|
+
tasks: [
|
|
545
|
+
{ title: "Review plugin manifest", status: "done" },
|
|
546
|
+
{ title: "Run integration checks", status: "in_progress" },
|
|
547
|
+
],
|
|
548
|
+
},
|
|
549
|
+
},
|
|
550
|
+
]);
|
|
551
|
+
const panel = (await panels.snapshot())[0];
|
|
552
|
+
if (panel === undefined) throw new Error("agent-teams-panel was not registered");
|
|
553
|
+
const members = (panel.data as { members: { id: string; status: string }[] }).members;
|
|
554
|
+
expect(members.find((member) => member.id === "observer")).toMatchObject({ status: "idle" });
|
|
555
|
+
expect(members.find((member) => member.id === "builder")).toMatchObject({
|
|
556
|
+
status: "working",
|
|
557
|
+
});
|
|
558
|
+
expect(entries).toHaveLength(7);
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
test("publishes agent team input bounds in the tool parameter schema", async () => {
|
|
562
|
+
const context = new Context();
|
|
563
|
+
contexts.push(context);
|
|
564
|
+
context.provide("piSession", { manager: { getEntries: () => [], getBranch: () => [], appendCustomEntry() {} } } as never);
|
|
565
|
+
const tools = new PiToolRegistry();
|
|
566
|
+
context.provide("piTools", tools);
|
|
567
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
568
|
+
await context.plugin(agentTeamsPlugin);
|
|
569
|
+
|
|
570
|
+
const idPattern = "^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$";
|
|
571
|
+
expect(namedTool(tools, "team_task").parameters).toMatchObject({
|
|
572
|
+
properties: {
|
|
573
|
+
action: {
|
|
574
|
+
anyOf: [
|
|
575
|
+
{ const: "add_task" },
|
|
576
|
+
{ const: "update_task" },
|
|
577
|
+
{ const: "claim_task" },
|
|
578
|
+
{ const: "remove_task" },
|
|
579
|
+
{ const: "add_member" },
|
|
580
|
+
{ const: "remove_member" },
|
|
581
|
+
{ const: "send_message" },
|
|
582
|
+
{ const: "read_messages" },
|
|
583
|
+
{ const: "clear_messages" },
|
|
584
|
+
{ const: "get_state" },
|
|
585
|
+
],
|
|
586
|
+
},
|
|
587
|
+
id: { maxLength: 64, pattern: idPattern },
|
|
588
|
+
title: { maxLength: 200 },
|
|
589
|
+
assignee: { maxLength: 64, pattern: idPattern },
|
|
590
|
+
name: { maxLength: 200 },
|
|
591
|
+
role: { maxLength: 200 },
|
|
592
|
+
status: {
|
|
593
|
+
anyOf: [{ const: "todo" }, { const: "blocked" }, { const: "in_progress" }, { const: "done" }, { const: "idle" }, { const: "working" }],
|
|
594
|
+
},
|
|
595
|
+
dependsOn: { maxItems: 256, items: { maxLength: 64, pattern: idPattern } },
|
|
596
|
+
from: { maxLength: 64, pattern: idPattern },
|
|
597
|
+
to: { maxLength: 64, pattern: idPattern },
|
|
598
|
+
body: { maxLength: 4_000 },
|
|
599
|
+
confirm: { type: "boolean" },
|
|
600
|
+
},
|
|
601
|
+
additionalProperties: false,
|
|
602
|
+
});
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
test("updates all mutable agent team task fields and recalculates readiness", async () => {
|
|
606
|
+
const context = new Context();
|
|
607
|
+
contexts.push(context);
|
|
608
|
+
const entries: unknown[] = [];
|
|
609
|
+
context.provide("piSession", {
|
|
610
|
+
manager: {
|
|
611
|
+
getEntries: () => entries,
|
|
612
|
+
getBranch: () => entries,
|
|
613
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
614
|
+
},
|
|
615
|
+
} as never);
|
|
616
|
+
const tools = new PiToolRegistry();
|
|
617
|
+
context.provide("piTools", tools);
|
|
618
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
619
|
+
await context.plugin(agentTeamsPlugin);
|
|
620
|
+
const tool = namedTool(tools, "team_task");
|
|
621
|
+
await tool.execute("dependency", { action: "add_task", title: "Dependency" }, undefined, undefined, {} as never);
|
|
622
|
+
await tool.execute("target", { action: "add_task", title: "Original" }, undefined, undefined, {} as never);
|
|
623
|
+
|
|
624
|
+
await expect(
|
|
625
|
+
tool.execute(
|
|
626
|
+
"update",
|
|
627
|
+
{ action: "update_task", id: "task-2", title: "Updated", assignee: "reviewer", status: "todo", dependsOn: [" task-1 ", "task-1"] },
|
|
628
|
+
undefined,
|
|
629
|
+
undefined,
|
|
630
|
+
{} as never,
|
|
631
|
+
),
|
|
632
|
+
).resolves.toMatchObject({
|
|
633
|
+
details: { item: { id: "task-2", title: "Updated", assignee: "reviewer", status: "blocked", dependsOn: ["task-1"] } },
|
|
634
|
+
});
|
|
635
|
+
expect(entries).toHaveLength(3);
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
test("rejects invalid agent team actions without persisting partial state", async () => {
|
|
639
|
+
const context = new Context();
|
|
640
|
+
contexts.push(context);
|
|
641
|
+
const entries: unknown[] = [];
|
|
642
|
+
context.provide("piSession", {
|
|
643
|
+
manager: {
|
|
644
|
+
getEntries: () => entries,
|
|
645
|
+
getBranch: () => entries,
|
|
646
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
647
|
+
},
|
|
648
|
+
} as never);
|
|
649
|
+
const tools = new PiToolRegistry();
|
|
650
|
+
context.provide("piTools", tools);
|
|
651
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
652
|
+
await context.plugin(agentTeamsPlugin);
|
|
653
|
+
const tool = namedTool(tools, "team_task");
|
|
654
|
+
|
|
655
|
+
await expect(tool.execute("missing-title", { action: "add_task" }, undefined, undefined, {} as never)).rejects.toThrow(/task title/iu);
|
|
656
|
+
await expect(
|
|
657
|
+
tool.execute("self-dependency", { action: "add_task", id: "self", title: "Self", dependsOn: ["self"] }, undefined, undefined, {} as never),
|
|
658
|
+
).rejects.toThrow(/depend on itself/iu);
|
|
659
|
+
await expect(
|
|
660
|
+
tool.execute("missing-dependency", { action: "add_task", title: "Missing dependency", dependsOn: ["missing"] }, undefined, undefined, {} as never),
|
|
661
|
+
).rejects.toThrow(/dependencies must already exist/iu);
|
|
662
|
+
await expect(tool.execute("missing-task", { action: "update_task", id: "missing" }, undefined, undefined, {} as never)).rejects.toThrow(/task not found/iu);
|
|
663
|
+
await expect(tool.execute("no-ready-task", { action: "claim_task", assignee: "builder" }, undefined, undefined, {} as never)).rejects.toThrow(/no ready/iu);
|
|
664
|
+
await expect(tool.execute("missing-name", { action: "add_member", id: "missing-name" }, undefined, undefined, {} as never)).rejects.toThrow(
|
|
665
|
+
/member name/iu,
|
|
666
|
+
);
|
|
667
|
+
await expect(
|
|
668
|
+
tool.execute("duplicate-member", { action: "add_member", id: "planner", name: "Duplicate" }, undefined, undefined, {} as never),
|
|
669
|
+
).rejects.toThrow(/member already exists/iu);
|
|
670
|
+
await expect(
|
|
671
|
+
tool.execute("unknown-sender", { action: "send_message", from: "missing", to: "planner", body: "Hello" }, undefined, undefined, {} as never),
|
|
672
|
+
).rejects.toThrow(/unknown sender/iu);
|
|
673
|
+
await expect(
|
|
674
|
+
tool.execute("unknown-recipient", { action: "send_message", from: "planner", to: "missing", body: "Hello" }, undefined, undefined, {} as never),
|
|
675
|
+
).rejects.toThrow(/unknown recipient/iu);
|
|
676
|
+
await expect(
|
|
677
|
+
tool.execute("oversized-message", { action: "send_message", from: "planner", to: "builder", body: "x".repeat(4_001) }, undefined, undefined, {} as never),
|
|
678
|
+
).rejects.toThrow(/4000 characters/iu);
|
|
679
|
+
await expect(tool.execute("unknown-mailbox", { action: "read_messages", to: "missing" }, undefined, undefined, {} as never)).rejects.toThrow(
|
|
680
|
+
/unknown recipient/iu,
|
|
681
|
+
);
|
|
682
|
+
await expect(tool.execute("unknown-action", { action: "archive_task" }, undefined, undefined, {} as never)).rejects.toThrow(/unknown team action/iu);
|
|
683
|
+
expect(entries).toEqual([]);
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
test("unregisters the agent team tool and panel when the plugin context is disposed", async () => {
|
|
687
|
+
const context = new Context();
|
|
688
|
+
contexts.push(context);
|
|
689
|
+
context.provide("piSession", { manager: { getEntries: () => [], getBranch: () => [], appendCustomEntry() {} } } as never);
|
|
690
|
+
const tools = new PiToolRegistry();
|
|
691
|
+
const panels = new PiPluginUiRegistry();
|
|
692
|
+
context.provide("piTools", tools);
|
|
693
|
+
context.provide("piPluginUi", panels);
|
|
694
|
+
await context.plugin(agentTeamsPlugin);
|
|
695
|
+
expect(tools.snapshot().customTools.map((tool) => tool.name)).toEqual(["team_task"]);
|
|
696
|
+
await expect(panels.snapshot()).resolves.toMatchObject([{ id: "agent-teams-panel" }]);
|
|
697
|
+
|
|
698
|
+
await context.fiber.dispose();
|
|
699
|
+
|
|
700
|
+
expect(tools.snapshot().customTools).toEqual([]);
|
|
701
|
+
await expect(panels.snapshot()).resolves.toEqual([]);
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
test("recovers agent team operations from malformed persisted members", async () => {
|
|
705
|
+
const context = new Context();
|
|
706
|
+
contexts.push(context);
|
|
707
|
+
const entries: unknown[] = [{ type: "custom", customType: "pi-harness/agent-teams", data: { members: [null], tasks: [], messages: [] } }];
|
|
708
|
+
context.provide("piSession", {
|
|
709
|
+
manager: {
|
|
710
|
+
getEntries: () => entries,
|
|
711
|
+
getBranch: () => entries,
|
|
712
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
713
|
+
},
|
|
714
|
+
} as never);
|
|
715
|
+
const tools = new PiToolRegistry();
|
|
716
|
+
const panels = new PiPluginUiRegistry();
|
|
717
|
+
context.provide("piTools", tools);
|
|
718
|
+
context.provide("piPluginUi", panels);
|
|
719
|
+
await context.plugin(agentTeamsPlugin);
|
|
720
|
+
|
|
721
|
+
await expect(
|
|
722
|
+
namedTool(tools, "team_task").execute("call-1", { action: "add_task", title: "Recover safely" }, undefined, undefined, {} as never),
|
|
723
|
+
).resolves.toMatchObject({
|
|
724
|
+
details: { item: { id: "task-1", status: "todo" } },
|
|
725
|
+
});
|
|
726
|
+
await expect(panels.snapshot()).resolves.toMatchObject([
|
|
727
|
+
{ id: "agent-teams-panel", data: { members: [{ id: "planner" }, { id: "builder" }, { id: "reviewer" }] } },
|
|
728
|
+
]);
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
test("restores the default role for a persisted agent team member", async () => {
|
|
732
|
+
const context = new Context();
|
|
733
|
+
contexts.push(context);
|
|
734
|
+
const entries: unknown[] = [
|
|
735
|
+
{ type: "custom", customType: "pi-harness/agent-teams", data: { members: [{ id: "observer", name: "Observer" }], tasks: [], messages: [] } },
|
|
736
|
+
];
|
|
737
|
+
context.provide("piSession", {
|
|
738
|
+
manager: {
|
|
739
|
+
getEntries: () => entries,
|
|
740
|
+
getBranch: () => entries,
|
|
741
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
742
|
+
},
|
|
743
|
+
} as never);
|
|
744
|
+
const panels = new PiPluginUiRegistry();
|
|
745
|
+
context.provide("piTools", new PiToolRegistry());
|
|
746
|
+
context.provide("piPluginUi", panels);
|
|
747
|
+
await context.plugin(agentTeamsPlugin);
|
|
748
|
+
|
|
749
|
+
await expect(panels.snapshot()).resolves.toMatchObject([{ data: { members: [{ id: "observer", name: "Observer", role: "协作成员", status: "idle" }] } }]);
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
test("recovers agent team state from a malformed persisted root value", async () => {
|
|
753
|
+
const context = new Context();
|
|
754
|
+
contexts.push(context);
|
|
755
|
+
const entries: unknown[] = [{ type: "custom", customType: "pi-harness/agent-teams", data: null }];
|
|
756
|
+
context.provide("piSession", {
|
|
757
|
+
manager: {
|
|
758
|
+
getEntries: () => entries,
|
|
759
|
+
getBranch: () => entries,
|
|
760
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
761
|
+
},
|
|
762
|
+
} as never);
|
|
763
|
+
const panels = new PiPluginUiRegistry();
|
|
764
|
+
context.provide("piTools", new PiToolRegistry());
|
|
765
|
+
context.provide("piPluginUi", panels);
|
|
766
|
+
await context.plugin(agentTeamsPlugin);
|
|
767
|
+
|
|
768
|
+
await expect(panels.snapshot()).resolves.toMatchObject([
|
|
769
|
+
{ data: { members: [{ id: "planner" }, { id: "builder" }, { id: "reviewer" }], tasks: [], messages: [] } },
|
|
770
|
+
]);
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
test("falls back to the latest valid agent team snapshot when the newest root is malformed", async () => {
|
|
774
|
+
const context = new Context();
|
|
775
|
+
contexts.push(context);
|
|
776
|
+
const entries: unknown[] = [
|
|
777
|
+
{
|
|
778
|
+
type: "custom",
|
|
779
|
+
customType: "pi-harness/agent-teams",
|
|
780
|
+
data: {
|
|
781
|
+
members: [{ id: "observer", name: "Observer", role: "Observe", status: "idle" }],
|
|
782
|
+
tasks: [{ id: "task-1", title: "Preserved", assignee: "observer", status: "todo", dependsOn: [] }],
|
|
783
|
+
messages: [],
|
|
784
|
+
},
|
|
785
|
+
},
|
|
786
|
+
{ type: "custom", customType: "pi-harness/agent-teams", data: null },
|
|
787
|
+
];
|
|
788
|
+
context.provide("piSession", {
|
|
789
|
+
manager: {
|
|
790
|
+
getEntries: () => entries,
|
|
791
|
+
getBranch: () => entries,
|
|
792
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
793
|
+
},
|
|
794
|
+
} as never);
|
|
795
|
+
const panels = new PiPluginUiRegistry();
|
|
796
|
+
context.provide("piTools", new PiToolRegistry());
|
|
797
|
+
context.provide("piPluginUi", panels);
|
|
798
|
+
await context.plugin(agentTeamsPlugin);
|
|
799
|
+
|
|
800
|
+
await expect(panels.snapshot()).resolves.toMatchObject([{ data: { members: [{ id: "observer" }], tasks: [{ id: "task-1", title: "Preserved" }] } }]);
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
test("deduplicates agent team identities loaded from persisted session state", async () => {
|
|
804
|
+
const context = new Context();
|
|
805
|
+
contexts.push(context);
|
|
806
|
+
const entries: unknown[] = [
|
|
807
|
+
{
|
|
808
|
+
type: "custom",
|
|
809
|
+
customType: "pi-harness/agent-teams",
|
|
810
|
+
data: {
|
|
811
|
+
members: [
|
|
812
|
+
{ id: "builder", name: "Builder", role: "Build", status: "idle" },
|
|
813
|
+
{ id: "builder", name: "Duplicate", role: "Duplicate", status: "working" },
|
|
814
|
+
],
|
|
815
|
+
tasks: [
|
|
816
|
+
{ id: "task-1", title: "First", assignee: "builder", status: "todo", dependsOn: [] },
|
|
817
|
+
{ id: "task-1", title: "Duplicate", assignee: "builder", status: "done", dependsOn: [] },
|
|
818
|
+
],
|
|
819
|
+
messages: [
|
|
820
|
+
{ id: "message-1", from: "builder", to: "builder", body: "First", timestamp: "2026-09-05T00:00:00.000Z", read: false },
|
|
821
|
+
{ id: "message-1", from: "builder", to: "builder", body: "Duplicate", timestamp: "2026-09-05T00:00:01.000Z", read: false },
|
|
822
|
+
],
|
|
823
|
+
},
|
|
824
|
+
},
|
|
825
|
+
];
|
|
826
|
+
context.provide("piSession", {
|
|
827
|
+
manager: {
|
|
828
|
+
getEntries: () => entries,
|
|
829
|
+
getBranch: () => entries,
|
|
830
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
831
|
+
},
|
|
832
|
+
} as never);
|
|
833
|
+
const panels = new PiPluginUiRegistry();
|
|
834
|
+
context.provide("piTools", new PiToolRegistry());
|
|
835
|
+
context.provide("piPluginUi", panels);
|
|
836
|
+
await context.plugin(agentTeamsPlugin);
|
|
837
|
+
|
|
838
|
+
await expect(panels.snapshot()).resolves.toMatchObject([
|
|
839
|
+
{
|
|
840
|
+
data: {
|
|
841
|
+
members: [{ id: "builder", name: "Builder" }],
|
|
842
|
+
tasks: [{ id: "task-1", title: "First" }],
|
|
843
|
+
messages: [{ id: "message-1", body: "First" }],
|
|
844
|
+
},
|
|
845
|
+
},
|
|
846
|
+
]);
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
test("repairs dangling agent team references loaded from persisted session state", async () => {
|
|
850
|
+
const context = new Context();
|
|
851
|
+
contexts.push(context);
|
|
852
|
+
const entries: unknown[] = [
|
|
853
|
+
{
|
|
854
|
+
type: "custom",
|
|
855
|
+
customType: "pi-harness/agent-teams",
|
|
856
|
+
data: {
|
|
857
|
+
members: [{ id: "builder", name: "Builder", role: "Build", status: "idle" }],
|
|
858
|
+
tasks: [{ id: "task-1", title: "Recovered", assignee: "missing-member", status: "todo", dependsOn: [] }],
|
|
859
|
+
messages: [
|
|
860
|
+
{ id: "message-1", from: "missing-member", to: "builder", body: "Ghost", timestamp: "2026-09-05T00:00:00.000Z", read: false },
|
|
861
|
+
{ id: "message-2", from: "builder", to: "builder", body: "Keep", timestamp: "2026-09-05T00:00:01.000Z", read: false },
|
|
862
|
+
],
|
|
863
|
+
},
|
|
864
|
+
},
|
|
865
|
+
];
|
|
866
|
+
context.provide("piSession", {
|
|
867
|
+
manager: {
|
|
868
|
+
getEntries: () => entries,
|
|
869
|
+
getBranch: () => entries,
|
|
870
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
871
|
+
},
|
|
872
|
+
} as never);
|
|
873
|
+
const panels = new PiPluginUiRegistry();
|
|
874
|
+
context.provide("piTools", new PiToolRegistry());
|
|
875
|
+
context.provide("piPluginUi", panels);
|
|
876
|
+
await context.plugin(agentTeamsPlugin);
|
|
877
|
+
|
|
878
|
+
await expect(panels.snapshot()).resolves.toMatchObject([
|
|
879
|
+
{
|
|
880
|
+
data: {
|
|
881
|
+
tasks: [{ id: "task-1", assignee: "unassigned" }],
|
|
882
|
+
messages: [{ id: "message-2", from: "builder", to: "builder", body: "Keep" }],
|
|
883
|
+
},
|
|
884
|
+
},
|
|
885
|
+
]);
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
test("requeues an in-progress agent team task whose persisted assignee is missing", async () => {
|
|
889
|
+
const context = new Context();
|
|
890
|
+
contexts.push(context);
|
|
891
|
+
const entries: unknown[] = [
|
|
892
|
+
{
|
|
893
|
+
type: "custom",
|
|
894
|
+
customType: "pi-harness/agent-teams",
|
|
895
|
+
data: {
|
|
896
|
+
members: [{ id: "builder", name: "Builder", role: "Build", status: "idle" }],
|
|
897
|
+
tasks: [{ id: "task-1", title: "Recover", assignee: "missing-member", status: "in_progress", dependsOn: [] }],
|
|
898
|
+
messages: [],
|
|
899
|
+
},
|
|
900
|
+
},
|
|
901
|
+
];
|
|
902
|
+
context.provide("piSession", {
|
|
903
|
+
manager: {
|
|
904
|
+
getEntries: () => entries,
|
|
905
|
+
getBranch: () => entries,
|
|
906
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
907
|
+
},
|
|
908
|
+
} as never);
|
|
909
|
+
const panels = new PiPluginUiRegistry();
|
|
910
|
+
context.provide("piTools", new PiToolRegistry());
|
|
911
|
+
context.provide("piPluginUi", panels);
|
|
912
|
+
await context.plugin(agentTeamsPlugin);
|
|
913
|
+
|
|
914
|
+
await expect(panels.snapshot()).resolves.toMatchObject([{ data: { tasks: [{ id: "task-1", assignee: "unassigned", status: "todo" }] } }]);
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
test("reconciles agent team member statuses with persisted in-progress tasks", async () => {
|
|
918
|
+
const context = new Context();
|
|
919
|
+
contexts.push(context);
|
|
920
|
+
const entries: unknown[] = [
|
|
921
|
+
{
|
|
922
|
+
type: "custom",
|
|
923
|
+
customType: "pi-harness/agent-teams",
|
|
924
|
+
data: {
|
|
925
|
+
members: [
|
|
926
|
+
{ id: "builder", name: "Builder", role: "Build", status: "working" },
|
|
927
|
+
{ id: "reviewer", name: "Reviewer", role: "Review", status: "idle" },
|
|
928
|
+
],
|
|
929
|
+
tasks: [{ id: "task-1", title: "Review", assignee: "reviewer", status: "in_progress", dependsOn: [] }],
|
|
930
|
+
messages: [],
|
|
931
|
+
},
|
|
932
|
+
},
|
|
933
|
+
];
|
|
934
|
+
context.provide("piSession", {
|
|
935
|
+
manager: {
|
|
936
|
+
getEntries: () => entries,
|
|
937
|
+
getBranch: () => entries,
|
|
938
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
939
|
+
},
|
|
940
|
+
} as never);
|
|
941
|
+
const panels = new PiPluginUiRegistry();
|
|
942
|
+
context.provide("piTools", new PiToolRegistry());
|
|
943
|
+
context.provide("piPluginUi", panels);
|
|
944
|
+
await context.plugin(agentTeamsPlugin);
|
|
945
|
+
|
|
946
|
+
await expect(panels.snapshot()).resolves.toMatchObject([
|
|
947
|
+
{
|
|
948
|
+
data: {
|
|
949
|
+
members: [
|
|
950
|
+
{ id: "builder", status: "idle" },
|
|
951
|
+
{ id: "reviewer", status: "working" },
|
|
952
|
+
],
|
|
953
|
+
},
|
|
954
|
+
},
|
|
955
|
+
]);
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
test("normalizes whitespace around persisted agent team statuses", async () => {
|
|
959
|
+
const context = new Context();
|
|
960
|
+
contexts.push(context);
|
|
961
|
+
const entries: unknown[] = [
|
|
962
|
+
{
|
|
963
|
+
type: "custom",
|
|
964
|
+
customType: "pi-harness/agent-teams",
|
|
965
|
+
data: {
|
|
966
|
+
members: [{ id: "builder", name: "Builder", role: "Build", status: " working " }],
|
|
967
|
+
tasks: [{ id: "task-1", title: "Active", assignee: "builder", status: " in_progress ", dependsOn: [] }],
|
|
968
|
+
messages: [],
|
|
969
|
+
},
|
|
970
|
+
},
|
|
971
|
+
];
|
|
972
|
+
context.provide("piSession", {
|
|
973
|
+
manager: {
|
|
974
|
+
getEntries: () => entries,
|
|
975
|
+
getBranch: () => entries,
|
|
976
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
977
|
+
},
|
|
978
|
+
} as never);
|
|
979
|
+
const panels = new PiPluginUiRegistry();
|
|
980
|
+
context.provide("piTools", new PiToolRegistry());
|
|
981
|
+
context.provide("piPluginUi", panels);
|
|
982
|
+
await context.plugin(agentTeamsPlugin);
|
|
983
|
+
|
|
984
|
+
await expect(panels.snapshot()).resolves.toMatchObject([
|
|
985
|
+
{ data: { members: [{ id: "builder", status: "working" }], tasks: [{ id: "task-1", status: "in_progress" }] } },
|
|
986
|
+
]);
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
test("reconciles persisted agent team task readiness before rendering state", async () => {
|
|
990
|
+
const context = new Context();
|
|
991
|
+
contexts.push(context);
|
|
992
|
+
const entries: unknown[] = [
|
|
993
|
+
{
|
|
994
|
+
type: "custom",
|
|
995
|
+
customType: "pi-harness/agent-teams",
|
|
996
|
+
data: {
|
|
997
|
+
members: [{ id: "builder", name: "Builder", role: "Build", status: "idle" }],
|
|
998
|
+
tasks: [
|
|
999
|
+
{ id: "waiting", title: "Waiting", assignee: "builder", status: "todo", dependsOn: ["dependency"] },
|
|
1000
|
+
{ id: "ready", title: "Ready", assignee: "builder", status: "blocked", dependsOn: [] },
|
|
1001
|
+
],
|
|
1002
|
+
messages: [],
|
|
1003
|
+
},
|
|
1004
|
+
},
|
|
1005
|
+
];
|
|
1006
|
+
context.provide("piSession", {
|
|
1007
|
+
manager: {
|
|
1008
|
+
getEntries: () => entries,
|
|
1009
|
+
getBranch: () => entries,
|
|
1010
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1011
|
+
},
|
|
1012
|
+
} as never);
|
|
1013
|
+
const panels = new PiPluginUiRegistry();
|
|
1014
|
+
context.provide("piTools", new PiToolRegistry());
|
|
1015
|
+
context.provide("piPluginUi", panels);
|
|
1016
|
+
await context.plugin(agentTeamsPlugin);
|
|
1017
|
+
|
|
1018
|
+
await expect(panels.snapshot()).resolves.toMatchObject([
|
|
1019
|
+
{
|
|
1020
|
+
data: {
|
|
1021
|
+
tasks: [
|
|
1022
|
+
{ id: "waiting", status: "blocked" },
|
|
1023
|
+
{ id: "ready", status: "todo" },
|
|
1024
|
+
],
|
|
1025
|
+
readyTasks: ["ready"],
|
|
1026
|
+
},
|
|
1027
|
+
},
|
|
1028
|
+
]);
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
test("reopens a persisted completed agent team task whose dependency is incomplete", async () => {
|
|
1032
|
+
const context = new Context();
|
|
1033
|
+
contexts.push(context);
|
|
1034
|
+
const entries: unknown[] = [
|
|
1035
|
+
{
|
|
1036
|
+
type: "custom",
|
|
1037
|
+
customType: "pi-harness/agent-teams",
|
|
1038
|
+
data: {
|
|
1039
|
+
members: [{ id: "builder", name: "Builder", role: "Build", status: "idle" }],
|
|
1040
|
+
tasks: [
|
|
1041
|
+
{ id: "dependency", title: "Dependency", assignee: "builder", status: "todo", dependsOn: [] },
|
|
1042
|
+
{ id: "dependent", title: "Dependent", assignee: "builder", status: "done", dependsOn: ["dependency"] },
|
|
1043
|
+
],
|
|
1044
|
+
messages: [],
|
|
1045
|
+
},
|
|
1046
|
+
},
|
|
1047
|
+
];
|
|
1048
|
+
context.provide("piSession", {
|
|
1049
|
+
manager: {
|
|
1050
|
+
getEntries: () => entries,
|
|
1051
|
+
getBranch: () => entries,
|
|
1052
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1053
|
+
},
|
|
1054
|
+
} as never);
|
|
1055
|
+
const panels = new PiPluginUiRegistry();
|
|
1056
|
+
context.provide("piTools", new PiToolRegistry());
|
|
1057
|
+
context.provide("piPluginUi", panels);
|
|
1058
|
+
await context.plugin(agentTeamsPlugin);
|
|
1059
|
+
|
|
1060
|
+
await expect(panels.snapshot()).resolves.toMatchObject([
|
|
1061
|
+
{
|
|
1062
|
+
data: {
|
|
1063
|
+
tasks: [
|
|
1064
|
+
{ id: "dependency", status: "todo" },
|
|
1065
|
+
{ id: "dependent", status: "blocked" },
|
|
1066
|
+
],
|
|
1067
|
+
},
|
|
1068
|
+
},
|
|
1069
|
+
]);
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
test("bounds agent team text loaded from persisted session state", async () => {
|
|
1073
|
+
const context = new Context();
|
|
1074
|
+
contexts.push(context);
|
|
1075
|
+
const entries: unknown[] = [
|
|
1076
|
+
{
|
|
1077
|
+
type: "custom",
|
|
1078
|
+
customType: "pi-harness/agent-teams",
|
|
1079
|
+
data: {
|
|
1080
|
+
members: [{ id: "builder", name: ` ${"n".repeat(201)} `, role: ` ${"r".repeat(201)} `, status: "idle" }],
|
|
1081
|
+
tasks: [{ id: "task-1", title: ` ${"t".repeat(201)} `, assignee: "builder", status: "todo", dependsOn: [] }],
|
|
1082
|
+
messages: [{ id: "message-1", from: "builder", to: "builder", body: ` ${"b".repeat(4_001)} `, timestamp: "2026-09-05T00:00:00.000Z", read: false }],
|
|
1083
|
+
},
|
|
1084
|
+
},
|
|
1085
|
+
];
|
|
1086
|
+
context.provide("piSession", {
|
|
1087
|
+
manager: {
|
|
1088
|
+
getEntries: () => entries,
|
|
1089
|
+
getBranch: () => entries,
|
|
1090
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1091
|
+
},
|
|
1092
|
+
} as never);
|
|
1093
|
+
const panels = new PiPluginUiRegistry();
|
|
1094
|
+
context.provide("piTools", new PiToolRegistry());
|
|
1095
|
+
context.provide("piPluginUi", panels);
|
|
1096
|
+
await context.plugin(agentTeamsPlugin);
|
|
1097
|
+
|
|
1098
|
+
const panel = (await panels.snapshot())[0];
|
|
1099
|
+
if (panel === undefined) throw new Error("agent-teams-panel was not registered");
|
|
1100
|
+
const state = panel.data as { members: Array<{ name: string; role: string }>; tasks: Array<{ title: string }>; messages: Array<{ body: string }> };
|
|
1101
|
+
expect(state.members[0]).toMatchObject({ name: "n".repeat(200), role: "r".repeat(200) });
|
|
1102
|
+
expect(state.tasks[0]?.title).toBe("t".repeat(200));
|
|
1103
|
+
expect(state.messages[0]?.body).toBe("b".repeat(4_000));
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1106
|
+
test("discards invalid agent team ids loaded from persisted session state", async () => {
|
|
1107
|
+
const context = new Context();
|
|
1108
|
+
contexts.push(context);
|
|
1109
|
+
const entries: unknown[] = [
|
|
1110
|
+
{
|
|
1111
|
+
type: "custom",
|
|
1112
|
+
customType: "pi-harness/agent-teams",
|
|
1113
|
+
data: {
|
|
1114
|
+
members: [
|
|
1115
|
+
{ id: "builder", name: "Builder", role: "Build", status: "idle" },
|
|
1116
|
+
{ id: "bad member", name: "Invalid", role: "Invalid", status: "idle" },
|
|
1117
|
+
],
|
|
1118
|
+
tasks: [
|
|
1119
|
+
{ id: "task-1", title: "Keep", assignee: "builder", status: "todo", dependsOn: [] },
|
|
1120
|
+
{ id: "t".repeat(65), title: "Invalid", assignee: "builder", status: "todo", dependsOn: [] },
|
|
1121
|
+
],
|
|
1122
|
+
messages: [
|
|
1123
|
+
{ id: "message-1", from: "builder", to: "builder", body: "Keep", timestamp: "2026-09-05T00:00:00.000Z", read: false },
|
|
1124
|
+
{ id: "bad message", from: "builder", to: "builder", body: "Invalid", timestamp: "2026-09-05T00:00:01.000Z", read: false },
|
|
1125
|
+
],
|
|
1126
|
+
},
|
|
1127
|
+
},
|
|
1128
|
+
];
|
|
1129
|
+
context.provide("piSession", {
|
|
1130
|
+
manager: {
|
|
1131
|
+
getEntries: () => entries,
|
|
1132
|
+
getBranch: () => entries,
|
|
1133
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1134
|
+
},
|
|
1135
|
+
} as never);
|
|
1136
|
+
const panels = new PiPluginUiRegistry();
|
|
1137
|
+
context.provide("piTools", new PiToolRegistry());
|
|
1138
|
+
context.provide("piPluginUi", panels);
|
|
1139
|
+
await context.plugin(agentTeamsPlugin);
|
|
1140
|
+
|
|
1141
|
+
await expect(panels.snapshot()).resolves.toMatchObject([
|
|
1142
|
+
{
|
|
1143
|
+
data: {
|
|
1144
|
+
members: [{ id: "builder" }],
|
|
1145
|
+
tasks: [{ id: "task-1" }],
|
|
1146
|
+
messages: [{ id: "message-1" }],
|
|
1147
|
+
},
|
|
1148
|
+
},
|
|
1149
|
+
]);
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
test("bounds and sanitizes agent team dependencies loaded from persisted session state", async () => {
|
|
1153
|
+
const context = new Context();
|
|
1154
|
+
contexts.push(context);
|
|
1155
|
+
const dependencies = [" bad reference ", ...Array.from({ length: 257 }, (_, index) => `dependency-${index}`), "dependency-0"];
|
|
1156
|
+
const entries: unknown[] = [
|
|
1157
|
+
{
|
|
1158
|
+
type: "custom",
|
|
1159
|
+
customType: "pi-harness/agent-teams",
|
|
1160
|
+
data: {
|
|
1161
|
+
members: [{ id: "builder", name: "Builder", role: "Build", status: "idle" }],
|
|
1162
|
+
tasks: [{ id: "task-1", title: "Recovered", assignee: "builder", status: "todo", dependsOn: dependencies }],
|
|
1163
|
+
messages: [],
|
|
1164
|
+
},
|
|
1165
|
+
},
|
|
1166
|
+
];
|
|
1167
|
+
context.provide("piSession", {
|
|
1168
|
+
manager: {
|
|
1169
|
+
getEntries: () => entries,
|
|
1170
|
+
getBranch: () => entries,
|
|
1171
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1172
|
+
},
|
|
1173
|
+
} as never);
|
|
1174
|
+
const panels = new PiPluginUiRegistry();
|
|
1175
|
+
context.provide("piTools", new PiToolRegistry());
|
|
1176
|
+
context.provide("piPluginUi", panels);
|
|
1177
|
+
await context.plugin(agentTeamsPlugin);
|
|
1178
|
+
|
|
1179
|
+
const panel = (await panels.snapshot())[0];
|
|
1180
|
+
if (panel === undefined) throw new Error("agent-teams-panel was not registered");
|
|
1181
|
+
const [task] = (panel.data as { tasks: Array<{ dependsOn: string[] }> }).tasks;
|
|
1182
|
+
expect(task?.dependsOn).toHaveLength(255);
|
|
1183
|
+
expect(task?.dependsOn[0]).toBe("dependency-0");
|
|
1184
|
+
expect(task?.dependsOn.at(-1)).toBe("dependency-254");
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
test("allocates a unique agent team task id after loading sparse persisted ids", async () => {
|
|
1188
|
+
const context = new Context();
|
|
1189
|
+
contexts.push(context);
|
|
1190
|
+
const entries: unknown[] = [
|
|
1191
|
+
{
|
|
1192
|
+
type: "custom",
|
|
1193
|
+
customType: "pi-harness/agent-teams",
|
|
1194
|
+
data: {
|
|
1195
|
+
members: [{ id: "builder", name: "Builder", role: "Build", status: "idle" }],
|
|
1196
|
+
tasks: [{ id: "task-2", title: "Existing", assignee: "builder", status: "todo", dependsOn: [] }],
|
|
1197
|
+
messages: [],
|
|
1198
|
+
},
|
|
1199
|
+
},
|
|
1200
|
+
];
|
|
1201
|
+
context.provide("piSession", {
|
|
1202
|
+
manager: {
|
|
1203
|
+
getEntries: () => entries,
|
|
1204
|
+
getBranch: () => entries,
|
|
1205
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1206
|
+
},
|
|
1207
|
+
} as never);
|
|
1208
|
+
const tools = new PiToolRegistry();
|
|
1209
|
+
context.provide("piTools", tools);
|
|
1210
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
1211
|
+
await context.plugin(agentTeamsPlugin);
|
|
1212
|
+
|
|
1213
|
+
await expect(
|
|
1214
|
+
namedTool(tools, "team_task").execute("call-1", { action: "add_task", title: "Recovered append" }, undefined, undefined, {} as never),
|
|
1215
|
+
).resolves.toMatchObject({
|
|
1216
|
+
details: { item: { id: "task-1" } },
|
|
1217
|
+
});
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
test("allocates a unique agent team message id after loading sparse persisted ids", async () => {
|
|
1221
|
+
const context = new Context();
|
|
1222
|
+
contexts.push(context);
|
|
1223
|
+
const entries: unknown[] = [
|
|
1224
|
+
{
|
|
1225
|
+
type: "custom",
|
|
1226
|
+
customType: "pi-harness/agent-teams",
|
|
1227
|
+
data: {
|
|
1228
|
+
members: [{ id: "builder", name: "Builder", role: "Build", status: "idle" }],
|
|
1229
|
+
tasks: [],
|
|
1230
|
+
messages: [{ id: "message-2", from: "builder", to: "builder", body: "Existing", timestamp: "2026-09-05T00:00:00.000Z", read: false }],
|
|
1231
|
+
},
|
|
1232
|
+
},
|
|
1233
|
+
];
|
|
1234
|
+
context.provide("piSession", {
|
|
1235
|
+
manager: {
|
|
1236
|
+
getEntries: () => entries,
|
|
1237
|
+
getBranch: () => entries,
|
|
1238
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1239
|
+
},
|
|
1240
|
+
} as never);
|
|
1241
|
+
const tools = new PiToolRegistry();
|
|
1242
|
+
context.provide("piTools", tools);
|
|
1243
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
1244
|
+
await context.plugin(agentTeamsPlugin);
|
|
1245
|
+
|
|
1246
|
+
await expect(
|
|
1247
|
+
namedTool(tools, "team_task").execute(
|
|
1248
|
+
"call-1",
|
|
1249
|
+
{ action: "send_message", from: "builder", to: "builder", body: "Recovered append" },
|
|
1250
|
+
undefined,
|
|
1251
|
+
undefined,
|
|
1252
|
+
{} as never,
|
|
1253
|
+
),
|
|
1254
|
+
).resolves.toMatchObject({ details: { item: { id: "message-1" } } });
|
|
1255
|
+
});
|
|
1256
|
+
|
|
1257
|
+
test("normalizes invalid agent team message timestamps loaded from persisted state", async () => {
|
|
1258
|
+
const context = new Context();
|
|
1259
|
+
contexts.push(context);
|
|
1260
|
+
const entries: unknown[] = [
|
|
1261
|
+
{
|
|
1262
|
+
type: "custom",
|
|
1263
|
+
customType: "pi-harness/agent-teams",
|
|
1264
|
+
data: {
|
|
1265
|
+
members: [{ id: "builder", name: "Builder", role: "Build", status: "idle" }],
|
|
1266
|
+
tasks: [],
|
|
1267
|
+
messages: [{ id: "message-1", from: "builder", to: "builder", body: "Recovered", timestamp: "not-a-date", read: false }],
|
|
1268
|
+
},
|
|
1269
|
+
},
|
|
1270
|
+
];
|
|
1271
|
+
context.provide("piSession", {
|
|
1272
|
+
manager: {
|
|
1273
|
+
getEntries: () => entries,
|
|
1274
|
+
getBranch: () => entries,
|
|
1275
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1276
|
+
},
|
|
1277
|
+
} as never);
|
|
1278
|
+
const panels = new PiPluginUiRegistry();
|
|
1279
|
+
context.provide("piTools", new PiToolRegistry());
|
|
1280
|
+
context.provide("piPluginUi", panels);
|
|
1281
|
+
await context.plugin(agentTeamsPlugin);
|
|
1282
|
+
|
|
1283
|
+
await expect(panels.snapshot()).resolves.toMatchObject([{ data: { messages: [{ timestamp: "1970-01-01T00:00:00.000Z" }] } }]);
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1286
|
+
test("rejects an unknown assignee when creating an agent team task", async () => {
|
|
1287
|
+
const context = new Context();
|
|
1288
|
+
contexts.push(context);
|
|
1289
|
+
const entries: unknown[] = [];
|
|
1290
|
+
context.provide("piSession", {
|
|
1291
|
+
manager: {
|
|
1292
|
+
getEntries: () => entries,
|
|
1293
|
+
getBranch: () => entries,
|
|
1294
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1295
|
+
},
|
|
1296
|
+
} as never);
|
|
1297
|
+
const tools = new PiToolRegistry();
|
|
1298
|
+
context.provide("piTools", tools);
|
|
1299
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
1300
|
+
await context.plugin(agentTeamsPlugin);
|
|
1301
|
+
|
|
1302
|
+
await expect(
|
|
1303
|
+
namedTool(tools, "team_task").execute(
|
|
1304
|
+
"call-1",
|
|
1305
|
+
{ action: "add_task", title: "Unowned work", assignee: "missing-member" },
|
|
1306
|
+
undefined,
|
|
1307
|
+
undefined,
|
|
1308
|
+
{} as never,
|
|
1309
|
+
),
|
|
1310
|
+
).rejects.toThrow(/unknown assignee/iu);
|
|
1311
|
+
expect(entries).toEqual([]);
|
|
1312
|
+
});
|
|
1313
|
+
|
|
1314
|
+
test("rejects an unknown assignee when updating an agent team task", async () => {
|
|
1315
|
+
const context = new Context();
|
|
1316
|
+
contexts.push(context);
|
|
1317
|
+
const entries: unknown[] = [];
|
|
1318
|
+
context.provide("piSession", {
|
|
1319
|
+
manager: {
|
|
1320
|
+
getEntries: () => entries,
|
|
1321
|
+
getBranch: () => entries,
|
|
1322
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1323
|
+
},
|
|
1324
|
+
} as never);
|
|
1325
|
+
const tools = new PiToolRegistry();
|
|
1326
|
+
context.provide("piTools", tools);
|
|
1327
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
1328
|
+
await context.plugin(agentTeamsPlugin);
|
|
1329
|
+
const tool = namedTool(tools, "team_task");
|
|
1330
|
+
await tool.execute("setup", { action: "add_task", title: "Owned work" }, undefined, undefined, {} as never);
|
|
1331
|
+
|
|
1332
|
+
await expect(
|
|
1333
|
+
tool.execute("call-1", { action: "update_task", id: "task-1", assignee: "missing-member" }, undefined, undefined, {} as never),
|
|
1334
|
+
).rejects.toThrow(/unknown assignee/iu);
|
|
1335
|
+
expect(entries).toHaveLength(1);
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1338
|
+
test("rejects an unknown assignee when claiming an agent team task", async () => {
|
|
1339
|
+
const context = new Context();
|
|
1340
|
+
contexts.push(context);
|
|
1341
|
+
const entries: unknown[] = [];
|
|
1342
|
+
context.provide("piSession", {
|
|
1343
|
+
manager: {
|
|
1344
|
+
getEntries: () => entries,
|
|
1345
|
+
getBranch: () => entries,
|
|
1346
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1347
|
+
},
|
|
1348
|
+
} as never);
|
|
1349
|
+
const tools = new PiToolRegistry();
|
|
1350
|
+
context.provide("piTools", tools);
|
|
1351
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
1352
|
+
await context.plugin(agentTeamsPlugin);
|
|
1353
|
+
const tool = namedTool(tools, "team_task");
|
|
1354
|
+
await tool.execute("setup", { action: "add_task", title: "Claimable work" }, undefined, undefined, {} as never);
|
|
1355
|
+
|
|
1356
|
+
await expect(tool.execute("call-1", { action: "claim_task", assignee: "missing-member" }, undefined, undefined, {} as never)).rejects.toThrow(
|
|
1357
|
+
/unknown assignee/iu,
|
|
1358
|
+
);
|
|
1359
|
+
expect(entries).toHaveLength(1);
|
|
1360
|
+
});
|
|
1361
|
+
|
|
1362
|
+
test("rejects an oversized agent team task id", async () => {
|
|
1363
|
+
const context = new Context();
|
|
1364
|
+
contexts.push(context);
|
|
1365
|
+
const entries: unknown[] = [];
|
|
1366
|
+
context.provide("piSession", {
|
|
1367
|
+
manager: {
|
|
1368
|
+
getEntries: () => entries,
|
|
1369
|
+
getBranch: () => entries,
|
|
1370
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1371
|
+
},
|
|
1372
|
+
} as never);
|
|
1373
|
+
const tools = new PiToolRegistry();
|
|
1374
|
+
context.provide("piTools", tools);
|
|
1375
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
1376
|
+
await context.plugin(agentTeamsPlugin);
|
|
1377
|
+
|
|
1378
|
+
await expect(
|
|
1379
|
+
namedTool(tools, "team_task").execute(
|
|
1380
|
+
"call-1",
|
|
1381
|
+
{ action: "add_task", id: "t".repeat(65), title: "Bounded identifier" },
|
|
1382
|
+
undefined,
|
|
1383
|
+
undefined,
|
|
1384
|
+
{} as never,
|
|
1385
|
+
),
|
|
1386
|
+
).rejects.toThrow(/task id.*1-64/iu);
|
|
1387
|
+
expect(entries).toEqual([]);
|
|
1388
|
+
});
|
|
1389
|
+
|
|
1390
|
+
test("rejects a completed agent team task whose dependencies are incomplete", async () => {
|
|
1391
|
+
const context = new Context();
|
|
1392
|
+
contexts.push(context);
|
|
1393
|
+
const entries: unknown[] = [];
|
|
1394
|
+
context.provide("piSession", {
|
|
1395
|
+
manager: {
|
|
1396
|
+
getEntries: () => entries,
|
|
1397
|
+
getBranch: () => entries,
|
|
1398
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1399
|
+
},
|
|
1400
|
+
} as never);
|
|
1401
|
+
const tools = new PiToolRegistry();
|
|
1402
|
+
context.provide("piTools", tools);
|
|
1403
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
1404
|
+
await context.plugin(agentTeamsPlugin);
|
|
1405
|
+
const tool = namedTool(tools, "team_task");
|
|
1406
|
+
await tool.execute("setup", { action: "add_task", title: "Dependency" }, undefined, undefined, {} as never);
|
|
1407
|
+
|
|
1408
|
+
await expect(
|
|
1409
|
+
tool.execute("call-1", { action: "add_task", title: "Premature completion", status: "done", dependsOn: ["task-1"] }, undefined, undefined, {} as never),
|
|
1410
|
+
).rejects.toThrow(/cannot be completed before its dependencies/iu);
|
|
1411
|
+
expect(entries).toHaveLength(1);
|
|
1412
|
+
});
|
|
1413
|
+
|
|
1414
|
+
test("rejects an in-progress agent team task without a member assignee", async () => {
|
|
1415
|
+
const context = new Context();
|
|
1416
|
+
contexts.push(context);
|
|
1417
|
+
const entries: unknown[] = [];
|
|
1418
|
+
context.provide("piSession", {
|
|
1419
|
+
manager: {
|
|
1420
|
+
getEntries: () => entries,
|
|
1421
|
+
getBranch: () => entries,
|
|
1422
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1423
|
+
},
|
|
1424
|
+
} as never);
|
|
1425
|
+
const tools = new PiToolRegistry();
|
|
1426
|
+
context.provide("piTools", tools);
|
|
1427
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
1428
|
+
await context.plugin(agentTeamsPlugin);
|
|
1429
|
+
|
|
1430
|
+
await expect(
|
|
1431
|
+
namedTool(tools, "team_task").execute(
|
|
1432
|
+
"call-1",
|
|
1433
|
+
{ action: "add_task", title: "Unowned active work", status: "in_progress" },
|
|
1434
|
+
undefined,
|
|
1435
|
+
undefined,
|
|
1436
|
+
{} as never,
|
|
1437
|
+
),
|
|
1438
|
+
).rejects.toThrow(/in-progress.*assignee/iu);
|
|
1439
|
+
expect(entries).toEqual([]);
|
|
1440
|
+
});
|
|
1441
|
+
|
|
1442
|
+
test("rejects an in-progress agent team task whose dependencies are incomplete", async () => {
|
|
1443
|
+
const context = new Context();
|
|
1444
|
+
contexts.push(context);
|
|
1445
|
+
const entries: unknown[] = [];
|
|
1446
|
+
context.provide("piSession", {
|
|
1447
|
+
manager: {
|
|
1448
|
+
getEntries: () => entries,
|
|
1449
|
+
getBranch: () => entries,
|
|
1450
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1451
|
+
},
|
|
1452
|
+
} as never);
|
|
1453
|
+
const tools = new PiToolRegistry();
|
|
1454
|
+
context.provide("piTools", tools);
|
|
1455
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
1456
|
+
await context.plugin(agentTeamsPlugin);
|
|
1457
|
+
const tool = namedTool(tools, "team_task");
|
|
1458
|
+
await tool.execute("setup", { action: "add_task", title: "Dependency" }, undefined, undefined, {} as never);
|
|
1459
|
+
|
|
1460
|
+
await expect(
|
|
1461
|
+
tool.execute(
|
|
1462
|
+
"call-1",
|
|
1463
|
+
{ action: "add_task", title: "Premature work", assignee: "builder", status: "in_progress", dependsOn: ["task-1"] },
|
|
1464
|
+
undefined,
|
|
1465
|
+
undefined,
|
|
1466
|
+
{} as never,
|
|
1467
|
+
),
|
|
1468
|
+
).rejects.toThrow(/cannot be started before its dependencies/iu);
|
|
1469
|
+
expect(entries).toHaveLength(1);
|
|
1470
|
+
});
|
|
1471
|
+
|
|
1472
|
+
test("rejects moving an unassigned agent team task into progress", async () => {
|
|
1473
|
+
const context = new Context();
|
|
1474
|
+
contexts.push(context);
|
|
1475
|
+
const entries: unknown[] = [];
|
|
1476
|
+
context.provide("piSession", {
|
|
1477
|
+
manager: {
|
|
1478
|
+
getEntries: () => entries,
|
|
1479
|
+
getBranch: () => entries,
|
|
1480
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1481
|
+
},
|
|
1482
|
+
} as never);
|
|
1483
|
+
const tools = new PiToolRegistry();
|
|
1484
|
+
context.provide("piTools", tools);
|
|
1485
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
1486
|
+
await context.plugin(agentTeamsPlugin);
|
|
1487
|
+
const tool = namedTool(tools, "team_task");
|
|
1488
|
+
await tool.execute("setup", { action: "add_task", title: "Unassigned" }, undefined, undefined, {} as never);
|
|
1489
|
+
|
|
1490
|
+
await expect(tool.execute("call-1", { action: "update_task", id: "task-1", status: "in_progress" }, undefined, undefined, {} as never)).rejects.toThrow(
|
|
1491
|
+
/in-progress.*assignee/iu,
|
|
1492
|
+
);
|
|
1493
|
+
expect(entries).toHaveLength(1);
|
|
1494
|
+
});
|
|
1495
|
+
|
|
1496
|
+
test("rejects removing the assignee from an in-progress agent team task", async () => {
|
|
1497
|
+
const context = new Context();
|
|
1498
|
+
contexts.push(context);
|
|
1499
|
+
const entries: unknown[] = [];
|
|
1500
|
+
context.provide("piSession", {
|
|
1501
|
+
manager: {
|
|
1502
|
+
getEntries: () => entries,
|
|
1503
|
+
getBranch: () => entries,
|
|
1504
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1505
|
+
},
|
|
1506
|
+
} as never);
|
|
1507
|
+
const tools = new PiToolRegistry();
|
|
1508
|
+
context.provide("piTools", tools);
|
|
1509
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
1510
|
+
await context.plugin(agentTeamsPlugin);
|
|
1511
|
+
const tool = namedTool(tools, "team_task");
|
|
1512
|
+
await tool.execute("setup", { action: "add_task", title: "Active", assignee: "builder", status: "in_progress" }, undefined, undefined, {} as never);
|
|
1513
|
+
|
|
1514
|
+
await expect(tool.execute("call-1", { action: "update_task", id: "task-1", assignee: "unassigned" }, undefined, undefined, {} as never)).rejects.toThrow(
|
|
1515
|
+
/in-progress.*assignee/iu,
|
|
1516
|
+
);
|
|
1517
|
+
expect(entries).toHaveLength(1);
|
|
1518
|
+
});
|
|
1519
|
+
|
|
1520
|
+
test("rejects starting an agent team task whose dependencies are incomplete", async () => {
|
|
1521
|
+
const context = new Context();
|
|
1522
|
+
contexts.push(context);
|
|
1523
|
+
const entries: unknown[] = [];
|
|
1524
|
+
context.provide("piSession", {
|
|
1525
|
+
manager: {
|
|
1526
|
+
getEntries: () => entries,
|
|
1527
|
+
getBranch: () => entries,
|
|
1528
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1529
|
+
},
|
|
1530
|
+
} as never);
|
|
1531
|
+
const tools = new PiToolRegistry();
|
|
1532
|
+
context.provide("piTools", tools);
|
|
1533
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
1534
|
+
await context.plugin(agentTeamsPlugin);
|
|
1535
|
+
const tool = namedTool(tools, "team_task");
|
|
1536
|
+
await tool.execute("dependency", { action: "add_task", title: "Dependency" }, undefined, undefined, {} as never);
|
|
1537
|
+
await tool.execute("dependent", { action: "add_task", title: "Dependent", assignee: "builder", dependsOn: ["task-1"] }, undefined, undefined, {} as never);
|
|
1538
|
+
|
|
1539
|
+
await expect(tool.execute("call-1", { action: "update_task", id: "task-2", status: "in_progress" }, undefined, undefined, {} as never)).rejects.toThrow(
|
|
1540
|
+
/cannot be started before its dependencies/iu,
|
|
1541
|
+
);
|
|
1542
|
+
expect(entries).toHaveLength(2);
|
|
1543
|
+
});
|
|
1544
|
+
|
|
1545
|
+
test("rejects adding an incomplete dependency to an in-progress agent team task", async () => {
|
|
1546
|
+
const context = new Context();
|
|
1547
|
+
contexts.push(context);
|
|
1548
|
+
const entries: unknown[] = [];
|
|
1549
|
+
context.provide("piSession", {
|
|
1550
|
+
manager: {
|
|
1551
|
+
getEntries: () => entries,
|
|
1552
|
+
getBranch: () => entries,
|
|
1553
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1554
|
+
},
|
|
1555
|
+
} as never);
|
|
1556
|
+
const tools = new PiToolRegistry();
|
|
1557
|
+
context.provide("piTools", tools);
|
|
1558
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
1559
|
+
await context.plugin(agentTeamsPlugin);
|
|
1560
|
+
const tool = namedTool(tools, "team_task");
|
|
1561
|
+
await tool.execute("dependency", { action: "add_task", title: "Dependency" }, undefined, undefined, {} as never);
|
|
1562
|
+
await tool.execute("active", { action: "add_task", title: "Active", assignee: "builder", status: "in_progress" }, undefined, undefined, {} as never);
|
|
1563
|
+
|
|
1564
|
+
await expect(tool.execute("call-1", { action: "update_task", id: "task-2", dependsOn: ["task-1"] }, undefined, undefined, {} as never)).rejects.toThrow(
|
|
1565
|
+
/cannot be started before its dependencies/iu,
|
|
1566
|
+
);
|
|
1567
|
+
expect(entries).toHaveLength(2);
|
|
1568
|
+
});
|
|
1569
|
+
|
|
1570
|
+
test("rejects adding an incomplete dependency to a completed agent team task", async () => {
|
|
1571
|
+
const context = new Context();
|
|
1572
|
+
contexts.push(context);
|
|
1573
|
+
const entries: unknown[] = [];
|
|
1574
|
+
context.provide("piSession", {
|
|
1575
|
+
manager: {
|
|
1576
|
+
getEntries: () => entries,
|
|
1577
|
+
getBranch: () => entries,
|
|
1578
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1579
|
+
},
|
|
1580
|
+
} as never);
|
|
1581
|
+
const tools = new PiToolRegistry();
|
|
1582
|
+
context.provide("piTools", tools);
|
|
1583
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
1584
|
+
await context.plugin(agentTeamsPlugin);
|
|
1585
|
+
const tool = namedTool(tools, "team_task");
|
|
1586
|
+
await tool.execute("dependency", { action: "add_task", title: "Dependency" }, undefined, undefined, {} as never);
|
|
1587
|
+
await tool.execute("completed", { action: "add_task", title: "Completed", status: "done" }, undefined, undefined, {} as never);
|
|
1588
|
+
|
|
1589
|
+
await expect(tool.execute("call-1", { action: "update_task", id: "task-2", dependsOn: ["task-1"] }, undefined, undefined, {} as never)).rejects.toThrow(
|
|
1590
|
+
/cannot be completed before its dependencies/iu,
|
|
1591
|
+
);
|
|
1592
|
+
expect(entries).toHaveLength(2);
|
|
1593
|
+
});
|
|
1594
|
+
|
|
1595
|
+
test("preserves a preassigned member when claiming an agent team task without an assignee parameter", async () => {
|
|
1596
|
+
const context = new Context();
|
|
1597
|
+
contexts.push(context);
|
|
1598
|
+
const entries: unknown[] = [];
|
|
1599
|
+
context.provide("piSession", {
|
|
1600
|
+
manager: {
|
|
1601
|
+
getEntries: () => entries,
|
|
1602
|
+
getBranch: () => entries,
|
|
1603
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1604
|
+
},
|
|
1605
|
+
} as never);
|
|
1606
|
+
const tools = new PiToolRegistry();
|
|
1607
|
+
context.provide("piTools", tools);
|
|
1608
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
1609
|
+
await context.plugin(agentTeamsPlugin);
|
|
1610
|
+
const tool = namedTool(tools, "team_task");
|
|
1611
|
+
await tool.execute("setup", { action: "add_task", title: "Assigned work", assignee: "builder" }, undefined, undefined, {} as never);
|
|
1612
|
+
|
|
1613
|
+
await expect(tool.execute("call-1", { action: "claim_task" }, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
1614
|
+
details: { item: { status: "in_progress", assignee: "builder" } },
|
|
1615
|
+
});
|
|
1616
|
+
});
|
|
1617
|
+
|
|
1618
|
+
test("requires a member when claiming an unassigned agent team task", async () => {
|
|
1619
|
+
const context = new Context();
|
|
1620
|
+
contexts.push(context);
|
|
1621
|
+
const entries: unknown[] = [];
|
|
1622
|
+
context.provide("piSession", {
|
|
1623
|
+
manager: {
|
|
1624
|
+
getEntries: () => entries,
|
|
1625
|
+
getBranch: () => entries,
|
|
1626
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1627
|
+
},
|
|
1628
|
+
} as never);
|
|
1629
|
+
const tools = new PiToolRegistry();
|
|
1630
|
+
context.provide("piTools", tools);
|
|
1631
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
1632
|
+
await context.plugin(agentTeamsPlugin);
|
|
1633
|
+
const tool = namedTool(tools, "team_task");
|
|
1634
|
+
await tool.execute("setup", { action: "add_task", title: "Unassigned work" }, undefined, undefined, {} as never);
|
|
1635
|
+
|
|
1636
|
+
await expect(tool.execute("call-1", { action: "claim_task" }, undefined, undefined, {} as never)).rejects.toThrow(/assignee.*required/iu);
|
|
1637
|
+
expect(entries).toHaveLength(1);
|
|
1638
|
+
});
|
|
1639
|
+
|
|
1640
|
+
test("rejects an oversized agent team dependency list before resolving references", async () => {
|
|
1641
|
+
const context = new Context();
|
|
1642
|
+
contexts.push(context);
|
|
1643
|
+
const entries: unknown[] = [];
|
|
1644
|
+
context.provide("piSession", {
|
|
1645
|
+
manager: {
|
|
1646
|
+
getEntries: () => entries,
|
|
1647
|
+
getBranch: () => entries,
|
|
1648
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1649
|
+
},
|
|
1650
|
+
} as never);
|
|
1651
|
+
const tools = new PiToolRegistry();
|
|
1652
|
+
context.provide("piTools", tools);
|
|
1653
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
1654
|
+
await context.plugin(agentTeamsPlugin);
|
|
1655
|
+
|
|
1656
|
+
await expect(
|
|
1657
|
+
namedTool(tools, "team_task").execute(
|
|
1658
|
+
"call-1",
|
|
1659
|
+
{ action: "add_task", title: "Too connected", dependsOn: Array.from({ length: 257 }, (_, index) => `dependency-${index}`) },
|
|
1660
|
+
undefined,
|
|
1661
|
+
undefined,
|
|
1662
|
+
{} as never,
|
|
1663
|
+
),
|
|
1664
|
+
).rejects.toThrow(/at most 256 dependencies/iu);
|
|
1665
|
+
expect(entries).toEqual([]);
|
|
1666
|
+
});
|
|
1667
|
+
|
|
1668
|
+
test("rejects an oversized agent team dependency id before resolving references", async () => {
|
|
1669
|
+
const context = new Context();
|
|
1670
|
+
contexts.push(context);
|
|
1671
|
+
const entries: unknown[] = [];
|
|
1672
|
+
context.provide("piSession", {
|
|
1673
|
+
manager: {
|
|
1674
|
+
getEntries: () => entries,
|
|
1675
|
+
getBranch: () => entries,
|
|
1676
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1677
|
+
},
|
|
1678
|
+
} as never);
|
|
1679
|
+
const tools = new PiToolRegistry();
|
|
1680
|
+
context.provide("piTools", tools);
|
|
1681
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
1682
|
+
await context.plugin(agentTeamsPlugin);
|
|
1683
|
+
|
|
1684
|
+
await expect(
|
|
1685
|
+
namedTool(tools, "team_task").execute(
|
|
1686
|
+
"call-1",
|
|
1687
|
+
{ action: "add_task", title: "Invalid reference", dependsOn: ["d".repeat(65)] },
|
|
1688
|
+
undefined,
|
|
1689
|
+
undefined,
|
|
1690
|
+
{} as never,
|
|
1691
|
+
),
|
|
1692
|
+
).rejects.toThrow(/task dependency id.*1-64/iu);
|
|
1693
|
+
expect(entries).toEqual([]);
|
|
1694
|
+
});
|
|
1695
|
+
|
|
1696
|
+
test("rejects an oversized dependency list when updating an agent team task", async () => {
|
|
1697
|
+
const context = new Context();
|
|
1698
|
+
contexts.push(context);
|
|
1699
|
+
const entries: unknown[] = [];
|
|
1700
|
+
context.provide("piSession", {
|
|
1701
|
+
manager: {
|
|
1702
|
+
getEntries: () => entries,
|
|
1703
|
+
getBranch: () => entries,
|
|
1704
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1705
|
+
},
|
|
1706
|
+
} as never);
|
|
1707
|
+
const tools = new PiToolRegistry();
|
|
1708
|
+
context.provide("piTools", tools);
|
|
1709
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
1710
|
+
await context.plugin(agentTeamsPlugin);
|
|
1711
|
+
const tool = namedTool(tools, "team_task");
|
|
1712
|
+
await tool.execute("setup", { action: "add_task", title: "Existing task" }, undefined, undefined, {} as never);
|
|
1713
|
+
|
|
1714
|
+
await expect(
|
|
1715
|
+
tool.execute(
|
|
1716
|
+
"call-1",
|
|
1717
|
+
{ action: "update_task", id: "task-1", dependsOn: Array.from({ length: 257 }, (_, index) => `dependency-${index}`) },
|
|
1718
|
+
undefined,
|
|
1719
|
+
undefined,
|
|
1720
|
+
{} as never,
|
|
1721
|
+
),
|
|
1722
|
+
).rejects.toThrow(/at most 256 dependencies/iu);
|
|
1723
|
+
expect(entries).toHaveLength(1);
|
|
1724
|
+
});
|
|
1725
|
+
|
|
1726
|
+
test("rejects an oversized agent team member name", async () => {
|
|
1727
|
+
const context = new Context();
|
|
1728
|
+
contexts.push(context);
|
|
1729
|
+
const entries: unknown[] = [];
|
|
1730
|
+
context.provide("piSession", {
|
|
1731
|
+
manager: {
|
|
1732
|
+
getEntries: () => entries,
|
|
1733
|
+
getBranch: () => entries,
|
|
1734
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1735
|
+
},
|
|
1736
|
+
} as never);
|
|
1737
|
+
const tools = new PiToolRegistry();
|
|
1738
|
+
context.provide("piTools", tools);
|
|
1739
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
1740
|
+
await context.plugin(agentTeamsPlugin);
|
|
1741
|
+
|
|
1742
|
+
await expect(
|
|
1743
|
+
namedTool(tools, "team_task").execute("call-1", { action: "add_member", id: "long-name", name: "n".repeat(201) }, undefined, undefined, {} as never),
|
|
1744
|
+
).rejects.toThrow(/member name.*1-200/iu);
|
|
1745
|
+
expect(entries).toEqual([]);
|
|
1746
|
+
});
|
|
1747
|
+
|
|
1748
|
+
test("rejects an oversized agent team member role", async () => {
|
|
1749
|
+
const context = new Context();
|
|
1750
|
+
contexts.push(context);
|
|
1751
|
+
const entries: unknown[] = [];
|
|
1752
|
+
context.provide("piSession", {
|
|
1753
|
+
manager: {
|
|
1754
|
+
getEntries: () => entries,
|
|
1755
|
+
getBranch: () => entries,
|
|
1756
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1757
|
+
},
|
|
1758
|
+
} as never);
|
|
1759
|
+
const tools = new PiToolRegistry();
|
|
1760
|
+
context.provide("piTools", tools);
|
|
1761
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
1762
|
+
await context.plugin(agentTeamsPlugin);
|
|
1763
|
+
|
|
1764
|
+
await expect(
|
|
1765
|
+
namedTool(tools, "team_task").execute(
|
|
1766
|
+
"call-1",
|
|
1767
|
+
{ action: "add_member", id: "long-role", name: "Long role", role: "r".repeat(201) },
|
|
1768
|
+
undefined,
|
|
1769
|
+
undefined,
|
|
1770
|
+
{} as never,
|
|
1771
|
+
),
|
|
1772
|
+
).rejects.toThrow(/member role.*1-200/iu);
|
|
1773
|
+
expect(entries).toEqual([]);
|
|
378
1774
|
});
|
|
379
1775
|
|
|
380
|
-
test("
|
|
1776
|
+
test("rejects a working agent team member without an in-progress task", async () => {
|
|
381
1777
|
const context = new Context();
|
|
382
1778
|
contexts.push(context);
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
1779
|
+
const entries: unknown[] = [];
|
|
1780
|
+
context.provide("piSession", {
|
|
1781
|
+
manager: {
|
|
1782
|
+
getEntries: () => entries,
|
|
1783
|
+
getBranch: () => entries,
|
|
1784
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1785
|
+
},
|
|
387
1786
|
} as never);
|
|
388
|
-
|
|
389
|
-
context.provide("piTools",
|
|
1787
|
+
const tools = new PiToolRegistry();
|
|
1788
|
+
context.provide("piTools", tools);
|
|
1789
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
1790
|
+
await context.plugin(agentTeamsPlugin);
|
|
390
1791
|
|
|
391
|
-
await
|
|
1792
|
+
await expect(
|
|
1793
|
+
namedTool(tools, "team_task").execute("call-1", { action: "add_member", id: "busy", name: "Busy", status: "working" }, undefined, undefined, {} as never),
|
|
1794
|
+
).rejects.toThrow(/working.*in-progress task/iu);
|
|
1795
|
+
expect(entries).toEqual([]);
|
|
1796
|
+
});
|
|
392
1797
|
|
|
393
|
-
|
|
394
|
-
context
|
|
395
|
-
|
|
1798
|
+
test("bounds the durable agent team task list before appending session state", async () => {
|
|
1799
|
+
const context = new Context();
|
|
1800
|
+
contexts.push(context);
|
|
1801
|
+
const tasks = Array.from({ length: 256 }, (_, index) => ({
|
|
1802
|
+
id: `task-${index + 1}`,
|
|
1803
|
+
title: `Task ${index + 1}`,
|
|
1804
|
+
assignee: "unassigned",
|
|
1805
|
+
status: "todo",
|
|
1806
|
+
dependsOn: [],
|
|
1807
|
+
}));
|
|
1808
|
+
const entries: unknown[] = [
|
|
396
1809
|
{
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
eventTypes: { message_start: 1, compaction_start: 1 },
|
|
401
|
-
recentEvents: [{ type: "message_start" }, { type: "compaction_start" }],
|
|
402
|
-
},
|
|
1810
|
+
type: "custom",
|
|
1811
|
+
customType: "pi-harness/agent-teams",
|
|
1812
|
+
data: { members: [{ id: "planner", name: "Planner", role: "Plan", status: "idle" }], tasks, messages: [] },
|
|
403
1813
|
},
|
|
404
|
-
]
|
|
1814
|
+
];
|
|
1815
|
+
context.provide("piSession", {
|
|
1816
|
+
manager: {
|
|
1817
|
+
getEntries: () => entries,
|
|
1818
|
+
getBranch: () => entries,
|
|
1819
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1820
|
+
},
|
|
1821
|
+
} as never);
|
|
1822
|
+
const tools = new PiToolRegistry();
|
|
1823
|
+
context.provide("piTools", tools);
|
|
1824
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
1825
|
+
await context.plugin(agentTeamsPlugin);
|
|
405
1826
|
|
|
406
|
-
await expect(
|
|
1827
|
+
await expect(
|
|
1828
|
+
namedTool(tools, "team_task").execute("call-1", { action: "add_task", title: "One too many" }, undefined, undefined, {} as never),
|
|
1829
|
+
).rejects.toThrow(/at most 256 tasks/iu);
|
|
1830
|
+
expect(entries).toHaveLength(1);
|
|
1831
|
+
});
|
|
1832
|
+
|
|
1833
|
+
test("bounds the durable agent team mailbox before appending session state", async () => {
|
|
1834
|
+
const context = new Context();
|
|
1835
|
+
contexts.push(context);
|
|
1836
|
+
const messages = Array.from({ length: 1_000 }, (_, index) => ({
|
|
1837
|
+
id: `message-${index + 1}`,
|
|
1838
|
+
from: "builder",
|
|
1839
|
+
to: "reviewer",
|
|
1840
|
+
body: "done",
|
|
1841
|
+
timestamp: "2026-09-05T00:00:00.000Z",
|
|
1842
|
+
read: false,
|
|
1843
|
+
}));
|
|
1844
|
+
const entries: unknown[] = [
|
|
407
1845
|
{
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
title: "上下文洞察",
|
|
411
|
-
description: "查看当前上下文占用、消息规模和压缩事件。",
|
|
412
|
-
icon: "◒",
|
|
1846
|
+
type: "custom",
|
|
1847
|
+
customType: "pi-harness/agent-teams",
|
|
413
1848
|
data: {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
composition: { user: 1, assistant: 0, toolResult: 0, system: 0, other: 0 },
|
|
421
|
-
eventTypes: { message_start: 1, compaction_start: 1 },
|
|
422
|
-
recentEvents: [expect.objectContaining({ type: "message_start" }), expect.objectContaining({ type: "compaction_start" })],
|
|
1849
|
+
members: [
|
|
1850
|
+
{ id: "builder", name: "Builder", role: "Build", status: "idle" },
|
|
1851
|
+
{ id: "reviewer", name: "Reviewer", role: "Review", status: "idle" },
|
|
1852
|
+
],
|
|
1853
|
+
tasks: [],
|
|
1854
|
+
messages,
|
|
423
1855
|
},
|
|
424
1856
|
},
|
|
425
|
-
]
|
|
1857
|
+
];
|
|
1858
|
+
context.provide("piSession", {
|
|
1859
|
+
manager: {
|
|
1860
|
+
getEntries: () => entries,
|
|
1861
|
+
getBranch: () => entries,
|
|
1862
|
+
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
1863
|
+
},
|
|
1864
|
+
} as never);
|
|
1865
|
+
const tools = new PiToolRegistry();
|
|
1866
|
+
context.provide("piTools", tools);
|
|
1867
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
1868
|
+
await context.plugin(agentTeamsPlugin);
|
|
426
1869
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
1870
|
+
await expect(
|
|
1871
|
+
namedTool(tools, "team_task").execute(
|
|
1872
|
+
"call-1",
|
|
1873
|
+
{ action: "send_message", from: "builder", to: "reviewer", body: "One too many" },
|
|
1874
|
+
undefined,
|
|
1875
|
+
undefined,
|
|
1876
|
+
{} as never,
|
|
1877
|
+
),
|
|
1878
|
+
).rejects.toThrow(/at most 1000 messages/iu);
|
|
1879
|
+
expect(entries).toHaveLength(1);
|
|
432
1880
|
});
|
|
433
1881
|
|
|
434
|
-
test("
|
|
1882
|
+
test("bounds the durable agent team member list before appending session state", async () => {
|
|
435
1883
|
const context = new Context();
|
|
436
1884
|
contexts.push(context);
|
|
437
|
-
const
|
|
438
|
-
const
|
|
439
|
-
const entries: unknown[] = [];
|
|
1885
|
+
const members = Array.from({ length: 64 }, (_, index) => ({ id: `member-${index + 1}`, name: `Member ${index + 1}`, role: "Worker", status: "idle" }));
|
|
1886
|
+
const entries: unknown[] = [{ type: "custom", customType: "pi-harness/agent-teams", data: { members, tasks: [], messages: [] } }];
|
|
440
1887
|
context.provide("piSession", {
|
|
441
1888
|
manager: {
|
|
442
1889
|
getEntries: () => entries,
|
|
1890
|
+
getBranch: () => entries,
|
|
443
1891
|
appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
|
|
444
1892
|
},
|
|
445
1893
|
} as never);
|
|
1894
|
+
const tools = new PiToolRegistry();
|
|
446
1895
|
context.provide("piTools", tools);
|
|
447
|
-
context.provide("piPluginUi",
|
|
448
|
-
|
|
1896
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
449
1897
|
await context.plugin(agentTeamsPlugin);
|
|
450
|
-
|
|
451
|
-
await expect(tool.execute("call-1", { action: "add_task", title: "Review plugin manifest" }, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
452
|
-
content: [{ text: "Task task-1 created." }],
|
|
453
|
-
});
|
|
454
|
-
await expect(
|
|
455
|
-
tool.execute("call-2", { action: "add_task", title: "Run integration checks", dependsOn: ["task-1"] }, undefined, undefined, {} as never),
|
|
456
|
-
).resolves.toMatchObject({ details: { item: { id: "task-2", status: "blocked", dependsOn: ["task-1"] } } });
|
|
457
|
-
await expect(tool.execute("call-3", { action: "update_task", id: "task-1", status: "done" }, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
458
|
-
details: { item: { id: "task-1", status: "done" } },
|
|
459
|
-
});
|
|
460
|
-
await expect(panels.snapshot()).resolves.toMatchObject([
|
|
461
|
-
{ id: "agent-teams-panel", data: { tasks: [{ status: "done" }, { id: "task-2", status: "todo" }] } },
|
|
462
|
-
]);
|
|
463
|
-
await expect(tool.execute("call-4", { action: "claim_task", assignee: "builder" }, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
464
|
-
details: { item: { id: "task-2", status: "in_progress", assignee: "builder" } },
|
|
465
|
-
});
|
|
466
|
-
await expect(
|
|
467
|
-
tool.execute("call-5", { action: "send_message", from: "builder", to: "reviewer", body: "实现已完成,请开始复核" }, undefined, undefined, {} as never),
|
|
468
|
-
).resolves.toMatchObject({ details: { item: { from: "builder", to: "reviewer", body: "实现已完成,请开始复核", read: false } } });
|
|
469
|
-
await expect(tool.execute("call-6", { action: "read_messages", to: "reviewer" }, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
470
|
-
details: { messages: [{ from: "builder", to: "reviewer", body: "实现已完成,请开始复核", read: true }] },
|
|
471
|
-
});
|
|
1898
|
+
|
|
472
1899
|
await expect(
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
{ title: "Review plugin manifest", status: "done" },
|
|
483
|
-
{ title: "Run integration checks", status: "in_progress" },
|
|
484
|
-
],
|
|
485
|
-
},
|
|
486
|
-
},
|
|
487
|
-
]);
|
|
488
|
-
const panel = (await panels.snapshot())[0];
|
|
489
|
-
if (panel === undefined) throw new Error("agent-teams-panel was not registered");
|
|
490
|
-
expect((panel.data as { members: { id: string; status: string }[] }).members.find((member) => member.id === "builder")).toMatchObject({
|
|
491
|
-
status: "working",
|
|
492
|
-
});
|
|
493
|
-
expect(entries).toHaveLength(6);
|
|
1900
|
+
namedTool(tools, "team_task").execute(
|
|
1901
|
+
"call-1",
|
|
1902
|
+
{ action: "add_member", id: "member-65", name: "Member 65", role: "Worker" },
|
|
1903
|
+
undefined,
|
|
1904
|
+
undefined,
|
|
1905
|
+
{} as never,
|
|
1906
|
+
),
|
|
1907
|
+
).rejects.toThrow(/at most 64 members/iu);
|
|
1908
|
+
expect(entries).toHaveLength(1);
|
|
494
1909
|
});
|
|
495
1910
|
|
|
496
1911
|
test("reloads the live Pi session through the plugin-dev bridge", async () => {
|
|
@@ -545,20 +1960,23 @@ describe("Pi domain plugins", () => {
|
|
|
545
1960
|
});
|
|
546
1961
|
|
|
547
1962
|
test("attaches an in-workspace image through the modlens tool", async () => {
|
|
548
|
-
const { context, cwd } = await createContext();
|
|
1963
|
+
const { context, cwd, agentDir } = await createContext();
|
|
549
1964
|
const panels = new PiPluginUiRegistry();
|
|
550
1965
|
const tools = new PiToolRegistry();
|
|
551
|
-
await writeFile(join(cwd, "screen.png"), "
|
|
1966
|
+
await writeFile(join(cwd, "screen.png"), Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB", "base64"));
|
|
552
1967
|
context.provide("piTools", tools);
|
|
553
1968
|
context.provide("piPluginUi", panels);
|
|
554
1969
|
|
|
555
1970
|
await context.plugin(modlensPlugin);
|
|
556
1971
|
const tool = firstTool(tools);
|
|
557
|
-
await expect(tool.execute("call-1", { path: "screen.png" }, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
1972
|
+
await expect(tool.execute("call-1", { path: "screen.png" }, undefined, undefined, { model: { input: ["image"] } } as never)).resolves.toMatchObject({
|
|
558
1973
|
content: [{ type: "image", mimeType: "image/png" }],
|
|
559
1974
|
});
|
|
560
|
-
await expect(panels.snapshot()).resolves.toMatchObject([{ id: "modlens-panel", data: { attached: true, image: { path: "screen.png", bytes:
|
|
1975
|
+
await expect(panels.snapshot()).resolves.toMatchObject([{ id: "modlens-panel", data: { attached: true, image: { path: "screen.png", bytes: 24 } } }]);
|
|
561
1976
|
await expect(tool.execute("call-2", { path: "../outside.png" }, undefined, undefined, {} as never)).rejects.toThrow(/inside the current workspace/);
|
|
1977
|
+
await writeFile(join(agentDir, "outside.png"), "outside-image", "utf8");
|
|
1978
|
+
await symlink(join(agentDir, "outside.png"), join(cwd, "linked.png"));
|
|
1979
|
+
await expect(tool.execute("call-3", { path: "linked.png" }, undefined, undefined, {} as never)).rejects.toThrow(/inside the current workspace/);
|
|
562
1980
|
});
|
|
563
1981
|
|
|
564
1982
|
test("stops a streaming run when the configured token budget is exceeded", async () => {
|
|
@@ -580,7 +1998,7 @@ describe("Pi domain plugins", () => {
|
|
|
580
1998
|
expect(aborts).toBe(1);
|
|
581
1999
|
});
|
|
582
2000
|
|
|
583
|
-
test("writes a Git
|
|
2001
|
+
test("writes a Git undo capsule outside the workspace", async () => {
|
|
584
2002
|
const { context, cwd } = await createContext();
|
|
585
2003
|
const panels = new PiPluginUiRegistry();
|
|
586
2004
|
const tools = new PiToolRegistry();
|
|
@@ -597,7 +2015,7 @@ describe("Pi domain plugins", () => {
|
|
|
597
2015
|
const result = await tool.execute("call-1", {}, undefined, undefined, {} as never);
|
|
598
2016
|
const message = result.content[0];
|
|
599
2017
|
expect(message?.type).toBe("text");
|
|
600
|
-
expect(message?.type === "text" ? message.text : "").toMatch(/^Git
|
|
2018
|
+
expect(message?.type === "text" ? message.text : "").toMatch(/^Git undo capsule saved:/);
|
|
601
2019
|
const snapshot = await panels.snapshot();
|
|
602
2020
|
expect(snapshot[0]?.id).toBe("git-time-capsule-panel");
|
|
603
2021
|
const capsuleData = snapshot[0]?.data as
|
|
@@ -626,8 +2044,19 @@ describe("Pi domain plugins", () => {
|
|
|
626
2044
|
await expect(panels.snapshot()).resolves.toMatchObject([{ id: "dependency-checker-panel", data: { report: { missing: ["missing-package"] } } }]);
|
|
627
2045
|
});
|
|
628
2046
|
|
|
629
|
-
test("
|
|
2047
|
+
test("rejects oversized dependency manifests before parsing them", async () => {
|
|
630
2048
|
const { context, cwd } = await createContext();
|
|
2049
|
+
const tools = new PiToolRegistry();
|
|
2050
|
+
await writeFile(join(cwd, "package.json"), JSON.stringify({ dependencies: {}, padding: "x".repeat(1024 * 1024) }), "utf8");
|
|
2051
|
+
context.provide("piTools", tools);
|
|
2052
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
2053
|
+
await context.plugin(dependencyCheckerPlugin);
|
|
2054
|
+
|
|
2055
|
+
await expect(namedTool(tools, "dependency_check").execute("call-1", {}, undefined, undefined, {} as never)).rejects.toThrow(/1 MiB|too large/iu);
|
|
2056
|
+
});
|
|
2057
|
+
|
|
2058
|
+
test("attaches a bounded workspace file for @file context", async () => {
|
|
2059
|
+
const { context, cwd, agentDir } = await createContext();
|
|
631
2060
|
const panels = new PiPluginUiRegistry();
|
|
632
2061
|
const tools = new PiToolRegistry();
|
|
633
2062
|
await writeFile(join(cwd, "notes.md"), "# Notes\ncontent", "utf8");
|
|
@@ -637,10 +2066,13 @@ describe("Pi domain plugins", () => {
|
|
|
637
2066
|
await context.plugin(atFilePlugin);
|
|
638
2067
|
const tool = firstTool(tools);
|
|
639
2068
|
await expect(tool.execute("call-1", { path: "notes.md" }, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
640
|
-
content: [{ text: '<file path="notes.md">\n# Notes\ncontent\n</file>' }],
|
|
2069
|
+
content: [{ text: '<file path="notes.md" untrusted="true">\n# Notes\ncontent\n</file>' }],
|
|
641
2070
|
});
|
|
642
2071
|
await expect(panels.snapshot()).resolves.toMatchObject([{ id: "at-file-panel", data: { lastFile: { path: "notes.md", bytes: 15 } } }]);
|
|
643
2072
|
await expect(tool.execute("call-2", { path: "../notes.md" }, undefined, undefined, {} as never)).rejects.toThrow(/inside the current workspace/);
|
|
2073
|
+
await writeFile(join(agentDir, "outside-notes.md"), "outside", "utf8");
|
|
2074
|
+
await symlink(join(agentDir, "outside-notes.md"), join(cwd, "linked-notes.md"));
|
|
2075
|
+
await expect(tool.execute("call-3", { path: "linked-notes.md" }, undefined, undefined, {} as never)).rejects.toThrow(/inside the current workspace/);
|
|
644
2076
|
});
|
|
645
2077
|
|
|
646
2078
|
test("deduplicates extension failures in the live failure logger panel", async () => {
|
|
@@ -666,7 +2098,7 @@ describe("Pi domain plugins", () => {
|
|
|
666
2098
|
context.provide("piPluginUi", panels);
|
|
667
2099
|
await context.plugin(testHarnessPlugin);
|
|
668
2100
|
const tool = firstTool(tools);
|
|
669
|
-
await expect(tool.execute("call-1", { script: "rm -rf /" }, undefined, undefined, {} as never)).rejects.toThrow(/not
|
|
2101
|
+
await expect(tool.execute("call-1", { script: "rm -rf /" }, undefined, undefined, {} as never)).rejects.toThrow(/not an approved verification script/iu);
|
|
670
2102
|
const snapshot = await panels.snapshot();
|
|
671
2103
|
expect(snapshot[0]?.id).toBe("test-harness-panel");
|
|
672
2104
|
const allowedScripts = (snapshot[0]?.data as { allowedScripts?: unknown } | undefined)?.allowedScripts;
|
|
@@ -683,7 +2115,7 @@ describe("Pi domain plugins", () => {
|
|
|
683
2115
|
assistantMessages: 2,
|
|
684
2116
|
toolCalls: 1,
|
|
685
2117
|
toolResults: 1,
|
|
686
|
-
totalMessages:
|
|
2118
|
+
totalMessages: 5,
|
|
687
2119
|
tokens: { input: 10, output: 20, cacheRead: 0, cacheWrite: 0, total: 30 },
|
|
688
2120
|
cost: 0.01,
|
|
689
2121
|
contextUsage: { tokens: 30, contextWindow: 1000, percent: 3 },
|
|
@@ -717,6 +2149,17 @@ describe("Pi domain plugins", () => {
|
|
|
717
2149
|
await expect(panels.snapshot()).resolves.toMatchObject([{ id: "readme-gen-panel", data: { generated: true, name: "demo", scripts: 2 } }]);
|
|
718
2150
|
});
|
|
719
2151
|
|
|
2152
|
+
test("rejects oversized package manifests before generating a README", async () => {
|
|
2153
|
+
const { context, cwd } = await createContext();
|
|
2154
|
+
const tools = new PiToolRegistry();
|
|
2155
|
+
await writeFile(join(cwd, "package.json"), JSON.stringify({ name: "demo", padding: "x".repeat(1024 * 1024) }), "utf8");
|
|
2156
|
+
context.provide("piTools", tools);
|
|
2157
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
2158
|
+
await context.plugin(readmeGenPlugin);
|
|
2159
|
+
|
|
2160
|
+
await expect(namedTool(tools, "readme_report").execute("call-1", {}, undefined, undefined, {} as never)).rejects.toThrow(/1 MiB|too large/iu);
|
|
2161
|
+
});
|
|
2162
|
+
|
|
720
2163
|
test("requires explicit confirmation before cleaning only generated capsules", async () => {
|
|
721
2164
|
const { context, agentDir } = await createContext();
|
|
722
2165
|
const panels = new PiPluginUiRegistry();
|
|
@@ -735,8 +2178,48 @@ describe("Pi domain plugins", () => {
|
|
|
735
2178
|
await expect(panels.snapshot()).resolves.toMatchObject([{ id: "cleaner-panel", data: { capsules: [{ name: "202601.patch" }] } }]);
|
|
736
2179
|
});
|
|
737
2180
|
|
|
2181
|
+
test("ignores symbolic links while cleaning capsule files", async () => {
|
|
2182
|
+
if (process.platform === "win32") return;
|
|
2183
|
+
const { context, cwd, agentDir } = await createContext();
|
|
2184
|
+
const panels = new PiPluginUiRegistry();
|
|
2185
|
+
const tools = new PiToolRegistry();
|
|
2186
|
+
const directory = join(agentDir, "capsules");
|
|
2187
|
+
const outside = join(cwd, "outside.patch");
|
|
2188
|
+
const link = join(directory, "linked.patch");
|
|
2189
|
+
await mkdir(directory, { recursive: true });
|
|
2190
|
+
await writeFile(join(directory, "generated.patch"), "generated", "utf8");
|
|
2191
|
+
await writeFile(outside, "outside", "utf8");
|
|
2192
|
+
await symlink(outside, link);
|
|
2193
|
+
context.provide("piTools", tools);
|
|
2194
|
+
context.provide("piPluginUi", panels);
|
|
2195
|
+
await context.plugin(cleanerPlugin);
|
|
2196
|
+
|
|
2197
|
+
await expect(
|
|
2198
|
+
namedTool(tools, "clean_harness_artifacts").execute("call-1", { confirm: true, keep: 0 }, undefined, undefined, {} as never),
|
|
2199
|
+
).resolves.toMatchObject({
|
|
2200
|
+
details: { removed: 1, kept: 0 },
|
|
2201
|
+
});
|
|
2202
|
+
expect(existsSync(link)).toBe(true);
|
|
2203
|
+
await expect(readFile(outside, "utf8")).resolves.toBe("outside");
|
|
2204
|
+
await expect(panels.snapshot()).resolves.toMatchObject([{ id: "cleaner-panel", data: { capsules: [] } }]);
|
|
2205
|
+
});
|
|
2206
|
+
|
|
2207
|
+
test("rejects an excessive number of capsule files without materializing an unbounded list", async () => {
|
|
2208
|
+
const { context, agentDir } = await createContext();
|
|
2209
|
+
const directory = join(agentDir, "capsules");
|
|
2210
|
+
await mkdir(directory, { recursive: true });
|
|
2211
|
+
await Promise.all(Array.from({ length: 257 }, (_, index) => writeFile(join(directory, `${String(index).padStart(4, "0")}.patch`), "x", "utf8")));
|
|
2212
|
+
context.provide("piTools", new PiToolRegistry());
|
|
2213
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
2214
|
+
await context.plugin(cleanerPlugin);
|
|
2215
|
+
|
|
2216
|
+
const snapshot = await context.piPluginUi.snapshot();
|
|
2217
|
+
expect(snapshot[0]?.id).toBe("cleaner-panel");
|
|
2218
|
+
expect(snapshot[0]?.error).toMatch(/exceeds.*256.*capsule/iu);
|
|
2219
|
+
});
|
|
2220
|
+
|
|
738
2221
|
test("reports missing and extra keys between local locale files", async () => {
|
|
739
|
-
const { context, cwd } = await createContext();
|
|
2222
|
+
const { context, cwd, agentDir } = await createContext();
|
|
740
2223
|
const panels = new PiPluginUiRegistry();
|
|
741
2224
|
const tools = new PiToolRegistry();
|
|
742
2225
|
await mkdir(join(cwd, "locales"), { recursive: true });
|
|
@@ -745,8 +2228,43 @@ describe("Pi domain plugins", () => {
|
|
|
745
2228
|
context.provide("piTools", tools);
|
|
746
2229
|
context.provide("piPluginUi", panels);
|
|
747
2230
|
await context.plugin(i18nPairPlugin);
|
|
748
|
-
const
|
|
2231
|
+
const tool = firstTool(tools);
|
|
2232
|
+
const result = await tool.execute("call-1", {}, undefined, undefined, {} as never);
|
|
749
2233
|
expect(result).toMatchObject({ details: { missing: ["save"], extra: ["onlyHere"] } });
|
|
2234
|
+
await writeFile(join(agentDir, "outside-locale.json"), JSON.stringify({ secret: "outside" }), "utf8");
|
|
2235
|
+
await symlink(join(agentDir, "outside-locale.json"), join(cwd, "locales", "linked.json"));
|
|
2236
|
+
await expect(tool.execute("call-2", { base: "locales/linked.json" }, undefined, undefined, {} as never)).rejects.toThrow(/symbolic link/iu);
|
|
2237
|
+
});
|
|
2238
|
+
|
|
2239
|
+
test("rejects oversized locale files before parsing them", async () => {
|
|
2240
|
+
const { context, cwd } = await createContext();
|
|
2241
|
+
const tools = new PiToolRegistry();
|
|
2242
|
+
await mkdir(join(cwd, "locales"), { recursive: true });
|
|
2243
|
+
await writeFile(join(cwd, "locales", "en.json"), JSON.stringify({ padding: "x".repeat(4 * 1024 * 1024) }), "utf8");
|
|
2244
|
+
await writeFile(join(cwd, "locales", "ja.json"), "{}", "utf8");
|
|
2245
|
+
context.provide("piTools", tools);
|
|
2246
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
2247
|
+
await context.plugin(i18nPairPlugin);
|
|
2248
|
+
|
|
2249
|
+
await expect(
|
|
2250
|
+
namedTool(tools, "i18n_check").execute("call-1", { base: "locales/en.json", target: "locales/ja.json" }, undefined, undefined, {} as never),
|
|
2251
|
+
).rejects.toThrow(/4 MiB|locale.*limit/iu);
|
|
2252
|
+
});
|
|
2253
|
+
|
|
2254
|
+
test("rejects locale objects deeper than the supported nesting limit", async () => {
|
|
2255
|
+
const { context, cwd } = await createContext();
|
|
2256
|
+
const tools = new PiToolRegistry();
|
|
2257
|
+
await mkdir(join(cwd, "locales"), { recursive: true });
|
|
2258
|
+
const deeplyNested = `${'{"nested":'.repeat(129)}"value"${"}".repeat(129)}`;
|
|
2259
|
+
await writeFile(join(cwd, "locales", "en.json"), deeplyNested, "utf8");
|
|
2260
|
+
await writeFile(join(cwd, "locales", "ja.json"), "{}", "utf8");
|
|
2261
|
+
context.provide("piTools", tools);
|
|
2262
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
2263
|
+
await context.plugin(i18nPairPlugin);
|
|
2264
|
+
|
|
2265
|
+
await expect(
|
|
2266
|
+
namedTool(tools, "i18n_check").execute("call-1", { base: "locales/en.json", target: "locales/ja.json" }, undefined, undefined, {} as never),
|
|
2267
|
+
).rejects.toThrow(/nesting|depth|128/iu);
|
|
750
2268
|
});
|
|
751
2269
|
|
|
752
2270
|
test("executes bounded read-only SQLite queries and rejects mutations", async () => {
|
|
@@ -809,6 +2327,36 @@ describe("Pi domain plugins", () => {
|
|
|
809
2327
|
await expect(status!.execute("call-4", {}, undefined, undefined, {} as never)).resolves.toMatchObject({ details: { running: false } });
|
|
810
2328
|
});
|
|
811
2329
|
|
|
2330
|
+
test("does not leak a rejected promise when mock server cleanup fails", async () => {
|
|
2331
|
+
const { context } = await createContext();
|
|
2332
|
+
const tools = new PiToolRegistry();
|
|
2333
|
+
context.provide("piTools", tools);
|
|
2334
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
2335
|
+
await context.plugin(mockServerPlugin, { port: 0, routes: [] });
|
|
2336
|
+
await namedTool(tools, "mock_server_start").execute("call-1", {}, undefined, undefined, {} as never);
|
|
2337
|
+
let closeCalled = false;
|
|
2338
|
+
const closeSpy = vi.spyOn(Server.prototype, "close");
|
|
2339
|
+
closeSpy.mockImplementationOnce(function (this: Server, callback?: (error?: Error) => void) {
|
|
2340
|
+
closeCalled = true;
|
|
2341
|
+
closeSpy.mockRestore();
|
|
2342
|
+
return this.close(() => callback?.(new Error("forced close failure")));
|
|
2343
|
+
} as typeof Server.prototype.close);
|
|
2344
|
+
const unhandled: unknown[] = [];
|
|
2345
|
+
const onUnhandled = (reason: unknown): void => {
|
|
2346
|
+
unhandled.push(reason);
|
|
2347
|
+
};
|
|
2348
|
+
process.on("unhandledRejection", onUnhandled);
|
|
2349
|
+
try {
|
|
2350
|
+
await context.fiber.dispose();
|
|
2351
|
+
await vi.waitFor(() => expect(closeCalled).toBe(true));
|
|
2352
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
2353
|
+
expect(unhandled).toEqual([]);
|
|
2354
|
+
} finally {
|
|
2355
|
+
process.off("unhandledRejection", onUnhandled);
|
|
2356
|
+
closeSpy.mockRestore();
|
|
2357
|
+
}
|
|
2358
|
+
});
|
|
2359
|
+
|
|
812
2360
|
test("records CLI notifications and respects the disabled setting", async () => {
|
|
813
2361
|
const { context } = await createContext();
|
|
814
2362
|
const panels = new PiPluginUiRegistry();
|
|
@@ -829,6 +2377,50 @@ describe("Pi domain plugins", () => {
|
|
|
829
2377
|
expect(notificationData?.notifications?.map((notification) => notification.message)).toEqual(["Agent turn completed.", "build finished"]);
|
|
830
2378
|
});
|
|
831
2379
|
|
|
2380
|
+
test("bounds notifier messages produced from session error events", async () => {
|
|
2381
|
+
const { context } = await createContext();
|
|
2382
|
+
const panels = new PiPluginUiRegistry();
|
|
2383
|
+
context.provide("piTools", new PiToolRegistry());
|
|
2384
|
+
context.provide("piPluginUi", panels);
|
|
2385
|
+
await context.plugin(cliNotifierPlugin, { enabled: false });
|
|
2386
|
+
|
|
2387
|
+
context.emit("pi/session-event", {
|
|
2388
|
+
type: "agent_end",
|
|
2389
|
+
messages: [{ role: "assistant", stopReason: "error", errorMessage: "x".repeat(3_000) }],
|
|
2390
|
+
willRetry: false,
|
|
2391
|
+
} as never);
|
|
2392
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
2393
|
+
|
|
2394
|
+
const snapshot = await panels.snapshot();
|
|
2395
|
+
const data = snapshot[0]?.data as { notifications?: Array<{ message: string }> } | undefined;
|
|
2396
|
+
expect(data?.notifications).toHaveLength(1);
|
|
2397
|
+
expect(data?.notifications?.[0]?.message.length).toBeLessThanOrEqual(2_048);
|
|
2398
|
+
});
|
|
2399
|
+
|
|
2400
|
+
test("times out a hung desktop notification process", async () => {
|
|
2401
|
+
if (process.platform === "win32") return;
|
|
2402
|
+
const { context, cwd } = await createContext();
|
|
2403
|
+
const bin = join(cwd, "bin");
|
|
2404
|
+
const executable = join(bin, process.platform === "darwin" ? "osascript" : "notify-send");
|
|
2405
|
+
await mkdir(bin);
|
|
2406
|
+
await writeFile(executable, "#!/bin/sh\nexec sleep 1\n", "utf8");
|
|
2407
|
+
await chmod(executable, 0o700);
|
|
2408
|
+
const originalPath = process.env.PATH;
|
|
2409
|
+
process.env.PATH = `${bin}${delimiter}${originalPath ?? ""}`;
|
|
2410
|
+
const tools = new PiToolRegistry();
|
|
2411
|
+
context.provide("piTools", tools);
|
|
2412
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
2413
|
+
try {
|
|
2414
|
+
await context.plugin(cliNotifierPlugin, { enabled: true, timeoutMs: 500 });
|
|
2415
|
+
const result = await namedTool(tools, "cli_notify").execute("call-1", { message: "timeout fixture" }, undefined, undefined, {} as never);
|
|
2416
|
+
const details = result.details as { delivered?: unknown; reason?: unknown };
|
|
2417
|
+
expect(details.delivered).toBe(false);
|
|
2418
|
+
expect(details.reason).toMatch(/timed out/iu);
|
|
2419
|
+
} finally {
|
|
2420
|
+
process.env.PATH = originalPath;
|
|
2421
|
+
}
|
|
2422
|
+
});
|
|
2423
|
+
|
|
832
2424
|
test("writes confirmed Markdown notes only inside the configured Obsidian vault", async () => {
|
|
833
2425
|
const { context } = await createContext();
|
|
834
2426
|
const vault = await mkdtemp(join(tmpdir(), "pi-harness-vault-"));
|
|
@@ -935,6 +2527,28 @@ describe("Pi domain plugins", () => {
|
|
|
935
2527
|
await expect(panels.snapshot()).resolves.toMatchObject([{ id: "reviewer-bot-panel", data: { latest: { status: "warning", changedFiles: 1 } } }]);
|
|
936
2528
|
});
|
|
937
2529
|
|
|
2530
|
+
test("times out a hung Git process while reviewing changes", async () => {
|
|
2531
|
+
if (process.platform === "win32") return;
|
|
2532
|
+
const { context, cwd } = await createContext();
|
|
2533
|
+
const bin = join(cwd, "bin");
|
|
2534
|
+
const executable = join(bin, "git");
|
|
2535
|
+
await mkdir(bin);
|
|
2536
|
+
await writeFile(executable, "#!/bin/sh\nexec sleep 1\n", "utf8");
|
|
2537
|
+
await chmod(executable, 0o700);
|
|
2538
|
+
const originalPath = process.env.PATH;
|
|
2539
|
+
process.env.PATH = `${bin}${delimiter}${originalPath ?? ""}`;
|
|
2540
|
+
const tools = new PiToolRegistry();
|
|
2541
|
+
context.provide("piTools", tools);
|
|
2542
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
2543
|
+
try {
|
|
2544
|
+
await context.plugin(reviewerBotPlugin, { timeoutMs: 500 });
|
|
2545
|
+
const pending = namedTool(tools, "review_changes").execute("call-1", {}, undefined, undefined, {} as never);
|
|
2546
|
+
await expect(pending).rejects.toThrow(/timed out after 500 ms/iu);
|
|
2547
|
+
} finally {
|
|
2548
|
+
process.env.PATH = originalPath;
|
|
2549
|
+
}
|
|
2550
|
+
});
|
|
2551
|
+
|
|
938
2552
|
test("executes safe argv commands and blocks risky auto-mode commands without confirmation", async () => {
|
|
939
2553
|
const { context } = await createContext();
|
|
940
2554
|
const panels = new PiPluginUiRegistry();
|
|
@@ -943,8 +2557,8 @@ describe("Pi domain plugins", () => {
|
|
|
943
2557
|
context.provide("piPluginUi", panels);
|
|
944
2558
|
await context.plugin(autoModePlugin, { mode: "safe", timeoutMs: 5_000 });
|
|
945
2559
|
const tool = namedTool(tools, "auto_mode_exec");
|
|
946
|
-
await expect(tool.execute("call-1", { command: ["
|
|
947
|
-
details: { allowed: true, exitCode: 0
|
|
2560
|
+
await expect(tool.execute("call-1", { command: ["git", "--version"] }, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
2561
|
+
details: { allowed: true, exitCode: 0 },
|
|
948
2562
|
});
|
|
949
2563
|
await expect(tool.execute("call-2", { command: ["rm", "-f", "file"] }, undefined, undefined, {} as never)).rejects.toThrow(/confirm=true/);
|
|
950
2564
|
await expect(tool.execute("call-3", { command: ["sh", "-c", "echo bad"], confirm: true }, undefined, undefined, {} as never)).rejects.toThrow(
|
|
@@ -1073,6 +2687,96 @@ describe("Pi domain plugins", () => {
|
|
|
1073
2687
|
await expect(secondPanels.snapshot()).resolves.toMatchObject([{ id: "memory-panel", data: { count: 0 } }]);
|
|
1074
2688
|
});
|
|
1075
2689
|
|
|
2690
|
+
test("rolls back a failed memory write and recovers the mutation queue", async () => {
|
|
2691
|
+
const { context, agentDir } = await createContext();
|
|
2692
|
+
const memoryPath = join(agentDir, "memory.json");
|
|
2693
|
+
await writeFile(memoryPath, JSON.stringify({ version: 1, memories: [] }), "utf8");
|
|
2694
|
+
const tools = new PiToolRegistry();
|
|
2695
|
+
context.provide("piTools", tools);
|
|
2696
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
2697
|
+
await context.plugin(memoryPlugin);
|
|
2698
|
+
const set = namedTool(tools, "memory_set");
|
|
2699
|
+
await namedTool(tools, "memory_search").execute("load", { query: "initial" }, undefined, undefined, {} as never);
|
|
2700
|
+
await rm(memoryPath);
|
|
2701
|
+
await mkdir(memoryPath);
|
|
2702
|
+
|
|
2703
|
+
await expect(set.execute("call-1", { key: "failed", value: "must roll back" }, undefined, undefined, {} as never)).rejects.toThrow();
|
|
2704
|
+
await rm(memoryPath, { recursive: true });
|
|
2705
|
+
await expect(set.execute("call-2", { key: "saved", value: "queue recovered" }, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
2706
|
+
details: { key: "saved" },
|
|
2707
|
+
});
|
|
2708
|
+
const persisted = JSON.parse(await readFile(memoryPath, "utf8")) as { memories: Array<{ key: string }> };
|
|
2709
|
+
expect(persisted.memories.map((memory) => memory.key)).toEqual(["saved"]);
|
|
2710
|
+
expect((await readdir(agentDir)).filter((name) => name.startsWith(".memory.json.") && name.endsWith(".tmp"))).toEqual([]);
|
|
2711
|
+
});
|
|
2712
|
+
|
|
2713
|
+
test("serializes concurrent contexts writing the same memory file", async () => {
|
|
2714
|
+
const first = await createContext();
|
|
2715
|
+
const second = new Context();
|
|
2716
|
+
contexts.push(second);
|
|
2717
|
+
provideLaunchContext(second, { cwd: first.cwd, agentDir: first.agentDir, args: [], requestExit() {} });
|
|
2718
|
+
const firstTools = new PiToolRegistry();
|
|
2719
|
+
const secondTools = new PiToolRegistry();
|
|
2720
|
+
first.context.provide("piTools", firstTools);
|
|
2721
|
+
first.context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
2722
|
+
second.provide("piTools", secondTools);
|
|
2723
|
+
second.provide("piPluginUi", new PiPluginUiRegistry());
|
|
2724
|
+
await Promise.all([first.context.plugin(memoryPlugin), second.plugin(memoryPlugin)]);
|
|
2725
|
+
|
|
2726
|
+
await Promise.all([
|
|
2727
|
+
namedTool(firstTools, "memory_set").execute("call-a", { key: "concurrent-a", value: "first writer" }, undefined, undefined, {} as never),
|
|
2728
|
+
namedTool(secondTools, "memory_set").execute("call-b", { key: "concurrent-b", value: "second writer" }, undefined, undefined, {} as never),
|
|
2729
|
+
]);
|
|
2730
|
+
const persisted = JSON.parse(await readFile(join(first.agentDir, "memory.json"), "utf8")) as { memories: Array<{ key: string }> };
|
|
2731
|
+
expect(persisted.memories.map((memory) => memory.key).sort()).toEqual(["concurrent-a", "concurrent-b"]);
|
|
2732
|
+
});
|
|
2733
|
+
|
|
2734
|
+
test("rejects malformed memory records instead of loading partial data", async () => {
|
|
2735
|
+
const { context, agentDir } = await createContext();
|
|
2736
|
+
await writeFile(join(agentDir, "memory.json"), JSON.stringify({ version: 1, memories: [{ key: "partial", value: "missing metadata" }] }), "utf8");
|
|
2737
|
+
const tools = new PiToolRegistry();
|
|
2738
|
+
context.provide("piTools", tools);
|
|
2739
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
2740
|
+
await context.plugin(memoryPlugin);
|
|
2741
|
+
|
|
2742
|
+
await expect(namedTool(tools, "memory_search").execute("call-1", { query: "partial" }, undefined, undefined, {} as never)).rejects.toThrow(
|
|
2743
|
+
/invalid memories/iu,
|
|
2744
|
+
);
|
|
2745
|
+
});
|
|
2746
|
+
|
|
2747
|
+
test("does not read memory records through a symbolic link", async () => {
|
|
2748
|
+
const { context, cwd, agentDir } = await createContext();
|
|
2749
|
+
const now = new Date().toISOString();
|
|
2750
|
+
const outside = join(cwd, "outside-memory.json");
|
|
2751
|
+
await writeFile(
|
|
2752
|
+
outside,
|
|
2753
|
+
JSON.stringify({ version: 1, memories: [{ id: "secret", key: "secret", value: "outside value", tags: [], createdAt: now, updatedAt: now }] }),
|
|
2754
|
+
"utf8",
|
|
2755
|
+
);
|
|
2756
|
+
await symlink(outside, join(agentDir, "memory.json"));
|
|
2757
|
+
const tools = new PiToolRegistry();
|
|
2758
|
+
context.provide("piTools", tools);
|
|
2759
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
2760
|
+
await context.plugin(memoryPlugin);
|
|
2761
|
+
|
|
2762
|
+
await expect(namedTool(tools, "memory_search").execute("call-1", { query: "secret" }, undefined, undefined, {} as never)).rejects.toThrow(
|
|
2763
|
+
/symbolic link|regular file/iu,
|
|
2764
|
+
);
|
|
2765
|
+
});
|
|
2766
|
+
|
|
2767
|
+
test("bounds the memory file size according to the configured entry limit", async () => {
|
|
2768
|
+
const { context, agentDir } = await createContext();
|
|
2769
|
+
await writeFile(join(agentDir, "memory.json"), Buffer.alloc(128 * 1024));
|
|
2770
|
+
const tools = new PiToolRegistry();
|
|
2771
|
+
context.provide("piTools", tools);
|
|
2772
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
2773
|
+
await context.plugin(memoryPlugin, { maxEntries: 1 });
|
|
2774
|
+
|
|
2775
|
+
await expect(namedTool(tools, "memory_search").execute("call-1", { query: "memory" }, undefined, undefined, {} as never)).rejects.toThrow(
|
|
2776
|
+
/size|limit|exceeds/iu,
|
|
2777
|
+
);
|
|
2778
|
+
});
|
|
2779
|
+
|
|
1076
2780
|
test("persists typed graph memory nodes and relations across plugin lifecycles", async () => {
|
|
1077
2781
|
const first = await createContext();
|
|
1078
2782
|
const panels = new PiPluginUiRegistry();
|
|
@@ -1149,6 +2853,30 @@ describe("Pi domain plugins", () => {
|
|
|
1149
2853
|
expect(panel?.error).toMatch(/invalid nodes/iu);
|
|
1150
2854
|
});
|
|
1151
2855
|
|
|
2856
|
+
test("does not read graph memory through a symbolic link", async () => {
|
|
2857
|
+
const { context, cwd, agentDir } = await createContext();
|
|
2858
|
+
const now = new Date().toISOString();
|
|
2859
|
+
const outside = join(cwd, "outside-graph.json");
|
|
2860
|
+
await writeFile(
|
|
2861
|
+
outside,
|
|
2862
|
+
JSON.stringify({
|
|
2863
|
+
version: 1,
|
|
2864
|
+
nodes: [{ id: "secret", kind: "event", label: "Secret event", summary: "outside value", createdAt: now, updatedAt: now }],
|
|
2865
|
+
relations: [],
|
|
2866
|
+
}),
|
|
2867
|
+
"utf8",
|
|
2868
|
+
);
|
|
2869
|
+
await symlink(outside, join(agentDir, "graph-memory.json"));
|
|
2870
|
+
const tools = new PiToolRegistry();
|
|
2871
|
+
context.provide("piTools", tools);
|
|
2872
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
2873
|
+
await context.plugin(graphMemoryPlugin);
|
|
2874
|
+
|
|
2875
|
+
await expect(namedTool(tools, "graph_memory_search").execute("call-1", { query: "secret" }, undefined, undefined, {} as never)).rejects.toThrow(
|
|
2876
|
+
/symbolic link|regular file/iu,
|
|
2877
|
+
);
|
|
2878
|
+
});
|
|
2879
|
+
|
|
1152
2880
|
test("serializes concurrent contexts writing the same graph memory file", async () => {
|
|
1153
2881
|
const first = await createContext();
|
|
1154
2882
|
const second = new Context();
|
|
@@ -1213,6 +2941,30 @@ describe("Pi domain plugins", () => {
|
|
|
1213
2941
|
await expect(secondPanels.snapshot()).resolves.toMatchObject([{ id: "taskboard-panel", data: { total: 1, counts: { done: 1 } } }]);
|
|
1214
2942
|
});
|
|
1215
2943
|
|
|
2944
|
+
test("rejects a taskboard database symlink before writing outside the agent directory", async () => {
|
|
2945
|
+
if (process.platform === "win32") return;
|
|
2946
|
+
const { context, cwd, agentDir } = await createContext();
|
|
2947
|
+
const outside = join(cwd, "outside.sqlite");
|
|
2948
|
+
const external = new DatabaseSync(outside);
|
|
2949
|
+
external.exec("CREATE TABLE sentinel (value TEXT NOT NULL); INSERT INTO sentinel VALUES ('unchanged')");
|
|
2950
|
+
external.close();
|
|
2951
|
+
await symlink(outside, join(agentDir, "taskboard.sqlite"));
|
|
2952
|
+
const tools = new PiToolRegistry();
|
|
2953
|
+
context.provide("piTools", tools);
|
|
2954
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
2955
|
+
await context.plugin(taskboardPlugin);
|
|
2956
|
+
|
|
2957
|
+
await expect(namedTool(tools, "taskboard_create").execute("call-1", { title: "Must stay local" }, undefined, undefined, {} as never)).rejects.toThrow(
|
|
2958
|
+
/symbolic link|regular file/iu,
|
|
2959
|
+
);
|
|
2960
|
+
const verified = new DatabaseSync(outside, { readOnly: true });
|
|
2961
|
+
try {
|
|
2962
|
+
expect(verified.prepare("SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name").all()).toEqual([{ name: "sentinel" }]);
|
|
2963
|
+
} finally {
|
|
2964
|
+
verified.close();
|
|
2965
|
+
}
|
|
2966
|
+
});
|
|
2967
|
+
|
|
1216
2968
|
test("generates validated Mermaid diagrams through the canvas draw plugin", async () => {
|
|
1217
2969
|
const { context } = await createContext();
|
|
1218
2970
|
const panels = new PiPluginUiRegistry();
|
|
@@ -1268,6 +3020,60 @@ describe("Pi domain plugins", () => {
|
|
|
1268
3020
|
await expect(panels.snapshot()).resolves.toMatchObject([{ id: "image-compressor-panel", data: { last: { outputPath: "compressed.png", saved: true } } }]);
|
|
1269
3021
|
});
|
|
1270
3022
|
|
|
3023
|
+
test("rejects PNG image data that expands beyond the IHDR-declared scanline size", async () => {
|
|
3024
|
+
const { context, cwd } = await createContext();
|
|
3025
|
+
const panels = new PiPluginUiRegistry();
|
|
3026
|
+
const tools = new PiToolRegistry();
|
|
3027
|
+
const header = Buffer.alloc(13);
|
|
3028
|
+
header.writeUInt32BE(1, 0);
|
|
3029
|
+
header.writeUInt32BE(1, 4);
|
|
3030
|
+
header[8] = 8;
|
|
3031
|
+
header[9] = 0;
|
|
3032
|
+
await writeFile(join(cwd, "bomb.png"), testPng(header, Buffer.alloc(1024 * 1024)));
|
|
3033
|
+
context.provide("piTools", tools);
|
|
3034
|
+
context.provide("piPluginUi", panels);
|
|
3035
|
+
await context.plugin(imageCompressorPlugin);
|
|
3036
|
+
|
|
3037
|
+
await expect(
|
|
3038
|
+
namedTool(tools, "image_compress").execute(
|
|
3039
|
+
"call-1",
|
|
3040
|
+
{ path: "bomb.png", outputPath: "compressed.png", confirm: true },
|
|
3041
|
+
undefined,
|
|
3042
|
+
undefined,
|
|
3043
|
+
{} as never,
|
|
3044
|
+
),
|
|
3045
|
+
).rejects.toThrow(/image data|scanline|decompress/iu);
|
|
3046
|
+
await expect((await import("node:fs/promises")).stat(join(cwd, "compressed.png"))).rejects.toMatchObject({ code: "ENOENT" });
|
|
3047
|
+
});
|
|
3048
|
+
|
|
3049
|
+
test("preserves PNG ancillary chunks while recompressing image data", async () => {
|
|
3050
|
+
const { context, cwd } = await createContext();
|
|
3051
|
+
const panels = new PiPluginUiRegistry();
|
|
3052
|
+
const tools = new PiToolRegistry();
|
|
3053
|
+
const header = Buffer.alloc(13);
|
|
3054
|
+
header.writeUInt32BE(1, 0);
|
|
3055
|
+
header.writeUInt32BE(1, 4);
|
|
3056
|
+
header[8] = 8;
|
|
3057
|
+
header[9] = 0;
|
|
3058
|
+
const gamma = testPngChunk("gAMA", Buffer.from([0, 0, 177, 143]));
|
|
3059
|
+
const text = testPngChunk("tEXt", Buffer.from("Comment\0production metadata", "latin1"));
|
|
3060
|
+
await writeFile(join(cwd, "source.png"), testPng(header, Buffer.from([0, 127]), [gamma], [text]));
|
|
3061
|
+
context.provide("piTools", tools);
|
|
3062
|
+
context.provide("piPluginUi", panels);
|
|
3063
|
+
await context.plugin(imageCompressorPlugin);
|
|
3064
|
+
|
|
3065
|
+
await namedTool(tools, "image_compress").execute(
|
|
3066
|
+
"call-1",
|
|
3067
|
+
{ path: "source.png", outputPath: "compressed.png", confirm: true },
|
|
3068
|
+
undefined,
|
|
3069
|
+
undefined,
|
|
3070
|
+
{} as never,
|
|
3071
|
+
);
|
|
3072
|
+
const output = await readFile(join(cwd, "compressed.png"));
|
|
3073
|
+
expect(output.includes(gamma)).toBe(true);
|
|
3074
|
+
expect(output.includes(text)).toBe(true);
|
|
3075
|
+
});
|
|
3076
|
+
|
|
1271
3077
|
test("searches bounded workspace text and reports file locations", async () => {
|
|
1272
3078
|
const { context, cwd } = await createContext();
|
|
1273
3079
|
const panels = new PiPluginUiRegistry();
|
|
@@ -1324,7 +3130,7 @@ describe("Pi domain plugins", () => {
|
|
|
1324
3130
|
});
|
|
1325
3131
|
|
|
1326
3132
|
test("packages selected source files into a local skill directory", async () => {
|
|
1327
|
-
const { context, cwd } = await createContext();
|
|
3133
|
+
const { context, cwd, agentDir } = await createContext();
|
|
1328
3134
|
const panels = new PiPluginUiRegistry();
|
|
1329
3135
|
const tools = new PiToolRegistry();
|
|
1330
3136
|
await mkdir(join(cwd, "src"), { recursive: true });
|
|
@@ -1339,6 +3145,25 @@ describe("Pi domain plugins", () => {
|
|
|
1339
3145
|
await expect(readFile(join(cwd, ".pi", "skills", "parser-guide", "SKILL.md"), "utf8")).resolves.toContain("Explain parser conventions");
|
|
1340
3146
|
await expect(readFile(join(cwd, ".pi", "skills", "parser-guide", "references", "src", "parser.ts"), "utf8")).resolves.toContain("parse");
|
|
1341
3147
|
await expect(panels.snapshot()).resolves.toMatchObject([{ id: "code2skill-panel", data: { generated: 1, latest: { slug: "parser-guide" } } }]);
|
|
3148
|
+
await expect(
|
|
3149
|
+
tool.execute("call-2", { name: "Parser Guide", description: "Do not overwrite", files: ["src/parser.ts"] }, undefined, undefined, {} as never),
|
|
3150
|
+
).rejects.toThrow(/already exists/);
|
|
3151
|
+
|
|
3152
|
+
await writeFile(join(agentDir, "outside.ts"), "export const secret = true;\n", "utf8");
|
|
3153
|
+
await symlink(join(agentDir, "outside.ts"), join(cwd, "src", "outside.ts"));
|
|
3154
|
+
await expect(
|
|
3155
|
+
tool.execute("call-3", { name: "Outside Source", description: "Must stay bounded", files: ["src/outside.ts"] }, undefined, undefined, {} as never),
|
|
3156
|
+
).rejects.toThrow(/inside the workspace/);
|
|
3157
|
+
|
|
3158
|
+
await expect(
|
|
3159
|
+
tool.execute("call-4", { name: "Directory Source", description: "Only regular files are allowed", files: ["src"] }, undefined, undefined, {} as never),
|
|
3160
|
+
).rejects.toThrow(/regular file/iu);
|
|
3161
|
+
|
|
3162
|
+
await symlink(agentDir, join(cwd, ".pi", "skills", "linked-pack"));
|
|
3163
|
+
await expect(
|
|
3164
|
+
tool.execute("call-5", { name: "Linked Pack", description: "Must stay bounded", files: ["src/parser.ts"] }, undefined, undefined, {} as never),
|
|
3165
|
+
).rejects.toThrow(/inside the workspace/);
|
|
3166
|
+
await expect(readFile(join(agentDir, "SKILL.md"), "utf8")).rejects.toMatchObject({ code: "ENOENT" });
|
|
1342
3167
|
});
|
|
1343
3168
|
|
|
1344
3169
|
test("persists named session tabs without deleting session files", async () => {
|
|
@@ -1358,11 +3183,110 @@ describe("Pi domain plugins", () => {
|
|
|
1358
3183
|
details: { tabs: [{ label: "API 回归", pinned: true }] },
|
|
1359
3184
|
});
|
|
1360
3185
|
await expect(readFile(join(agentDir, "session-tabs.json"), "utf8")).resolves.toContain("API 回归");
|
|
3186
|
+
expect((await stat(join(agentDir, "session-tabs.json"))).mode & 0o777).toBe(0o600);
|
|
1361
3187
|
await expect(panels.snapshot()).resolves.toMatchObject([
|
|
1362
3188
|
{ id: "tab-manager-panel", data: { activeId: "session-a", tabs: [{ label: "API 回归" }], writes: 2 } },
|
|
1363
3189
|
]);
|
|
1364
3190
|
});
|
|
1365
3191
|
|
|
3192
|
+
test("rejects a corrupted session tab store instead of replacing it", async () => {
|
|
3193
|
+
const { context, agentDir } = await createContext();
|
|
3194
|
+
await writeFile(join(agentDir, "session-tabs.json"), "{not-json", "utf8");
|
|
3195
|
+
const tools = new PiToolRegistry();
|
|
3196
|
+
context.provide("piSession", { manager: { getSessionId: () => "session-a", getSessionFile: () => join(agentDir, "session-a.jsonl") } } as never);
|
|
3197
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
3198
|
+
context.provide("piTools", tools);
|
|
3199
|
+
let activationError: unknown;
|
|
3200
|
+
|
|
3201
|
+
try {
|
|
3202
|
+
await context.plugin(tabManagerPlugin);
|
|
3203
|
+
} catch (error) {
|
|
3204
|
+
activationError = error;
|
|
3205
|
+
}
|
|
3206
|
+
expect(activationError).toBeInstanceOf(Error);
|
|
3207
|
+
if (!(activationError instanceof Error)) throw new Error("Expected corrupt tab store rejection");
|
|
3208
|
+
expect(activationError.message).toMatch(/session tab store.*invalid JSON/iu);
|
|
3209
|
+
await expect(readFile(join(agentDir, "session-tabs.json"), "utf8")).resolves.toBe("{not-json");
|
|
3210
|
+
});
|
|
3211
|
+
|
|
3212
|
+
test("does not load session tabs through a symbolic link", async () => {
|
|
3213
|
+
const { context, cwd, agentDir } = await createContext();
|
|
3214
|
+
const outside = join(cwd, "outside-tabs.json");
|
|
3215
|
+
await writeFile(outside, JSON.stringify({ tabs: [], activeId: null }), "utf8");
|
|
3216
|
+
await symlink(outside, join(agentDir, "session-tabs.json"));
|
|
3217
|
+
context.provide("piSession", { manager: { getSessionId: () => "session-a", getSessionFile: () => join(agentDir, "session-a.jsonl") } } as never);
|
|
3218
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
3219
|
+
context.provide("piTools", new PiToolRegistry());
|
|
3220
|
+
|
|
3221
|
+
let activationError: unknown;
|
|
3222
|
+
try {
|
|
3223
|
+
await context.plugin(tabManagerPlugin);
|
|
3224
|
+
} catch (error) {
|
|
3225
|
+
activationError = error;
|
|
3226
|
+
}
|
|
3227
|
+
expect(activationError).toBeInstanceOf(Error);
|
|
3228
|
+
if (!(activationError instanceof Error)) throw new Error("Expected linked tab store rejection");
|
|
3229
|
+
expect(activationError.message).toMatch(/symbolic link|regular file/iu);
|
|
3230
|
+
});
|
|
3231
|
+
|
|
3232
|
+
test("rejects an oversized session tab store before parsing it", async () => {
|
|
3233
|
+
const { context, agentDir } = await createContext();
|
|
3234
|
+
await writeFile(join(agentDir, "session-tabs.json"), Buffer.alloc(1024 * 1024 + 1));
|
|
3235
|
+
context.provide("piSession", { manager: { getSessionId: () => "session-a", getSessionFile: () => join(agentDir, "session-a.jsonl") } } as never);
|
|
3236
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
3237
|
+
context.provide("piTools", new PiToolRegistry());
|
|
3238
|
+
|
|
3239
|
+
let activationError: unknown;
|
|
3240
|
+
try {
|
|
3241
|
+
await context.plugin(tabManagerPlugin);
|
|
3242
|
+
} catch (error) {
|
|
3243
|
+
activationError = error;
|
|
3244
|
+
}
|
|
3245
|
+
expect(activationError).toBeInstanceOf(Error);
|
|
3246
|
+
if (!(activationError instanceof Error)) throw new Error("Expected oversized tab store rejection");
|
|
3247
|
+
expect(activationError.message).toMatch(/1 MiB|size|limit/iu);
|
|
3248
|
+
});
|
|
3249
|
+
|
|
3250
|
+
test("rejects oversized labels before changing the session tab store", async () => {
|
|
3251
|
+
const { context, agentDir } = await createContext();
|
|
3252
|
+
const tools = new PiToolRegistry();
|
|
3253
|
+
context.provide("piSession", { manager: { getSessionId: () => "session-a", getSessionFile: () => join(agentDir, "session-a.jsonl") } } as never);
|
|
3254
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
3255
|
+
context.provide("piTools", tools);
|
|
3256
|
+
await context.plugin(tabManagerPlugin);
|
|
3257
|
+
const tool = namedTool(tools, "session_tab_manage");
|
|
3258
|
+
await tool.execute("call-1", { action: "pin", label: "Valid" }, undefined, undefined, {} as never);
|
|
3259
|
+
|
|
3260
|
+
await expect(tool.execute("call-2", { action: "pin", label: "x".repeat(121) }, undefined, undefined, {} as never)).rejects.toThrow(/label.*1 to 120/iu);
|
|
3261
|
+
const persisted = JSON.parse(await readFile(join(agentDir, "session-tabs.json"), "utf8")) as { tabs: Array<{ label: string }> };
|
|
3262
|
+
expect(persisted.tabs[0]?.label).toBe("Valid");
|
|
3263
|
+
});
|
|
3264
|
+
|
|
3265
|
+
test("serializes concurrent contexts writing the same session tab store", async () => {
|
|
3266
|
+
const first = await createContext();
|
|
3267
|
+
const second = new Context();
|
|
3268
|
+
contexts.push(second);
|
|
3269
|
+
provideLaunchContext(second, { cwd: first.cwd, agentDir: first.agentDir, args: [], requestExit() {} });
|
|
3270
|
+
const firstTools = new PiToolRegistry();
|
|
3271
|
+
const secondTools = new PiToolRegistry();
|
|
3272
|
+
first.context.provide("piSession", {
|
|
3273
|
+
manager: { getSessionId: () => "session-a", getSessionFile: () => join(first.agentDir, "session-a.jsonl") },
|
|
3274
|
+
} as never);
|
|
3275
|
+
second.provide("piSession", { manager: { getSessionId: () => "session-b", getSessionFile: () => join(first.agentDir, "session-b.jsonl") } } as never);
|
|
3276
|
+
first.context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
3277
|
+
first.context.provide("piTools", firstTools);
|
|
3278
|
+
second.provide("piPluginUi", new PiPluginUiRegistry());
|
|
3279
|
+
second.provide("piTools", secondTools);
|
|
3280
|
+
await Promise.all([first.context.plugin(tabManagerPlugin), second.plugin(tabManagerPlugin)]);
|
|
3281
|
+
|
|
3282
|
+
await Promise.all([
|
|
3283
|
+
namedTool(firstTools, "session_tab_manage").execute("call-a", { action: "pin", label: "Session A" }, undefined, undefined, {} as never),
|
|
3284
|
+
namedTool(secondTools, "session_tab_manage").execute("call-b", { action: "pin", label: "Session B" }, undefined, undefined, {} as never),
|
|
3285
|
+
]);
|
|
3286
|
+
const persisted = JSON.parse(await readFile(join(first.agentDir, "session-tabs.json"), "utf8")) as { tabs: Array<{ id: string }> };
|
|
3287
|
+
expect(persisted.tabs.map((tab) => tab.id).sort()).toEqual(["session-a", "session-b"]);
|
|
3288
|
+
});
|
|
3289
|
+
|
|
1366
3290
|
test("renders structured GenUI blocks without accepting executable markup", async () => {
|
|
1367
3291
|
const { context } = await createContext();
|
|
1368
3292
|
const panels = new PiPluginUiRegistry();
|
|
@@ -1398,7 +3322,7 @@ describe("Pi domain plugins", () => {
|
|
|
1398
3322
|
const tools = new PiToolRegistry();
|
|
1399
3323
|
context.provide("piPluginUi", panels);
|
|
1400
3324
|
context.provide("piTools", tools);
|
|
1401
|
-
await context.plugin(anchoredStandardPlugin, { maxToolCalls: 2 });
|
|
3325
|
+
await context.plugin(anchoredStandardPlugin, { maxToolCalls: 2, allowedTools: [" read ", "read"] });
|
|
1402
3326
|
const check = namedTool(tools, "trajectory_anchor_check");
|
|
1403
3327
|
context.emit("pi/session-event", { type: "tool_execution_start", toolCallId: "orphan", toolName: "bash" } as never);
|
|
1404
3328
|
context.emit("pi/session-event", { type: "agent_start" } as never);
|
|
@@ -1406,10 +3330,174 @@ describe("Pi domain plugins", () => {
|
|
|
1406
3330
|
context.emit("pi/session-event", { type: "tool_execution_start", toolCallId: "two", toolName: "read" } as never);
|
|
1407
3331
|
context.emit("pi/session-event", { type: "tool_execution_start", toolCallId: "three", toolName: "read" } as never);
|
|
1408
3332
|
context.emit("pi/session-event", { type: "agent_end", messages: [], willRetry: false } as never);
|
|
1409
|
-
await
|
|
3333
|
+
const result = await check.execute("call-1", {}, undefined, undefined, {} as never);
|
|
3334
|
+
const details = result.details as { status: string; toolCalls: number; violations: Array<{ code: string }> };
|
|
3335
|
+
expect(details).toMatchObject({ status: "violated", toolCalls: 3, allowedTools: ["read"] });
|
|
3336
|
+
expect(details.violations.map((violation) => violation.code)).toContain("disallowed_tool");
|
|
1410
3337
|
await expect(panels.snapshot()).resolves.toMatchObject([{ id: "anchored-standard-panel", data: { status: "violated", events: 6, toolCalls: 3 } }]);
|
|
1411
3338
|
});
|
|
1412
3339
|
|
|
3340
|
+
test("reports clean anchored-standard lifecycle transitions", async () => {
|
|
3341
|
+
const { context } = await createContext();
|
|
3342
|
+
const panels = new PiPluginUiRegistry();
|
|
3343
|
+
const tools = new PiToolRegistry();
|
|
3344
|
+
context.provide("piPluginUi", panels);
|
|
3345
|
+
context.provide("piTools", tools);
|
|
3346
|
+
await context.plugin(anchoredStandardPlugin, { allowedTools: ["read"] });
|
|
3347
|
+
const check = namedTool(tools, "trajectory_anchor_check");
|
|
3348
|
+
await expect(check.execute("idle", {}, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
3349
|
+
details: { status: "idle", events: 0, toolCalls: 0, allowedTools: ["read"], violations: [] },
|
|
3350
|
+
});
|
|
3351
|
+
|
|
3352
|
+
context.emit("pi/session-event", { type: "agent_start" } as never);
|
|
3353
|
+
context.emit("pi/session-event", { type: "tool_execution_start", toolCallId: "one", toolName: "read" } as never);
|
|
3354
|
+
await expect(check.execute("anchored", {}, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
3355
|
+
details: { status: "anchored", events: 2, toolCalls: 1, violations: [] },
|
|
3356
|
+
});
|
|
3357
|
+
|
|
3358
|
+
context.emit("pi/session-event", { type: "agent_end", messages: [], willRetry: false } as never);
|
|
3359
|
+
await expect(check.execute("ended", {}, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
3360
|
+
details: { status: "idle", events: 3, toolCalls: 1, violations: [] },
|
|
3361
|
+
});
|
|
3362
|
+
});
|
|
3363
|
+
|
|
3364
|
+
test("reports nested and orphan anchored-standard lifecycle events once per violation code", async () => {
|
|
3365
|
+
const { context } = await createContext();
|
|
3366
|
+
const panels = new PiPluginUiRegistry();
|
|
3367
|
+
const tools = new PiToolRegistry();
|
|
3368
|
+
context.provide("piPluginUi", panels);
|
|
3369
|
+
context.provide("piTools", tools);
|
|
3370
|
+
await context.plugin(anchoredStandardPlugin);
|
|
3371
|
+
const check = namedTool(tools, "trajectory_anchor_check");
|
|
3372
|
+
context.emit("pi/session-event", { type: "agent_end", messages: [], willRetry: false } as never);
|
|
3373
|
+
context.emit("pi/session-event", { type: "agent_end", messages: [], willRetry: false } as never);
|
|
3374
|
+
context.emit("pi/session-event", { type: "agent_start" } as never);
|
|
3375
|
+
context.emit("pi/session-event", { type: "agent_start" } as never);
|
|
3376
|
+
|
|
3377
|
+
const result = await check.execute("check", {}, undefined, undefined, {} as never);
|
|
3378
|
+
const details = result.details as { violations: Array<{ code: string }> };
|
|
3379
|
+
expect(details.violations.map((violation) => violation.code)).toEqual(["orphan_end", "nested_run"]);
|
|
3380
|
+
});
|
|
3381
|
+
|
|
3382
|
+
test("stops anchored-standard event inspection when its plugin context is disposed", async () => {
|
|
3383
|
+
const { context } = await createContext();
|
|
3384
|
+
const panels = new PiPluginUiRegistry();
|
|
3385
|
+
const tools = new PiToolRegistry();
|
|
3386
|
+
context.provide("piPluginUi", panels);
|
|
3387
|
+
context.provide("piTools", tools);
|
|
3388
|
+
await context.plugin(anchoredStandardPlugin);
|
|
3389
|
+
const check = namedTool(tools, "trajectory_anchor_check");
|
|
3390
|
+
|
|
3391
|
+
await context.fiber.dispose();
|
|
3392
|
+
context.emit("pi/session-event", { type: "agent_start" } as never);
|
|
3393
|
+
|
|
3394
|
+
await expect(check.execute("check", {}, undefined, undefined, {} as never)).resolves.toMatchObject({ details: { status: "idle", events: 0 } });
|
|
3395
|
+
expect(tools.snapshot().customTools).toEqual([]);
|
|
3396
|
+
await expect(panels.snapshot()).resolves.toEqual([]);
|
|
3397
|
+
});
|
|
3398
|
+
|
|
3399
|
+
test("normalizes a non-finite anchored-standard tool budget to the default", async () => {
|
|
3400
|
+
const { context } = await createContext();
|
|
3401
|
+
const panels = new PiPluginUiRegistry();
|
|
3402
|
+
const tools = new PiToolRegistry();
|
|
3403
|
+
context.provide("piPluginUi", panels);
|
|
3404
|
+
context.provide("piTools", tools);
|
|
3405
|
+
await context.plugin(anchoredStandardPlugin, { maxToolCalls: Number.NaN });
|
|
3406
|
+
|
|
3407
|
+
await expect(namedTool(tools, "trajectory_anchor_check").execute("call-1", {}, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
3408
|
+
details: { status: "idle", maxToolCalls: 64 },
|
|
3409
|
+
});
|
|
3410
|
+
});
|
|
3411
|
+
|
|
3412
|
+
test("clamps and truncates anchored-standard tool budgets", async () => {
|
|
3413
|
+
for (const [configured, expected] of [
|
|
3414
|
+
[0, 1],
|
|
3415
|
+
[3.9, 3],
|
|
3416
|
+
[999, 512],
|
|
3417
|
+
] as const) {
|
|
3418
|
+
const { context } = await createContext();
|
|
3419
|
+
const tools = new PiToolRegistry();
|
|
3420
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
3421
|
+
context.provide("piTools", tools);
|
|
3422
|
+
await context.plugin(anchoredStandardPlugin, { maxToolCalls: configured });
|
|
3423
|
+
await expect(namedTool(tools, "trajectory_anchor_check").execute("check", {}, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
3424
|
+
details: { maxToolCalls: expected },
|
|
3425
|
+
});
|
|
3426
|
+
}
|
|
3427
|
+
});
|
|
3428
|
+
|
|
3429
|
+
test("leaves tool names unrestricted when anchored-standard has no explicit allowlist", async () => {
|
|
3430
|
+
const { context } = await createContext();
|
|
3431
|
+
const tools = new PiToolRegistry();
|
|
3432
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
3433
|
+
context.provide("piTools", tools);
|
|
3434
|
+
await context.plugin(anchoredStandardPlugin);
|
|
3435
|
+
context.emit("pi/session-event", { type: "agent_start" } as never);
|
|
3436
|
+
context.emit("pi/session-event", { type: "tool_execution_start", toolCallId: "one", toolName: "custom_tool" } as never);
|
|
3437
|
+
|
|
3438
|
+
await expect(namedTool(tools, "trajectory_anchor_check").execute("check", {}, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
3439
|
+
details: { status: "anchored", allowedTools: [], violations: [] },
|
|
3440
|
+
});
|
|
3441
|
+
});
|
|
3442
|
+
|
|
3443
|
+
test("rejects an oversized anchored-standard tool allowlist before registering", async () => {
|
|
3444
|
+
const { context } = await createContext();
|
|
3445
|
+
const panels = new PiPluginUiRegistry();
|
|
3446
|
+
const tools = new PiToolRegistry();
|
|
3447
|
+
context.provide("piPluginUi", panels);
|
|
3448
|
+
context.provide("piTools", tools);
|
|
3449
|
+
|
|
3450
|
+
let activationError: unknown;
|
|
3451
|
+
try {
|
|
3452
|
+
await context.plugin(anchoredStandardPlugin, { allowedTools: Array.from({ length: 513 }, (_, index) => `tool-${index}`) });
|
|
3453
|
+
} catch (error) {
|
|
3454
|
+
activationError = error;
|
|
3455
|
+
}
|
|
3456
|
+
expect(activationError).toBeInstanceOf(Error);
|
|
3457
|
+
if (!(activationError instanceof Error)) throw new Error("Expected anchored-standard activation to reject an oversized allowlist");
|
|
3458
|
+
expect(activationError.message).toMatch(/512/iu);
|
|
3459
|
+
expect(tools.snapshot().customTools).toEqual([]);
|
|
3460
|
+
await expect(panels.snapshot()).resolves.toEqual([]);
|
|
3461
|
+
});
|
|
3462
|
+
|
|
3463
|
+
test("rejects an oversized anchored-standard tool name before registering", async () => {
|
|
3464
|
+
const { context } = await createContext();
|
|
3465
|
+
const panels = new PiPluginUiRegistry();
|
|
3466
|
+
const tools = new PiToolRegistry();
|
|
3467
|
+
context.provide("piPluginUi", panels);
|
|
3468
|
+
context.provide("piTools", tools);
|
|
3469
|
+
let activationError: unknown;
|
|
3470
|
+
try {
|
|
3471
|
+
await context.plugin(anchoredStandardPlugin, { allowedTools: ["t".repeat(129)] });
|
|
3472
|
+
} catch (error) {
|
|
3473
|
+
activationError = error;
|
|
3474
|
+
}
|
|
3475
|
+
expect(activationError).toBeInstanceOf(Error);
|
|
3476
|
+
if (!(activationError instanceof Error)) throw new Error("Expected anchored-standard activation to reject an oversized tool name");
|
|
3477
|
+
expect(activationError.message).toMatch(/128/iu);
|
|
3478
|
+
expect(tools.snapshot().customTools).toEqual([]);
|
|
3479
|
+
await expect(panels.snapshot()).resolves.toEqual([]);
|
|
3480
|
+
});
|
|
3481
|
+
|
|
3482
|
+
test("rejects a blank anchored-standard tool name instead of disabling the allowlist", async () => {
|
|
3483
|
+
const { context } = await createContext();
|
|
3484
|
+
const panels = new PiPluginUiRegistry();
|
|
3485
|
+
const tools = new PiToolRegistry();
|
|
3486
|
+
context.provide("piPluginUi", panels);
|
|
3487
|
+
context.provide("piTools", tools);
|
|
3488
|
+
let activationError: unknown;
|
|
3489
|
+
try {
|
|
3490
|
+
await context.plugin(anchoredStandardPlugin, { allowedTools: [" "] });
|
|
3491
|
+
} catch (error) {
|
|
3492
|
+
activationError = error;
|
|
3493
|
+
}
|
|
3494
|
+
expect(activationError).toBeInstanceOf(Error);
|
|
3495
|
+
if (!(activationError instanceof Error)) throw new Error("Expected anchored-standard activation to reject a blank tool name");
|
|
3496
|
+
expect(activationError.message).toMatch(/match regexp|non-whitespace/iu);
|
|
3497
|
+
expect(tools.snapshot().customTools).toEqual([]);
|
|
3498
|
+
await expect(panels.snapshot()).resolves.toEqual([]);
|
|
3499
|
+
});
|
|
3500
|
+
|
|
1413
3501
|
test("blocks telemetry by default and never stores event properties", async () => {
|
|
1414
3502
|
const { context } = await createContext();
|
|
1415
3503
|
const panels = new PiPluginUiRegistry();
|
|
@@ -1476,7 +3564,7 @@ describe("Pi domain plugins", () => {
|
|
|
1476
3564
|
const server = join(cwd, "mcp-fixture.mjs");
|
|
1477
3565
|
await writeFile(
|
|
1478
3566
|
server,
|
|
1479
|
-
`let buffer =
|
|
3567
|
+
`let buffer = ""; const handle = (message) => { if (message.id === undefined) return; let result = {}; if (message.method === "initialize") result = { protocolVersion: "2025-06-18", capabilities: { resources: {}, prompts: {} }, serverInfo: { name: "fixture", version: "1" } }; if (message.method === "tools/list") result = { tools: [{ name: "echo", description: "Echo text", inputSchema: { type: "object" } }] }; if (message.method === "tools/call") result = { content: [{ type: "text", text: String(message.params.arguments?.text ?? "") }], isError: false }; if (message.method === "resources/list") result = { resources: [{ uri: "fixture://readme", name: "Readme", mimeType: "text/plain" }] }; if (message.method === "resources/read") result = { contents: [{ uri: message.params.uri, mimeType: "text/plain", text: "resource body" }] }; if (message.method === "prompts/list") result = { prompts: [{ name: "review", description: "Review prompt", arguments: [] }] }; if (message.method === "prompts/get") result = { description: "Review prompt", messages: [{ role: "user", content: { type: "text", text: "Review this" } }] }; process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id: message.id, result }) + "\\n"); }; process.stdin.setEncoding("utf8"); process.stdin.on("data", (chunk) => { buffer += chunk; while (true) { const newline = buffer.indexOf("\\n"); if (newline < 0) break; const line = buffer.slice(0, newline).trim(); buffer = buffer.slice(newline + 1); if (line !== "") handle(JSON.parse(line)); } });`,
|
|
1480
3568
|
"utf8",
|
|
1481
3569
|
);
|
|
1482
3570
|
const panels = new PiPluginUiRegistry();
|
|
@@ -1541,6 +3629,88 @@ describe("Pi domain plugins", () => {
|
|
|
1541
3629
|
);
|
|
1542
3630
|
});
|
|
1543
3631
|
|
|
3632
|
+
test("initializes a spec-compliant MCP stdio server with current client metadata", async () => {
|
|
3633
|
+
const { context, cwd } = await createContext();
|
|
3634
|
+
const server = join(cwd, "mcp-newline-fixture.mjs");
|
|
3635
|
+
const packageMetadata = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8")) as { version: string };
|
|
3636
|
+
await writeFile(
|
|
3637
|
+
server,
|
|
3638
|
+
`let buffer = ""; let clientVersion = ""; const send = (message) => process.stdout.write(JSON.stringify(message) + "\\n"); process.stdin.setEncoding("utf8"); process.stdin.on("data", (chunk) => { buffer += chunk; while (true) { const newline = buffer.indexOf("\\n"); if (newline < 0) break; const line = buffer.slice(0, newline).trim(); buffer = buffer.slice(newline + 1); if (line === "") continue; if (/^content-length:/i.test(line)) { send({ jsonrpc: "2.0", id: 1, error: { code: -32600, message: "Content-Length framing is not valid MCP stdio" } }); continue; } const message = JSON.parse(line); if (message.method === "initialize") { clientVersion = String(message.params.clientInfo.version); send({ jsonrpc: "2.0", id: message.id, result: { protocolVersion: "2025-06-18", capabilities: {}, serverInfo: { name: "newline-fixture", version: "1" } } }); } else if (message.method === "tools/list") send({ jsonrpc: "2.0", id: message.id, result: { tools: [{ name: "metadata", description: clientVersion, inputSchema: { type: "object" } }] } }); } });`,
|
|
3639
|
+
"utf8",
|
|
3640
|
+
);
|
|
3641
|
+
const panels = new PiPluginUiRegistry();
|
|
3642
|
+
const tools = new PiToolRegistry();
|
|
3643
|
+
context.provide("piTools", tools);
|
|
3644
|
+
context.provide("piPluginUi", panels);
|
|
3645
|
+
await context.plugin(mcpClientPlugin);
|
|
3646
|
+
|
|
3647
|
+
await expect(
|
|
3648
|
+
namedTool(tools, "mcp_list_tools").execute("call-1", { command: [process.execPath, server] }, undefined, undefined, {} as never),
|
|
3649
|
+
).resolves.toMatchObject({
|
|
3650
|
+
details: { tools: [{ name: "metadata", description: packageMetadata.version }] },
|
|
3651
|
+
});
|
|
3652
|
+
});
|
|
3653
|
+
|
|
3654
|
+
test("rejects MCP response frames larger than 1 MiB", async () => {
|
|
3655
|
+
const { context, cwd } = await createContext();
|
|
3656
|
+
const server = join(cwd, "mcp-oversized-fixture.mjs");
|
|
3657
|
+
await writeFile(
|
|
3658
|
+
server,
|
|
3659
|
+
`let buffer = Buffer.alloc(0); const send = (message) => process.stdout.write(JSON.stringify(message) + "\\n"); const handle = (message) => { if (message.method === "initialize") { send({ jsonrpc: "2.0", id: message.id, result: { protocolVersion: "2025-06-18", capabilities: {}, serverInfo: { name: "oversized-fixture", version: "1" } } }); return; } if (message.method === "tools/list") { const body = Buffer.from(JSON.stringify({ jsonrpc: "2.0", id: message.id, result: { padding: "x".repeat(1024 * 1024) } })); process.stdout.write(Buffer.concat([Buffer.from("Content-Length: " + body.length + "\\r\\n\\r\\n"), body])); } }; process.stdin.on("data", (chunk) => { buffer = Buffer.concat([buffer, chunk]); while (buffer.length > 0) { const headerEnd = buffer.indexOf("\\r\\n\\r\\n"); if (/^content-length:/i.test(buffer.toString("ascii", 0, Math.min(buffer.length, 32)))) { if (headerEnd < 0) return; const match = buffer.subarray(0, headerEnd).toString("ascii").match(/content-length:\\s*(\\d+)/i); if (!match) return; const length = Number(match[1]); if (buffer.length < headerEnd + 4 + length) return; const body = buffer.subarray(headerEnd + 4, headerEnd + 4 + length); buffer = buffer.subarray(headerEnd + 4 + length); handle(JSON.parse(body.toString("utf8"))); continue; } const newline = buffer.indexOf(10); if (newline < 0) return; const line = buffer.subarray(0, newline).toString("utf8").trim(); buffer = buffer.subarray(newline + 1); if (line !== "") handle(JSON.parse(line)); } });`,
|
|
3660
|
+
"utf8",
|
|
3661
|
+
);
|
|
3662
|
+
const panels = new PiPluginUiRegistry();
|
|
3663
|
+
const tools = new PiToolRegistry();
|
|
3664
|
+
context.provide("piTools", tools);
|
|
3665
|
+
context.provide("piPluginUi", panels);
|
|
3666
|
+
await context.plugin(mcpClientPlugin);
|
|
3667
|
+
|
|
3668
|
+
await expect(
|
|
3669
|
+
namedTool(tools, "mcp_list_tools").execute("call-1", { command: [process.execPath, server] }, undefined, undefined, {} as never),
|
|
3670
|
+
).rejects.toThrow(/1 MiB limit/iu);
|
|
3671
|
+
});
|
|
3672
|
+
|
|
3673
|
+
test("cancels an in-flight MCP request and notifies the stdio server", async () => {
|
|
3674
|
+
const { context, cwd } = await createContext();
|
|
3675
|
+
const server = join(cwd, "mcp-cancellation-fixture.mjs");
|
|
3676
|
+
const requestedMarker = join(cwd, "mcp-requested");
|
|
3677
|
+
const cancelledMarker = join(cwd, "mcp-cancelled");
|
|
3678
|
+
await writeFile(
|
|
3679
|
+
server,
|
|
3680
|
+
`import { writeFileSync } from "node:fs"; let buffer = Buffer.alloc(0); const send = (message) => process.stdout.write(JSON.stringify(message) + "\\n"); const handle = (message) => { if (message.method === "initialize") { send({ jsonrpc: "2.0", id: message.id, result: { protocolVersion: "2025-06-18", capabilities: {}, serverInfo: { name: "cancellation-fixture", version: "1" } } }); return; } if (message.method === "tools/list") writeFileSync(${JSON.stringify(requestedMarker)}, "requested"); if (message.method === "notifications/cancelled") writeFileSync(${JSON.stringify(cancelledMarker)}, String(message.params.requestId)); }; process.stdin.on("data", (chunk) => { buffer = Buffer.concat([buffer, chunk]); while (buffer.length > 0) { const headerEnd = buffer.indexOf("\\r\\n\\r\\n"); if (/^content-length:/i.test(buffer.toString("ascii", 0, Math.min(buffer.length, 32)))) { if (headerEnd < 0) return; const match = buffer.subarray(0, headerEnd).toString("ascii").match(/content-length:\\s*(\\d+)/i); if (!match) return; const length = Number(match[1]); if (buffer.length < headerEnd + 4 + length) return; const body = buffer.subarray(headerEnd + 4, headerEnd + 4 + length); buffer = buffer.subarray(headerEnd + 4 + length); handle(JSON.parse(body.toString("utf8"))); continue; } const newline = buffer.indexOf(10); if (newline < 0) return; const line = buffer.subarray(0, newline).toString("utf8").trim(); buffer = buffer.subarray(newline + 1); if (line !== "") handle(JSON.parse(line)); } });`,
|
|
3681
|
+
"utf8",
|
|
3682
|
+
);
|
|
3683
|
+
const panels = new PiPluginUiRegistry();
|
|
3684
|
+
const tools = new PiToolRegistry();
|
|
3685
|
+
context.provide("piTools", tools);
|
|
3686
|
+
context.provide("piPluginUi", panels);
|
|
3687
|
+
await context.plugin(mcpClientPlugin, { servers: [{ id: "cancellation", command: [process.execPath, server], autoStart: false }] });
|
|
3688
|
+
await namedTool(tools, "mcp_server_start").execute("call-1", { serverId: "cancellation" }, undefined, undefined, {} as never);
|
|
3689
|
+
const controller = new AbortController();
|
|
3690
|
+
let outcome: unknown;
|
|
3691
|
+
const pending = namedTool(tools, "mcp_list_tools")
|
|
3692
|
+
.execute("call-2", { serverId: "cancellation" }, controller.signal, undefined, {} as never)
|
|
3693
|
+
.then(
|
|
3694
|
+
(result) => {
|
|
3695
|
+
outcome = result;
|
|
3696
|
+
},
|
|
3697
|
+
(error: unknown) => {
|
|
3698
|
+
outcome = error;
|
|
3699
|
+
},
|
|
3700
|
+
);
|
|
3701
|
+
await vi.waitFor(() => expect(existsSync(requestedMarker)).toBe(true));
|
|
3702
|
+
controller.abort(new Error("test cancellation"));
|
|
3703
|
+
try {
|
|
3704
|
+
await vi.waitFor(() => expect(outcome).toBeInstanceOf(Error), { timeout: 500 });
|
|
3705
|
+
if (!(outcome instanceof Error)) throw new Error("Expected MCP cancellation rejection");
|
|
3706
|
+
expect(outcome.message).toMatch(/cancelled/iu);
|
|
3707
|
+
await vi.waitFor(() => expect(existsSync(cancelledMarker)).toBe(true));
|
|
3708
|
+
} finally {
|
|
3709
|
+
await context.fiber.dispose();
|
|
3710
|
+
await pending;
|
|
3711
|
+
}
|
|
3712
|
+
});
|
|
3713
|
+
|
|
1544
3714
|
test("fetches bounded browser pages and blocks private targets by default", async () => {
|
|
1545
3715
|
const server = createServer((_request, response) => {
|
|
1546
3716
|
response.setHeader("content-type", "text/html; charset=utf-8");
|
|
@@ -1912,6 +4082,73 @@ describe("Pi domain plugins", () => {
|
|
|
1912
4082
|
}
|
|
1913
4083
|
});
|
|
1914
4084
|
|
|
4085
|
+
test("rejects an oversized plugin radar response before reading its body", async () => {
|
|
4086
|
+
const originalFetch = globalThis.fetch;
|
|
4087
|
+
let bodyRead = false;
|
|
4088
|
+
globalThis.fetch = () =>
|
|
4089
|
+
Promise.resolve({
|
|
4090
|
+
ok: true,
|
|
4091
|
+
status: 200,
|
|
4092
|
+
headers: new Headers({ "content-length": String(1024 * 1024 + 1) }),
|
|
4093
|
+
body: {
|
|
4094
|
+
getReader() {
|
|
4095
|
+
bodyRead = true;
|
|
4096
|
+
throw new Error("oversized body was read");
|
|
4097
|
+
},
|
|
4098
|
+
},
|
|
4099
|
+
text() {
|
|
4100
|
+
bodyRead = true;
|
|
4101
|
+
return Promise.reject(new Error("oversized body was read"));
|
|
4102
|
+
},
|
|
4103
|
+
} as unknown as Response);
|
|
4104
|
+
try {
|
|
4105
|
+
const { context } = await createContext();
|
|
4106
|
+
const panels = new PiPluginUiRegistry();
|
|
4107
|
+
const tools = new PiToolRegistry();
|
|
4108
|
+
context.provide("piTools", tools);
|
|
4109
|
+
context.provide("piPluginUi", panels);
|
|
4110
|
+
await context.plugin(pluginRadarPlugin, { apiUrl: "https://api.github.test" });
|
|
4111
|
+
|
|
4112
|
+
await expect(namedTool(tools, "plugin_radar_search").execute("call-1", {}, undefined, undefined, {} as never)).rejects.toThrow(/1 MiB limit/iu);
|
|
4113
|
+
expect(bodyRead).toBe(false);
|
|
4114
|
+
} finally {
|
|
4115
|
+
globalThis.fetch = originalFetch;
|
|
4116
|
+
}
|
|
4117
|
+
});
|
|
4118
|
+
|
|
4119
|
+
test("times out plugin radar requests independently of caller cancellation", async () => {
|
|
4120
|
+
const originalFetch = globalThis.fetch;
|
|
4121
|
+
const caller = new AbortController();
|
|
4122
|
+
globalThis.fetch = (_input, init) =>
|
|
4123
|
+
new Promise<Response>((_resolve, reject) => {
|
|
4124
|
+
const signal = init?.signal;
|
|
4125
|
+
if (signal?.aborted === true) reject(signal.reason instanceof Error ? signal.reason : new Error("Plugin radar request aborted"));
|
|
4126
|
+
else
|
|
4127
|
+
signal?.addEventListener("abort", () => reject(signal.reason instanceof Error ? signal.reason : new Error("Plugin radar request aborted")), {
|
|
4128
|
+
once: true,
|
|
4129
|
+
});
|
|
4130
|
+
});
|
|
4131
|
+
vi.useFakeTimers();
|
|
4132
|
+
try {
|
|
4133
|
+
const { context } = await createContext();
|
|
4134
|
+
const panels = new PiPluginUiRegistry();
|
|
4135
|
+
const tools = new PiToolRegistry();
|
|
4136
|
+
context.provide("piTools", tools);
|
|
4137
|
+
context.provide("piPluginUi", panels);
|
|
4138
|
+
await context.plugin(pluginRadarPlugin, { apiUrl: "https://api.github.test", timeoutMs: 1_000 });
|
|
4139
|
+
const pending = namedTool(tools, "plugin_radar_search").execute("call-1", {}, caller.signal, undefined, {} as never);
|
|
4140
|
+
const timedOut = expect(pending).rejects.toThrow(/timed out after 1000 ms/iu);
|
|
4141
|
+
|
|
4142
|
+
await vi.advanceTimersByTimeAsync(1_000);
|
|
4143
|
+
caller.abort(new Error("test fallback cancellation"));
|
|
4144
|
+
await timedOut;
|
|
4145
|
+
} finally {
|
|
4146
|
+
caller.abort();
|
|
4147
|
+
vi.useRealTimers();
|
|
4148
|
+
globalThis.fetch = originalFetch;
|
|
4149
|
+
}
|
|
4150
|
+
});
|
|
4151
|
+
|
|
1915
4152
|
test("checks plugin manifests and patches without modifying repositories", async () => {
|
|
1916
4153
|
const { context, cwd } = await createContext();
|
|
1917
4154
|
const good = join(cwd, "dsh-good");
|
|
@@ -1947,6 +4184,79 @@ describe("Pi domain plugins", () => {
|
|
|
1947
4184
|
expect(await readFile(join(good, "cordis.patch.yml"), "utf8")).toBe("- id: dsh-good\n name: dsh-good\n");
|
|
1948
4185
|
});
|
|
1949
4186
|
|
|
4187
|
+
test("keeps plugin repository checks inside the current workspace", async () => {
|
|
4188
|
+
const { context, cwd, agentDir } = await createContext();
|
|
4189
|
+
const outside = join(agentDir, "dsh-outside");
|
|
4190
|
+
await mkdir(join(outside, "src"), { recursive: true });
|
|
4191
|
+
await writeFile(join(outside, "package.json"), JSON.stringify({ name: "dsh-outside", main: "dist/index.js" }), "utf8");
|
|
4192
|
+
await symlink(outside, join(cwd, "dsh-linked"));
|
|
4193
|
+
const tools = new PiToolRegistry();
|
|
4194
|
+
context.provide("piTools", tools);
|
|
4195
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
4196
|
+
await context.plugin(pluginCheckPlugin, {});
|
|
4197
|
+
|
|
4198
|
+
await expect(
|
|
4199
|
+
namedTool(tools, "plugin_check").execute("call-1", { action: "check", path: "dsh-linked" }, undefined, undefined, {} as never),
|
|
4200
|
+
).rejects.toThrow(/inside the current workspace/iu);
|
|
4201
|
+
});
|
|
4202
|
+
|
|
4203
|
+
test("starts plugin repository checks with default configuration", async () => {
|
|
4204
|
+
const { context } = await createContext();
|
|
4205
|
+
const tools = new PiToolRegistry();
|
|
4206
|
+
context.provide("piTools", tools);
|
|
4207
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
4208
|
+
let activationError: unknown;
|
|
4209
|
+
try {
|
|
4210
|
+
await context.plugin(pluginCheckPlugin);
|
|
4211
|
+
} catch (error) {
|
|
4212
|
+
activationError = error;
|
|
4213
|
+
}
|
|
4214
|
+
expect(activationError).toBeUndefined();
|
|
4215
|
+
await expect(namedTool(tools, "plugin_check").execute("call-1", { action: "schema" }, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
4216
|
+
details: { verdict: "pass" },
|
|
4217
|
+
});
|
|
4218
|
+
});
|
|
4219
|
+
|
|
4220
|
+
test("bounds plugin source reads and reports oversized files as skipped", async () => {
|
|
4221
|
+
const { context, cwd } = await createContext();
|
|
4222
|
+
const repo = join(cwd, "dsh-large-source");
|
|
4223
|
+
await mkdir(join(repo, "src"), { recursive: true });
|
|
4224
|
+
await writeFile(join(repo, "package.json"), JSON.stringify({ name: "dsh-large-source", main: "dist/index.js", scripts: { build: "tsc" } }), "utf8");
|
|
4225
|
+
await writeFile(join(repo, "cordis.patch.yml"), "- id: dsh-large-source\n", "utf8");
|
|
4226
|
+
await writeFile(join(repo, "README.md"), "pi plugin --profile web add github:example/dsh-large-source\n", "utf8");
|
|
4227
|
+
await writeFile(join(repo, "src", "oversized.ts"), Buffer.alloc(1024 * 1024 + 1, 0x20));
|
|
4228
|
+
const tools = new PiToolRegistry();
|
|
4229
|
+
context.provide("piTools", tools);
|
|
4230
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
4231
|
+
await context.plugin(pluginCheckPlugin);
|
|
4232
|
+
|
|
4233
|
+
const result = await namedTool(tools, "plugin_check").execute("call-1", { action: "check", path: "dsh-large-source" }, undefined, undefined, {} as never);
|
|
4234
|
+
const details = result.details as PluginCheckReport;
|
|
4235
|
+
expect(details.sourceScan).toEqual({ checked: 0, skipped: 1, truncated: false });
|
|
4236
|
+
expect(details.warnings.some((warning) => warning.code === "source-scan-incomplete")).toBe(true);
|
|
4237
|
+
});
|
|
4238
|
+
|
|
4239
|
+
test("rejects oversized plugin metadata files before parsing them", async () => {
|
|
4240
|
+
const { context, cwd } = await createContext();
|
|
4241
|
+
const repo = join(cwd, "dsh-large-manifest");
|
|
4242
|
+
await mkdir(join(repo, "src"), { recursive: true });
|
|
4243
|
+
await writeFile(
|
|
4244
|
+
join(repo, "package.json"),
|
|
4245
|
+
JSON.stringify({ name: "dsh-large-manifest", main: "dist/index.js", padding: "x".repeat(1024 * 1024) }),
|
|
4246
|
+
"utf8",
|
|
4247
|
+
);
|
|
4248
|
+
await writeFile(join(repo, "cordis.patch.yml"), "- id: dsh-large-manifest\n", "utf8");
|
|
4249
|
+
const tools = new PiToolRegistry();
|
|
4250
|
+
context.provide("piTools", tools);
|
|
4251
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
4252
|
+
await context.plugin(pluginCheckPlugin);
|
|
4253
|
+
|
|
4254
|
+
const result = await namedTool(tools, "plugin_check").execute("call-1", { action: "check", path: "dsh-large-manifest" }, undefined, undefined, {} as never);
|
|
4255
|
+
const details = result.details as PluginCheckReport;
|
|
4256
|
+
const manifestError = details.errors.find((error) => error.code === "no-manifest");
|
|
4257
|
+
expect(manifestError?.message).toMatch(/1 MiB|large/iu);
|
|
4258
|
+
});
|
|
4259
|
+
|
|
1950
4260
|
test("manages numbered annotations and renders a model-ready prompt block", async () => {
|
|
1951
4261
|
const { context } = await createContext();
|
|
1952
4262
|
const panels = new PiPluginUiRegistry();
|
|
@@ -1965,6 +4275,12 @@ describe("Pi domain plugins", () => {
|
|
|
1965
4275
|
);
|
|
1966
4276
|
expect(first.details).toMatchObject({ id: 1, quote: "Use the streaming transport", note: "Keep this behavior" });
|
|
1967
4277
|
await tool.execute("add-2", { action: "add", quote: "Render Markdown with a library" }, undefined, undefined, {} as never);
|
|
4278
|
+
await expect(tool.execute("empty-prompt", { action: "prompt", question: " " }, undefined, undefined, {} as never)).rejects.toThrow(
|
|
4279
|
+
/annotation question is required/iu,
|
|
4280
|
+
);
|
|
4281
|
+
await expect(tool.execute("long-prompt", { action: "prompt", question: "q".repeat(4_001) }, undefined, undefined, {} as never)).rejects.toThrow(
|
|
4282
|
+
/annotation question.*4000/iu,
|
|
4283
|
+
);
|
|
1968
4284
|
const prompt = await tool.execute("prompt", { action: "prompt", question: "What should we change?" }, undefined, undefined, {} as never);
|
|
1969
4285
|
const [firstBlock] = prompt.content;
|
|
1970
4286
|
expect(firstBlock?.type).toBe("text");
|
|
@@ -1978,6 +4294,138 @@ describe("Pi domain plugins", () => {
|
|
|
1978
4294
|
expect((await panels.snapshot())[0]).toMatchObject({ data: { count: 0, annotations: [] } });
|
|
1979
4295
|
});
|
|
1980
4296
|
|
|
4297
|
+
test("publishes annotation input bounds in the tool parameter schema", async () => {
|
|
4298
|
+
const { context } = await createContext();
|
|
4299
|
+
const tools = new PiToolRegistry();
|
|
4300
|
+
context.provide("piTools", tools);
|
|
4301
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
4302
|
+
await context.plugin(annotationPlugin);
|
|
4303
|
+
|
|
4304
|
+
expect(namedTool(tools, "annotation_manage").parameters).toMatchObject({
|
|
4305
|
+
properties: {
|
|
4306
|
+
quote: { maxLength: 4_000 },
|
|
4307
|
+
note: { maxLength: 1_000 },
|
|
4308
|
+
id: { type: "integer", minimum: 1 },
|
|
4309
|
+
question: { maxLength: 4_000 },
|
|
4310
|
+
},
|
|
4311
|
+
});
|
|
4312
|
+
});
|
|
4313
|
+
|
|
4314
|
+
test("rejects an unknown annotation action at the execution boundary", async () => {
|
|
4315
|
+
const { context } = await createContext();
|
|
4316
|
+
const tools = new PiToolRegistry();
|
|
4317
|
+
context.provide("piTools", tools);
|
|
4318
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
4319
|
+
await context.plugin(annotationPlugin);
|
|
4320
|
+
|
|
4321
|
+
await expect(namedTool(tools, "annotation_manage").execute("unknown", { action: "archive" }, undefined, undefined, {} as never)).rejects.toThrow(
|
|
4322
|
+
/unknown annotation action/iu,
|
|
4323
|
+
);
|
|
4324
|
+
});
|
|
4325
|
+
|
|
4326
|
+
test("does not expose mutable annotation state through an add result", async () => {
|
|
4327
|
+
const { context } = await createContext();
|
|
4328
|
+
const tools = new PiToolRegistry();
|
|
4329
|
+
context.provide("piTools", tools);
|
|
4330
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
4331
|
+
await context.plugin(annotationPlugin);
|
|
4332
|
+
const tool = namedTool(tools, "annotation_manage");
|
|
4333
|
+
const added = await tool.execute("add", { action: "add", quote: "Original" }, undefined, undefined, {} as never);
|
|
4334
|
+
(added.details as { quote: string }).quote = "Mutated";
|
|
4335
|
+
|
|
4336
|
+
await expect(tool.execute("list", { action: "list" }, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
4337
|
+
details: { annotations: [{ quote: "Original" }] },
|
|
4338
|
+
});
|
|
4339
|
+
});
|
|
4340
|
+
|
|
4341
|
+
test("does not expose mutable annotation state through a list result", async () => {
|
|
4342
|
+
const { context } = await createContext();
|
|
4343
|
+
const tools = new PiToolRegistry();
|
|
4344
|
+
context.provide("piTools", tools);
|
|
4345
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
4346
|
+
await context.plugin(annotationPlugin);
|
|
4347
|
+
const tool = namedTool(tools, "annotation_manage");
|
|
4348
|
+
await tool.execute("add", { action: "add", quote: "Original", note: "Original note" }, undefined, undefined, {} as never);
|
|
4349
|
+
const listed = await tool.execute("list", { action: "list" }, undefined, undefined, {} as never);
|
|
4350
|
+
(listed.details as { annotations: Array<{ note: string }> }).annotations[0]!.note = "Mutated";
|
|
4351
|
+
|
|
4352
|
+
await expect(tool.execute("list-again", { action: "list" }, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
4353
|
+
details: { annotations: [{ note: "Original note" }] },
|
|
4354
|
+
});
|
|
4355
|
+
});
|
|
4356
|
+
|
|
4357
|
+
test("enforces annotation action preconditions and bounded collection capacity", async () => {
|
|
4358
|
+
const { context } = await createContext();
|
|
4359
|
+
const tools = new PiToolRegistry();
|
|
4360
|
+
context.provide("piTools", tools);
|
|
4361
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
4362
|
+
await context.plugin(annotationPlugin);
|
|
4363
|
+
const tool = namedTool(tools, "annotation_manage");
|
|
4364
|
+
|
|
4365
|
+
await expect(tool.execute("empty-list", { action: "list" }, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
4366
|
+
details: { count: 0, annotations: [] },
|
|
4367
|
+
});
|
|
4368
|
+
await expect(tool.execute("empty-prompt", { action: "prompt", question: "Question" }, undefined, undefined, {} as never)).rejects.toThrow(
|
|
4369
|
+
/add at least one annotation/iu,
|
|
4370
|
+
);
|
|
4371
|
+
await expect(tool.execute("missing-remove", { action: "remove" }, undefined, undefined, {} as never)).rejects.toThrow(/id is required/iu);
|
|
4372
|
+
await expect(tool.execute("unknown-remove", { action: "remove", id: 1 }, undefined, undefined, {} as never)).rejects.toThrow(/not found/iu);
|
|
4373
|
+
await expect(tool.execute("empty-quote", { action: "add", quote: " " }, undefined, undefined, {} as never)).rejects.toThrow(/quote.*1-4000/iu);
|
|
4374
|
+
await expect(tool.execute("long-quote", { action: "add", quote: "q".repeat(4_001) }, undefined, undefined, {} as never)).rejects.toThrow(/quote.*1-4000/iu);
|
|
4375
|
+
await expect(tool.execute("long-note", { action: "add", quote: "Valid", note: "n".repeat(1_001) }, undefined, undefined, {} as never)).rejects.toThrow(
|
|
4376
|
+
/note.*0-1000/iu,
|
|
4377
|
+
);
|
|
4378
|
+
for (let index = 1; index <= 50; index += 1) {
|
|
4379
|
+
await tool.execute(`add-${index}`, { action: "add", quote: `Quote ${index}` }, undefined, undefined, {} as never);
|
|
4380
|
+
}
|
|
4381
|
+
await expect(tool.execute("over-capacity", { action: "add", quote: "One too many" }, undefined, undefined, {} as never)).rejects.toThrow(
|
|
4382
|
+
/at most 50 annotations/iu,
|
|
4383
|
+
);
|
|
4384
|
+
await tool.execute("remove", { action: "remove", id: 25 }, undefined, undefined, {} as never);
|
|
4385
|
+
await expect(tool.execute("replacement", { action: "add", quote: "Replacement" }, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
4386
|
+
details: { id: 51 },
|
|
4387
|
+
});
|
|
4388
|
+
});
|
|
4389
|
+
|
|
4390
|
+
test("clears the last annotation prompt together with the collection", async () => {
|
|
4391
|
+
const { context } = await createContext();
|
|
4392
|
+
const tools = new PiToolRegistry();
|
|
4393
|
+
const panels = new PiPluginUiRegistry();
|
|
4394
|
+
context.provide("piTools", tools);
|
|
4395
|
+
context.provide("piPluginUi", panels);
|
|
4396
|
+
await context.plugin(annotationPlugin);
|
|
4397
|
+
const tool = namedTool(tools, "annotation_manage");
|
|
4398
|
+
await tool.execute("add", { action: "add", quote: "Quote" }, undefined, undefined, {} as never);
|
|
4399
|
+
await tool.execute("prompt", { action: "prompt", question: "Question" }, undefined, undefined, {} as never);
|
|
4400
|
+
const beforeClear = (await panels.snapshot())[0];
|
|
4401
|
+
if (beforeClear === undefined) throw new Error("annotation-panel was not registered");
|
|
4402
|
+
expect(beforeClear.data).toMatchObject({ count: 1 });
|
|
4403
|
+
expect(typeof (beforeClear.data as { lastPrompt?: string }).lastPrompt).toBe("string");
|
|
4404
|
+
|
|
4405
|
+
await tool.execute("clear", { action: "clear" }, undefined, undefined, {} as never);
|
|
4406
|
+
|
|
4407
|
+
const panel = (await panels.snapshot())[0];
|
|
4408
|
+
if (panel === undefined) throw new Error("annotation-panel was not registered");
|
|
4409
|
+
expect(panel.data).toMatchObject({ count: 0, annotations: [] });
|
|
4410
|
+
expect((panel.data as { lastPrompt?: string }).lastPrompt).toBeUndefined();
|
|
4411
|
+
});
|
|
4412
|
+
|
|
4413
|
+
test("unregisters the annotation tool and panel when its plugin context is disposed", async () => {
|
|
4414
|
+
const { context } = await createContext();
|
|
4415
|
+
const tools = new PiToolRegistry();
|
|
4416
|
+
const panels = new PiPluginUiRegistry();
|
|
4417
|
+
context.provide("piTools", tools);
|
|
4418
|
+
context.provide("piPluginUi", panels);
|
|
4419
|
+
await context.plugin(annotationPlugin);
|
|
4420
|
+
expect(tools.snapshot().customTools.map((tool) => tool.name)).toEqual(["annotation_manage"]);
|
|
4421
|
+
await expect(panels.snapshot()).resolves.toMatchObject([{ id: "annotation-panel" }]);
|
|
4422
|
+
|
|
4423
|
+
await context.fiber.dispose();
|
|
4424
|
+
|
|
4425
|
+
expect(tools.snapshot().customTools).toEqual([]);
|
|
4426
|
+
await expect(panels.snapshot()).resolves.toEqual([]);
|
|
4427
|
+
});
|
|
4428
|
+
|
|
1981
4429
|
test("persists completed session costs and exposes a daily budget report", async () => {
|
|
1982
4430
|
const { context, agentDir } = await createContext();
|
|
1983
4431
|
const panels = new PiPluginUiRegistry();
|
|
@@ -1998,7 +4446,6 @@ describe("Pi domain plugins", () => {
|
|
|
1998
4446
|
context.provide("piRuntime", { session: { getSessionStats: () => sessionStats } } as never);
|
|
1999
4447
|
await context.plugin(costMeterPlugin, { dailyBudget: 5 });
|
|
2000
4448
|
context.emit("pi/session-event", { type: "agent_end", messages: [], willRetry: false });
|
|
2001
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
2002
4449
|
sessionStats = {
|
|
2003
4450
|
...sessionStats,
|
|
2004
4451
|
assistantMessages: 3,
|
|
@@ -2007,7 +4454,6 @@ describe("Pi domain plugins", () => {
|
|
|
2007
4454
|
cost: 2,
|
|
2008
4455
|
};
|
|
2009
4456
|
context.emit("pi/session-event", { type: "agent_end", messages: [], willRetry: false });
|
|
2010
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
2011
4457
|
const tool = tools.snapshot().customTools.find((entry) => entry.name === "cost_report");
|
|
2012
4458
|
if (tool === undefined) throw new Error("cost_report was not registered");
|
|
2013
4459
|
const report = await tool.execute("report", {}, undefined, undefined, {} as never);
|
|
@@ -2019,6 +4465,130 @@ describe("Pi domain plugins", () => {
|
|
|
2019
4465
|
expect(await readFile(join(agentDir, "cost-meter.json"), "utf8")).toContain("session-1");
|
|
2020
4466
|
});
|
|
2021
4467
|
|
|
4468
|
+
test("does not read cost history through a symbolic link", async () => {
|
|
4469
|
+
const { context, cwd, agentDir } = await createContext();
|
|
4470
|
+
const outside = join(cwd, "outside-costs.json");
|
|
4471
|
+
await writeFile(
|
|
4472
|
+
outside,
|
|
4473
|
+
JSON.stringify({ version: 1, entries: [{ sessionId: "secret", cost: 99, tokens: 1, messages: 1, recordedAt: new Date().toISOString() }] }),
|
|
4474
|
+
"utf8",
|
|
4475
|
+
);
|
|
4476
|
+
await symlink(outside, join(agentDir, "cost-meter.json"));
|
|
4477
|
+
const tools = new PiToolRegistry();
|
|
4478
|
+
context.provide("piTools", tools);
|
|
4479
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
4480
|
+
context.provide("piRuntime", {
|
|
4481
|
+
session: {
|
|
4482
|
+
getSessionStats: () => ({
|
|
4483
|
+
sessionFile: undefined,
|
|
4484
|
+
sessionId: "current",
|
|
4485
|
+
userMessages: 0,
|
|
4486
|
+
assistantMessages: 0,
|
|
4487
|
+
toolCalls: 0,
|
|
4488
|
+
toolResults: 0,
|
|
4489
|
+
totalMessages: 0,
|
|
4490
|
+
tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
4491
|
+
cost: 0,
|
|
4492
|
+
}),
|
|
4493
|
+
},
|
|
4494
|
+
} as never);
|
|
4495
|
+
await context.plugin(costMeterPlugin);
|
|
4496
|
+
|
|
4497
|
+
await expect(namedTool(tools, "cost_report").execute("call-1", {}, undefined, undefined, {} as never)).rejects.toThrow(/symbolic link|regular file/iu);
|
|
4498
|
+
});
|
|
4499
|
+
|
|
4500
|
+
test("rejects oversized cost history before parsing it", async () => {
|
|
4501
|
+
const { context, agentDir } = await createContext();
|
|
4502
|
+
await writeFile(join(agentDir, "cost-meter.json"), Buffer.alloc(4 * 1024 * 1024 + 1));
|
|
4503
|
+
const tools = new PiToolRegistry();
|
|
4504
|
+
context.provide("piTools", tools);
|
|
4505
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
4506
|
+
context.provide("piRuntime", {
|
|
4507
|
+
session: {
|
|
4508
|
+
getSessionStats: () => ({
|
|
4509
|
+
sessionFile: undefined,
|
|
4510
|
+
sessionId: "current",
|
|
4511
|
+
userMessages: 0,
|
|
4512
|
+
assistantMessages: 0,
|
|
4513
|
+
toolCalls: 0,
|
|
4514
|
+
toolResults: 0,
|
|
4515
|
+
totalMessages: 0,
|
|
4516
|
+
tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
4517
|
+
cost: 0,
|
|
4518
|
+
}),
|
|
4519
|
+
},
|
|
4520
|
+
} as never);
|
|
4521
|
+
await context.plugin(costMeterPlugin);
|
|
4522
|
+
|
|
4523
|
+
await expect(namedTool(tools, "cost_report").execute("call-1", {}, undefined, undefined, {} as never)).rejects.toThrow(/4 MiB|size|limit/iu);
|
|
4524
|
+
});
|
|
4525
|
+
|
|
4526
|
+
test("rolls back failed cost records and recovers the persistence queue", async () => {
|
|
4527
|
+
const { context, agentDir } = await createContext();
|
|
4528
|
+
const costPath = join(agentDir, "cost-meter.json");
|
|
4529
|
+
await writeFile(costPath, JSON.stringify({ version: 1, entries: [] }), "utf8");
|
|
4530
|
+
const tools = new PiToolRegistry();
|
|
4531
|
+
context.provide("piTools", tools);
|
|
4532
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
4533
|
+
let sessionStats = {
|
|
4534
|
+
sessionFile: undefined,
|
|
4535
|
+
sessionId: "failed-session",
|
|
4536
|
+
userMessages: 1,
|
|
4537
|
+
assistantMessages: 1,
|
|
4538
|
+
toolCalls: 0,
|
|
4539
|
+
toolResults: 0,
|
|
4540
|
+
totalMessages: 2,
|
|
4541
|
+
tokens: { input: 10, output: 10, cacheRead: 0, cacheWrite: 0, total: 20 },
|
|
4542
|
+
cost: 1,
|
|
4543
|
+
};
|
|
4544
|
+
context.provide("piRuntime", { session: { getSessionStats: () => sessionStats } } as never);
|
|
4545
|
+
await context.plugin(costMeterPlugin);
|
|
4546
|
+
const report = namedTool(tools, "cost_report");
|
|
4547
|
+
await report.execute("load", {}, undefined, undefined, {} as never);
|
|
4548
|
+
await rm(costPath);
|
|
4549
|
+
await mkdir(costPath);
|
|
4550
|
+
|
|
4551
|
+
await expect(report.execute("call-1", { refresh: true }, undefined, undefined, {} as never)).rejects.toThrow();
|
|
4552
|
+
await rm(costPath, { recursive: true });
|
|
4553
|
+
sessionStats = { ...sessionStats, sessionId: "saved-session", cost: 2 };
|
|
4554
|
+
await expect(report.execute("call-2", { refresh: true }, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
4555
|
+
details: { entries: [{ sessionId: "saved-session" }] },
|
|
4556
|
+
});
|
|
4557
|
+
const persisted = JSON.parse(await readFile(costPath, "utf8")) as { entries: Array<{ sessionId: string }> };
|
|
4558
|
+
expect(persisted.entries.map((entry) => entry.sessionId)).toEqual(["saved-session"]);
|
|
4559
|
+
expect((await readdir(agentDir)).filter((name) => name.startsWith(".cost-meter.json.") && name.endsWith(".tmp"))).toEqual([]);
|
|
4560
|
+
});
|
|
4561
|
+
|
|
4562
|
+
test("rejects malformed cost entries instead of reporting partial data", async () => {
|
|
4563
|
+
const { context, agentDir } = await createContext();
|
|
4564
|
+
await writeFile(
|
|
4565
|
+
join(agentDir, "cost-meter.json"),
|
|
4566
|
+
JSON.stringify({ version: 1, entries: [{ sessionId: "partial", cost: 1, recordedAt: new Date().toISOString() }] }),
|
|
4567
|
+
"utf8",
|
|
4568
|
+
);
|
|
4569
|
+
const tools = new PiToolRegistry();
|
|
4570
|
+
context.provide("piTools", tools);
|
|
4571
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
4572
|
+
context.provide("piRuntime", {
|
|
4573
|
+
session: {
|
|
4574
|
+
getSessionStats: () => ({
|
|
4575
|
+
sessionFile: undefined,
|
|
4576
|
+
sessionId: "current",
|
|
4577
|
+
userMessages: 0,
|
|
4578
|
+
assistantMessages: 0,
|
|
4579
|
+
toolCalls: 0,
|
|
4580
|
+
toolResults: 0,
|
|
4581
|
+
totalMessages: 0,
|
|
4582
|
+
tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
4583
|
+
cost: 0,
|
|
4584
|
+
}),
|
|
4585
|
+
},
|
|
4586
|
+
} as never);
|
|
4587
|
+
await context.plugin(costMeterPlugin);
|
|
4588
|
+
|
|
4589
|
+
await expect(namedTool(tools, "cost_report").execute("call-1", {}, undefined, undefined, {} as never)).rejects.toThrow(/invalid entries/iu);
|
|
4590
|
+
});
|
|
4591
|
+
|
|
2022
4592
|
test("creates, diffs, lists, and safely restores a workspace savepoint", async () => {
|
|
2023
4593
|
const { context, cwd, agentDir } = await createContext();
|
|
2024
4594
|
await mkdir(join(cwd, "src"), { recursive: true });
|
|
@@ -2086,6 +4656,69 @@ describe("Pi domain plugins", () => {
|
|
|
2086
4656
|
expect((await panels.snapshot())[0]).toMatchObject({ id: "skill-catalog-panel", data: { skillCount: 1, mcpCount: 1 } });
|
|
2087
4657
|
});
|
|
2088
4658
|
|
|
4659
|
+
test("rejects non-file skill catalog entries before reading them", async () => {
|
|
4660
|
+
const { context, cwd } = await createContext();
|
|
4661
|
+
const skillPath = join(cwd, ".pi", "skills", "directory-skill", "SKILL.md");
|
|
4662
|
+
await mkdir(skillPath, { recursive: true });
|
|
4663
|
+
const tools = new PiToolRegistry();
|
|
4664
|
+
context.provide("piTools", tools);
|
|
4665
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
4666
|
+
context.provide("piResources", {
|
|
4667
|
+
resourceLoader: {
|
|
4668
|
+
getSkills: () => ({
|
|
4669
|
+
skills: [
|
|
4670
|
+
{
|
|
4671
|
+
name: "directory-skill",
|
|
4672
|
+
description: "Invalid directory entry",
|
|
4673
|
+
filePath: skillPath,
|
|
4674
|
+
baseDir: dirname(skillPath),
|
|
4675
|
+
sourceInfo: { source: "test", scope: "project" },
|
|
4676
|
+
disableModelInvocation: false,
|
|
4677
|
+
},
|
|
4678
|
+
],
|
|
4679
|
+
diagnostics: [],
|
|
4680
|
+
}),
|
|
4681
|
+
},
|
|
4682
|
+
} as never);
|
|
4683
|
+
context.provide("piMcp", { snapshot: () => ({ servers: [] }) });
|
|
4684
|
+
await context.plugin(skillCatalogPlugin);
|
|
4685
|
+
|
|
4686
|
+
await expect(
|
|
4687
|
+
namedTool(tools, "skill_catalog").execute("read", { action: "read", name: "directory-skill" }, undefined, undefined, {} as never),
|
|
4688
|
+
).rejects.toThrow(/regular file/iu);
|
|
4689
|
+
});
|
|
4690
|
+
|
|
4691
|
+
test("scans loaded skills through the bounded skill-guard plugin path", async () => {
|
|
4692
|
+
const { context, cwd } = await createContext();
|
|
4693
|
+
const skillPath = join(cwd, "oversized-skill.md");
|
|
4694
|
+
await writeFile(skillPath, Buffer.alloc(128 * 1024 + 1, 0x20));
|
|
4695
|
+
const tools = new PiToolRegistry();
|
|
4696
|
+
context.provide("piTools", tools);
|
|
4697
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
4698
|
+
context.provide("piResources", {
|
|
4699
|
+
resourceLoader: {
|
|
4700
|
+
getSkills: () => ({
|
|
4701
|
+
skills: [
|
|
4702
|
+
{
|
|
4703
|
+
name: "oversized",
|
|
4704
|
+
description: "Oversized fixture",
|
|
4705
|
+
filePath: skillPath,
|
|
4706
|
+
baseDir: cwd,
|
|
4707
|
+
sourceInfo: { source: "test", scope: "project" },
|
|
4708
|
+
disableModelInvocation: false,
|
|
4709
|
+
},
|
|
4710
|
+
],
|
|
4711
|
+
diagnostics: [],
|
|
4712
|
+
}),
|
|
4713
|
+
},
|
|
4714
|
+
} as never);
|
|
4715
|
+
await context.plugin(skillGuardPlugin);
|
|
4716
|
+
|
|
4717
|
+
await expect(namedTool(tools, "skill_guard_scan").execute("scan", {}, undefined, undefined, {} as never)).resolves.toMatchObject({
|
|
4718
|
+
details: { total: 1, review: 1, reports: [{ name: "oversized", findings: [{ code: "size_limit" }] }] },
|
|
4719
|
+
});
|
|
4720
|
+
});
|
|
4721
|
+
|
|
2089
4722
|
test("reports MCP server health and bridged tools through the console plugin", async () => {
|
|
2090
4723
|
const { context, agentDir } = await createContext();
|
|
2091
4724
|
const panels = new PiPluginUiRegistry();
|
|
@@ -2144,7 +4777,29 @@ describe("Pi domain plugins", () => {
|
|
|
2144
4777
|
{} as never,
|
|
2145
4778
|
);
|
|
2146
4779
|
expect(await readFile(patchPath, "utf8")).toContain("mcp-search");
|
|
2147
|
-
expect(await readFile(`${patchPath}.bak`, "utf8")).
|
|
4780
|
+
expect(await readFile(`${patchPath}.bak`, "utf8")).toContain("mcp-docs");
|
|
2148
4781
|
expect((await panels.snapshot())[0]).toMatchObject({ id: "mcp-panel", data: { servers: [{ id: "docs", toolCount: 1 }] } });
|
|
2149
4782
|
});
|
|
4783
|
+
|
|
4784
|
+
test("rejects oversized MCP patch files before reading or backing them up", async () => {
|
|
4785
|
+
const { context, agentDir } = await createContext();
|
|
4786
|
+
const patchPath = join(agentDir, "cordis.patch.yml");
|
|
4787
|
+
await writeFile(patchPath, Buffer.alloc(2 * 1024 * 1024 + 1));
|
|
4788
|
+
const tools = new PiToolRegistry();
|
|
4789
|
+
context.provide("piTools", tools);
|
|
4790
|
+
context.provide("piPluginUi", new PiPluginUiRegistry());
|
|
4791
|
+
context.provide("piMcp", { snapshot: () => ({ servers: [] }) });
|
|
4792
|
+
await context.plugin(mcpPanelPlugin, { patchPath });
|
|
4793
|
+
|
|
4794
|
+
await expect(
|
|
4795
|
+
namedTool(tools, "mcp_panel").execute(
|
|
4796
|
+
"apply",
|
|
4797
|
+
{ action: "apply", serverId: "docs", command: ["node", "server.js"], confirm: true },
|
|
4798
|
+
undefined,
|
|
4799
|
+
undefined,
|
|
4800
|
+
{} as never,
|
|
4801
|
+
),
|
|
4802
|
+
).rejects.toThrow(/2 MiB|size|limit/iu);
|
|
4803
|
+
await expect(stat(`${patchPath}.bak`)).rejects.toMatchObject({ code: "ENOENT" });
|
|
4804
|
+
});
|
|
2150
4805
|
});
|