@pi-harness/pi-harness 0.1.22 → 0.1.24
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 +12 -10
- package/apps/web/dist/assets/index-BcLXLKqO.js +24 -0
- package/apps/web/dist/assets/index-xF647pQJ.css +2 -0
- package/apps/web/dist/index.html +2 -2
- package/apps/web/package.json +6 -6
- package/apps/web/profile/cordis.yml +1 -0
- package/apps/web/server-dist/bin.js +45 -7
- package/apps/web/server-dist/bin.js.map +1 -1
- package/apps/web/src/server/bin.ts +45 -7
- package/apps/web/test/bin.test.ts +313 -0
- package/node_modules/@pi-harness/api-gateway/dist/index.d.ts.map +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/index.js +295 -86
- 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 +1 -1
- 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 -1
- 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 +1 -1
- 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 +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/browser-session.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/canvas-draw.json +1 -1
- 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 +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cli-notifier.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/code2skill.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/colleague-skill.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/context-doctor.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/context.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cost-meter.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/dependency-checker.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/docker-sandbox.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/fail-logger.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/genui.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/git-time-capsule.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/graph-memory.json +1 -1
- 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 +3 -3
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/i18n-pair.json +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/recall-unread.json +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/sql-lens.json +1 -1
- 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 +1 -1
- 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 +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/turn-rewind.json +1 -1
- 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 +1 -1
- 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 +1 -1
- package/node_modules/@pi-harness/api-gateway/package.json +1 -1
- package/node_modules/@pi-harness/cli/dist/main.d.ts +1 -0
- package/node_modules/@pi-harness/cli/dist/main.d.ts.map +1 -1
- package/node_modules/@pi-harness/cli/dist/main.js +11 -0
- package/node_modules/@pi-harness/cli/dist/main.js.map +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 +3 -1
- package/node_modules/@pi-harness/cli/dist/node-stdio.js.map +1 -1
- package/node_modules/@pi-harness/cli/dist/relaunch.d.ts.map +1 -1
- package/node_modules/@pi-harness/cli/dist/relaunch.js +4 -1
- package/node_modules/@pi-harness/cli/dist/relaunch.js.map +1 -1
- package/node_modules/@pi-harness/cli/package.json +1 -1
- package/node_modules/@pi-harness/host-webserver/dist/index.d.ts +1 -0
- package/node_modules/@pi-harness/host-webserver/dist/index.d.ts.map +1 -1
- package/node_modules/@pi-harness/host-webserver/dist/index.js +86 -3
- package/node_modules/@pi-harness/host-webserver/dist/index.js.map +1 -1
- package/node_modules/@pi-harness/host-webserver/package.json +1 -1
- package/node_modules/@pi-harness/web-app/dist/index.d.ts +3 -0
- package/node_modules/@pi-harness/web-app/dist/index.d.ts.map +1 -1
- package/node_modules/@pi-harness/web-app/dist/index.js +21 -8
- package/node_modules/@pi-harness/web-app/dist/index.js.map +1 -1
- package/node_modules/@pi-harness/web-app/package.json +1 -1
- package/package.json +8 -8
- package/packages/api-gateway/dist/index.d.ts.map +1 -1
- package/packages/api-gateway/dist/index.js +295 -86
- package/packages/api-gateway/dist/index.js.map +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/agent-teams.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/anchored-standard.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/annotation.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/archify.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/at-file.json +1 -1
- 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 +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/browser-session.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/canvas-draw.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/change-verifier.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/cleaner.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/cli-notifier.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/code2skill.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/colleague-skill.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/context-doctor.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/context.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/cost-meter.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/dependency-checker.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/docker-sandbox.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/fail-logger.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/genui.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/git-time-capsule.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/graph-memory.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/history-compressor.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/hol-guard.json +3 -3
- package/packages/api-gateway/dist/marketplace-entries/official/i18n-pair.json +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/recall-unread.json +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/sql-lens.json +1 -1
- 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 +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/theme-studio.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/token-guard.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/turn-rewind.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/undo-savepoint.json +1 -1
- package/packages/api-gateway/dist/marketplace-entries/official/vision-toolkit.json +1 -1
- 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 +1 -1
- package/packages/api-gateway/package.json +1 -1
- package/packages/api-gateway/src/index.ts +310 -87
- package/packages/api-gateway/src/marketplace-entries/official/agent-teams.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/anchored-standard.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/annotation.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/archify.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/at-file.json +1 -1
- 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 +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/browser-session.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/canvas-draw.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/change-verifier.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/cleaner.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/cli-notifier.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/code2skill.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/colleague-skill.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/context-doctor.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/context.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/cost-meter.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/dependency-checker.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/docker-sandbox.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/fail-logger.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/genui.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/git-time-capsule.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/graph-memory.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/history-compressor.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/hol-guard.json +3 -3
- package/packages/api-gateway/src/marketplace-entries/official/i18n-pair.json +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/recall-unread.json +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/sql-lens.json +1 -1
- 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 +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/theme-studio.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/token-guard.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/turn-rewind.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/undo-savepoint.json +1 -1
- package/packages/api-gateway/src/marketplace-entries/official/vision-toolkit.json +1 -1
- 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 +1 -1
- package/packages/api-gateway/test/index.test.ts +799 -3
- package/packages/api-gateway/test/marketplace-schema.test.ts +139 -0
- package/packages/api-gateway/test/packed-package.test.ts +70 -0
- package/packages/api-gateway/test/reference-docs.test.ts +50 -0
- package/packages/api-gateway/test/release-contract.test.ts +77 -0
- package/packages/api-gateway/test/release-package.test.ts +33 -2
- package/packages/api-gateway/test/release-version.test.ts +46 -4
- package/packages/bundle-web-app/dist/index.d.ts +3 -0
- package/packages/bundle-web-app/dist/index.d.ts.map +1 -1
- package/packages/bundle-web-app/dist/index.js +21 -8
- package/packages/bundle-web-app/dist/index.js.map +1 -1
- package/packages/bundle-web-app/package.json +1 -1
- package/packages/bundle-web-app/src/index.ts +25 -8
- package/packages/bundle-web-app/test/index.test.ts +116 -4
- package/packages/cli/dist/main.d.ts +1 -0
- package/packages/cli/dist/main.d.ts.map +1 -1
- package/packages/cli/dist/main.js +11 -0
- package/packages/cli/dist/main.js.map +1 -1
- package/packages/cli/dist/node-stdio.d.ts.map +1 -1
- package/packages/cli/dist/node-stdio.js +3 -1
- package/packages/cli/dist/node-stdio.js.map +1 -1
- package/packages/cli/dist/relaunch.d.ts.map +1 -1
- package/packages/cli/dist/relaunch.js +4 -1
- package/packages/cli/dist/relaunch.js.map +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/cli/src/main.ts +11 -0
- package/packages/cli/src/node-stdio.ts +6 -3
- package/packages/cli/src/relaunch.ts +4 -1
- package/packages/cli/test/cli.test.ts +3 -1
- package/packages/cli/test/main.test.ts +134 -0
- package/packages/cli/test/node-stdio.test.ts +43 -0
- package/packages/cli/test/relaunch.test.ts +110 -0
- package/packages/host-webserver/dist/index.d.ts +1 -0
- package/packages/host-webserver/dist/index.d.ts.map +1 -1
- package/packages/host-webserver/dist/index.js +86 -3
- package/packages/host-webserver/dist/index.js.map +1 -1
- package/packages/host-webserver/package.json +1 -1
- package/packages/host-webserver/src/index.ts +95 -3
- package/packages/host-webserver/test/index.test.ts +160 -1
- package/apps/web/dist/assets/index-BVI_IS-R.css +0 -2
- package/apps/web/dist/assets/index-w0KQgv8x.js +0 -22
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
|
-
import { mkdir, mkdtemp, readFile, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
1
|
+
import { execFile, type ExecFileException } from "node:child_process";
|
|
2
|
+
import { mkdir, mkdtemp, readFile, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
3
3
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
4
|
-
import { basename, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import type { Context } from "@deepseek-ai/cordis";
|
|
7
7
|
import { SessionManager, type AgentSessionEvent, type ExtensionUIContext } from "@earendil-works/pi-coding-agent";
|
|
8
8
|
import type Loader from "@deepseek-ai/cordis-plugin-loader";
|
|
9
|
+
import { atomicWriteFile } from "@pi-harness/core";
|
|
9
10
|
import type { PiPluginUiRegistry, PiRuntimeService, PiModelsService, PiHarnessLaunch } from "@pi-harness/core";
|
|
10
11
|
import type { WebServer } from "@pi-harness/host-webserver";
|
|
11
12
|
import {
|
|
@@ -17,6 +18,7 @@ import {
|
|
|
17
18
|
paginateMarketplace,
|
|
18
19
|
searchMarketplace,
|
|
19
20
|
needsMarketplacePackageInstall,
|
|
21
|
+
marketplaceNpmPackageName,
|
|
20
22
|
sortMarketplaceByRecommendation,
|
|
21
23
|
type MarketplacePlugin,
|
|
22
24
|
} from "./marketplace.js";
|
|
@@ -33,23 +35,39 @@ interface ApiServices {
|
|
|
33
35
|
|
|
34
36
|
const loadMarketplaceStatistics = createMarketplaceStatisticsLoader();
|
|
35
37
|
|
|
38
|
+
const DEFAULT_BODY_LIMIT_BYTES = 64 * 1024;
|
|
39
|
+
const IMPORT_CONTENT_LIMIT_BYTES = 10 * 1024 * 1024;
|
|
40
|
+
// The import body carries the JSONL content as a JSON string, so quotes and newlines are escaped; leave headroom above the content limit for that overhead.
|
|
41
|
+
const IMPORT_BODY_LIMIT_BYTES = 16 * 1024 * 1024;
|
|
42
|
+
// Retained trajectory events are replayed to every SSE client and returned by /api/session, so the array must stay bounded.
|
|
43
|
+
const MAX_RETAINED_EVENTS = 2000;
|
|
44
|
+
const GIT_TIMEOUT_MS = 15_000;
|
|
45
|
+
|
|
46
|
+
class PayloadTooLargeError extends Error {
|
|
47
|
+
constructor() {
|
|
48
|
+
super("Request body is too large");
|
|
49
|
+
this.name = "PayloadTooLargeError";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
36
53
|
function sendJson(response: ServerResponse, status: number, payload: unknown): void {
|
|
37
54
|
const body = JSON.stringify(payload);
|
|
38
55
|
response.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store", "content-length": Buffer.byteLength(body) });
|
|
39
56
|
response.end(body);
|
|
40
57
|
}
|
|
41
58
|
|
|
42
|
-
|
|
43
|
-
|
|
59
|
+
// A multi-byte UTF-8 sequence can straddle a chunk boundary, so the raw bytes are collected and decoded once; decoding each chunk on its own would replace the split sequence with U+FFFD.
|
|
60
|
+
async function bodyText(request: IncomingMessage, maxBytes = DEFAULT_BODY_LIMIT_BYTES): Promise<string> {
|
|
61
|
+
const chunks: Buffer[] = [];
|
|
44
62
|
let length = 0;
|
|
45
63
|
for await (const chunk of request) {
|
|
46
64
|
const input: string | Uint8Array = chunk as string | Uint8Array;
|
|
47
|
-
const
|
|
48
|
-
length +=
|
|
49
|
-
if (length >
|
|
50
|
-
chunks.push(
|
|
65
|
+
const buffer = typeof input === "string" ? Buffer.from(input, "utf8") : Buffer.isBuffer(input) ? input : Buffer.from(input);
|
|
66
|
+
length += buffer.length;
|
|
67
|
+
if (length > maxBytes) throw new PayloadTooLargeError();
|
|
68
|
+
chunks.push(buffer);
|
|
51
69
|
}
|
|
52
|
-
return chunks.
|
|
70
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
53
71
|
}
|
|
54
72
|
|
|
55
73
|
function errorText(error: unknown): string {
|
|
@@ -64,9 +82,14 @@ function jsonSafe(value: unknown, seen = new WeakSet<object>()): unknown {
|
|
|
64
82
|
if (value instanceof Date) return value.toISOString();
|
|
65
83
|
if (typeof value !== "object") return Object.prototype.toString.call(value);
|
|
66
84
|
if (seen.has(value)) return "[Circular]";
|
|
85
|
+
// Only the ancestors of the current node stay marked: a sibling that repeats the same object (Pi pushes one AgentMessage instance into both the message list and its events) is legitimate data, not a cycle.
|
|
67
86
|
seen.add(value);
|
|
68
|
-
|
|
69
|
-
|
|
87
|
+
try {
|
|
88
|
+
if (Array.isArray(value)) return value.map((item) => jsonSafe(item, seen));
|
|
89
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, jsonSafe(item, seen)]));
|
|
90
|
+
} finally {
|
|
91
|
+
seen.delete(value);
|
|
92
|
+
}
|
|
70
93
|
}
|
|
71
94
|
|
|
72
95
|
function createStatus(services: ApiServices, events: readonly AgentSessionEvent[]) {
|
|
@@ -139,26 +162,67 @@ function sessionMetadataFile(manager: SessionManager): string {
|
|
|
139
162
|
return resolve(manager.getSessionDir(), ".pi-harness-session-meta.json");
|
|
140
163
|
}
|
|
141
164
|
|
|
142
|
-
|
|
165
|
+
interface MetadataLogger {
|
|
166
|
+
warn(message: string): void;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// A missing file is the normal first-run state. Any other read or parse failure means the file exists but is unusable; it is renamed aside so the wreckage survives for recovery and the next write cannot silently replace the only copy.
|
|
170
|
+
async function readSessionMetadata(manager: SessionManager, logger: MetadataLogger): Promise<SessionMetadataMap> {
|
|
171
|
+
const file = sessionMetadataFile(manager);
|
|
172
|
+
let raw: string;
|
|
173
|
+
try {
|
|
174
|
+
raw = await readFile(file, "utf8");
|
|
175
|
+
} catch (error) {
|
|
176
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return {};
|
|
177
|
+
await quarantineSessionMetadata(file, logger, error);
|
|
178
|
+
return {};
|
|
179
|
+
}
|
|
143
180
|
try {
|
|
144
|
-
const parsed: unknown = JSON.parse(
|
|
145
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
181
|
+
const parsed: unknown = JSON.parse(raw);
|
|
182
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("session metadata must be a JSON object");
|
|
146
183
|
return parsed as SessionMetadataMap;
|
|
147
|
-
} catch {
|
|
184
|
+
} catch (error) {
|
|
185
|
+
await quarantineSessionMetadata(file, logger, error);
|
|
148
186
|
return {};
|
|
149
187
|
}
|
|
150
188
|
}
|
|
151
189
|
|
|
190
|
+
async function quarantineSessionMetadata(file: string, logger: MetadataLogger, error: unknown): Promise<void> {
|
|
191
|
+
const quarantined = `${file}.corrupt-${Date.now()}`;
|
|
192
|
+
try {
|
|
193
|
+
await rename(file, quarantined);
|
|
194
|
+
logger.warn(`Session metadata at ${file} is unreadable (${errorText(error)}); moved it to ${quarantined} and starting from an empty map`);
|
|
195
|
+
} catch (renameError) {
|
|
196
|
+
logger.warn(`Session metadata at ${file} is unreadable (${errorText(error)}) and could not be quarantined: ${errorText(renameError)}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
152
200
|
async function writeSessionMetadata(manager: SessionManager, metadata: SessionMetadataMap): Promise<void> {
|
|
153
201
|
await mkdir(manager.getSessionDir(), { recursive: true });
|
|
154
|
-
await
|
|
202
|
+
await atomicWriteFile(sessionMetadataFile(manager), JSON.stringify(metadata, null, 2) + "\n", { encoding: "utf8" });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Every read-modify-write of the metadata file runs through this chain so concurrent requests cannot interleave and drop each other's updates.
|
|
206
|
+
let sessionMetadataQueue: Promise<unknown> = Promise.resolve();
|
|
207
|
+
|
|
208
|
+
function mutateSessionMetadata<T>(manager: SessionManager, logger: MetadataLogger, mutate: (metadata: SessionMetadataMap) => Promise<T> | T): Promise<T> {
|
|
209
|
+
const operation = sessionMetadataQueue
|
|
210
|
+
.catch(() => undefined)
|
|
211
|
+
.then(async () => {
|
|
212
|
+
const metadata = await readSessionMetadata(manager, logger);
|
|
213
|
+
const result = await mutate(metadata);
|
|
214
|
+
await writeSessionMetadata(manager, metadata);
|
|
215
|
+
return result;
|
|
216
|
+
});
|
|
217
|
+
sessionMetadataQueue = operation;
|
|
218
|
+
return operation;
|
|
155
219
|
}
|
|
156
220
|
|
|
157
221
|
function sessionPathInDirectory(path: string, manager: SessionManager): boolean {
|
|
158
222
|
const root = resolve(manager.getSessionDir());
|
|
159
223
|
const target = resolve(path);
|
|
160
224
|
const relativePath = relative(root, target);
|
|
161
|
-
return relativePath !== "" && !isAbsolute(relativePath) && relativePath !== ".." && !relativePath.startsWith(".." +
|
|
225
|
+
return relativePath !== "" && !isAbsolute(relativePath) && relativePath !== ".." && !relativePath.startsWith(".." + sep) && target.endsWith(".jsonl");
|
|
162
226
|
}
|
|
163
227
|
|
|
164
228
|
const webSessionUi = {
|
|
@@ -293,6 +357,7 @@ function sanitizeLogValue(value: unknown, state: LogSanitizeState, depth = 0): u
|
|
|
293
357
|
if (typeof value !== "object") return Object.prototype.toString.call(value);
|
|
294
358
|
if (depth >= loggerPanelDepthLimit) return "[Max Depth]";
|
|
295
359
|
if (state.seen.has(value)) return "[Circular]";
|
|
360
|
+
// Unmarked once its subtree is done, so an object logged twice as a sibling is rendered twice instead of being mistaken for a cycle.
|
|
296
361
|
state.seen.add(value);
|
|
297
362
|
|
|
298
363
|
try {
|
|
@@ -322,6 +387,8 @@ function sanitizeLogValue(value: unknown, state: LogSanitizeState, depth = 0): u
|
|
|
322
387
|
return Object.fromEntries(entries);
|
|
323
388
|
} catch {
|
|
324
389
|
return "[Unavailable]";
|
|
390
|
+
} finally {
|
|
391
|
+
state.seen.delete(value);
|
|
325
392
|
}
|
|
326
393
|
}
|
|
327
394
|
|
|
@@ -452,25 +519,70 @@ function writeSse(response: ServerResponse, payload: unknown): void {
|
|
|
452
519
|
response.write(`data: ${JSON.stringify(jsonSafe(payload))}\n\n`);
|
|
453
520
|
}
|
|
454
521
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
522
|
+
type GitTermination = "timeout" | "output-limit";
|
|
523
|
+
|
|
524
|
+
// Node reports a maxBuffer overflow through a string error code and a timeout through `killed`; neither is a git exit status, so they must not be folded into an ordinary non-zero exit.
|
|
525
|
+
function gitTermination(error: ExecFileException | null): GitTermination | undefined {
|
|
526
|
+
if (error === null) return undefined;
|
|
527
|
+
if (error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") return "output-limit";
|
|
528
|
+
if (error.killed === true) return "timeout";
|
|
529
|
+
return undefined;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function gitTerminationMessage(command: string, termination: GitTermination): string {
|
|
533
|
+
return termination === "timeout"
|
|
534
|
+
? `git ${command} timed out after ${GIT_TIMEOUT_MS / 1000} seconds`
|
|
535
|
+
: `git ${command} produced more output than the buffer limit allows`;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
interface GitStatusResult {
|
|
539
|
+
readonly output: string;
|
|
540
|
+
readonly truncated: boolean;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// `-z` is what makes the paths usable: without it git wraps any path holding a space, a quote or a non-ASCII byte in C-style quoting, and the quoted form is not a pathspec git will accept back on diff, add or ls-files.
|
|
544
|
+
function gitStatus(cwd: string): Promise<GitStatusResult> {
|
|
545
|
+
return new Promise((resolveStatus, rejectStatus) => {
|
|
546
|
+
execFile("git", ["status", "--short", "-z", "--untracked-files=all"], { cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: 512 * 1024 }, (error, stdout, stderr) => {
|
|
547
|
+
if (error === null) {
|
|
548
|
+
resolveStatus({ output: stdout, truncated: false });
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
const termination = gitTermination(error);
|
|
552
|
+
if (termination === "output-limit") {
|
|
553
|
+
// Keep the complete records that arrived before the kill; the final one may have been cut mid-path.
|
|
554
|
+
resolveStatus({ output: stdout.slice(0, stdout.lastIndexOf("\0") + 1), truncated: true });
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
if (termination === undefined && error.code === 128 && /not a git repository/i.test(stderr)) {
|
|
558
|
+
resolveStatus({ output: "", truncated: false });
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
rejectStatus(new Error(termination ? gitTerminationMessage("status", termination) : stderr.trim() || error.message, { cause: error }));
|
|
562
|
+
});
|
|
458
563
|
});
|
|
459
564
|
}
|
|
460
565
|
|
|
461
566
|
function gitDiff(cwd: string, path: string): Promise<string> {
|
|
462
567
|
return new Promise((resolveOutput) => {
|
|
463
|
-
execFile("git", ["diff", "--no-ext-diff", "--", path], { cwd, maxBuffer: 1024 * 1024 }, (error, stdout) =>
|
|
568
|
+
execFile("git", ["diff", "--no-ext-diff", "--", path], { cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: 1024 * 1024 }, (error, stdout) =>
|
|
464
569
|
resolveOutput(error && stdout.length === 0 ? "" : stdout),
|
|
465
570
|
);
|
|
466
571
|
});
|
|
467
572
|
}
|
|
468
573
|
|
|
469
|
-
|
|
574
|
+
interface GitCommandResult {
|
|
575
|
+
readonly stdout: string;
|
|
576
|
+
readonly stderr: string;
|
|
577
|
+
readonly code: number;
|
|
578
|
+
readonly terminated: GitTermination | undefined;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function gitCommand(cwd: string, args: readonly string[]): Promise<GitCommandResult> {
|
|
470
582
|
return new Promise((resolveResult) => {
|
|
471
|
-
execFile("git", [...args], { cwd, maxBuffer: 2 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
583
|
+
execFile("git", [...args], { cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: 2 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
472
584
|
const code = error && typeof error.code === "number" ? error.code : error ? 1 : 0;
|
|
473
|
-
resolveResult({ stdout, stderr, code });
|
|
585
|
+
resolveResult({ stdout, stderr, code, terminated: gitTermination(error) });
|
|
474
586
|
});
|
|
475
587
|
});
|
|
476
588
|
}
|
|
@@ -509,6 +621,26 @@ async function isDirectory(path: string): Promise<boolean> {
|
|
|
509
621
|
}
|
|
510
622
|
}
|
|
511
623
|
|
|
624
|
+
async function isFile(path: string): Promise<boolean> {
|
|
625
|
+
try {
|
|
626
|
+
return (await stat(path)).isFile();
|
|
627
|
+
} catch {
|
|
628
|
+
return false;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// The loader resolves a bare plugin specifier from the profile file upwards (`createRequire(profilePath).resolve(name)`), never from the shell the harness was started in, so a marketplace package has to land in the package that owns the profile or it installs successfully and then fails to load.
|
|
633
|
+
async function marketplaceInstallDirectory(configPath: string, fallback: string): Promise<string> {
|
|
634
|
+
let directory = dirname(resolve(configPath));
|
|
635
|
+
let parent = dirname(directory);
|
|
636
|
+
while (parent !== directory) {
|
|
637
|
+
if (await isFile(join(directory, "package.json"))) return directory;
|
|
638
|
+
directory = parent;
|
|
639
|
+
parent = dirname(directory);
|
|
640
|
+
}
|
|
641
|
+
return fallback;
|
|
642
|
+
}
|
|
643
|
+
|
|
512
644
|
function pickDirectory(): Promise<string> {
|
|
513
645
|
return new Promise((resolvePath, reject) => {
|
|
514
646
|
execFile(
|
|
@@ -559,7 +691,11 @@ export default {
|
|
|
559
691
|
const events: AgentSessionEvent[] = [];
|
|
560
692
|
const eventClients = new Set<ServerResponse>();
|
|
561
693
|
const handleEvent = (event: AgentSessionEvent) => {
|
|
562
|
-
events.
|
|
694
|
+
// Streaming deltas reach clients live over SSE and each one carries the whole partial message, so only durable events are retained for snapshots.
|
|
695
|
+
if (event.type !== "message_update") {
|
|
696
|
+
events.push(event);
|
|
697
|
+
if (events.length > MAX_RETAINED_EVENTS) events.splice(0, events.length - MAX_RETAINED_EVENTS);
|
|
698
|
+
}
|
|
563
699
|
for (const response of eventClients) {
|
|
564
700
|
if (response.writableEnded || response.destroyed) {
|
|
565
701
|
eventClients.delete(response);
|
|
@@ -686,7 +822,12 @@ export default {
|
|
|
686
822
|
return;
|
|
687
823
|
}
|
|
688
824
|
const path = join(services.launch.agentDir, "settings.json");
|
|
689
|
-
|
|
825
|
+
// Replace the file atomically so an interrupted write never leaves a truncated settings.json behind; keep the existing permission bits.
|
|
826
|
+
const mode = await stat(path).then(
|
|
827
|
+
(info) => info.mode & 0o777,
|
|
828
|
+
() => 0o644,
|
|
829
|
+
);
|
|
830
|
+
await atomicWriteFile(path, JSON.stringify(parsed, null, 2) + "\n", { encoding: "utf8", mode });
|
|
690
831
|
await services.runtime.session.settingsManager.reload();
|
|
691
832
|
sendJson(response, 200, piConfig(services));
|
|
692
833
|
} catch (error) {
|
|
@@ -963,15 +1104,17 @@ export default {
|
|
|
963
1104
|
sendJson(response, 409, { error: "Plugin is already installed", plugin });
|
|
964
1105
|
return;
|
|
965
1106
|
}
|
|
966
|
-
const
|
|
967
|
-
const
|
|
1107
|
+
const installDirectory = await marketplaceInstallDirectory(configPath, services.launch.cwd);
|
|
1108
|
+
const packageJsonPath = join(installDirectory, "package.json");
|
|
1109
|
+
const packageLockPath = join(installDirectory, "package-lock.json");
|
|
968
1110
|
const packageJsonBefore = await readFile(packageJsonPath, "utf8").catch(() => undefined);
|
|
969
1111
|
const packageLockBefore = await readFile(packageLockPath, "utf8").catch(() => undefined);
|
|
970
1112
|
let profileBefore: string | undefined;
|
|
971
1113
|
let entryId: string | undefined;
|
|
972
1114
|
try {
|
|
973
1115
|
if (needsMarketplacePackageInstall(plugin)) {
|
|
974
|
-
|
|
1116
|
+
const specifier = `${marketplaceNpmPackageName(plugin.packageName)}@${plugin.version}`;
|
|
1117
|
+
await runProcess("npm", ["install", "--save-exact", "--package-lock=false", specifier], installDirectory);
|
|
975
1118
|
}
|
|
976
1119
|
profileBefore = await appendMarketplaceProfile(configPath, plugin);
|
|
977
1120
|
if (!needsMarketplacePackageInstall(plugin)) {
|
|
@@ -991,8 +1134,11 @@ export default {
|
|
|
991
1134
|
} catch (error) {
|
|
992
1135
|
if (entryId !== undefined) await loader.remove(entryId).catch(() => {});
|
|
993
1136
|
if (profileBefore !== undefined) await writeFile(configPath, profileBefore, "utf8").catch(() => {});
|
|
994
|
-
|
|
995
|
-
if (
|
|
1137
|
+
// A manifest npm created for this install is removed rather than left behind; restoring is only possible when one existed before.
|
|
1138
|
+
if (packageJsonBefore === undefined) await unlink(packageJsonPath).catch(() => {});
|
|
1139
|
+
else await writeFile(packageJsonPath, packageJsonBefore, "utf8").catch(() => {});
|
|
1140
|
+
if (packageLockBefore === undefined) await unlink(packageLockPath).catch(() => {});
|
|
1141
|
+
else await writeFile(packageLockPath, packageLockBefore, "utf8").catch(() => {});
|
|
996
1142
|
sendJson(response, 502, { error: errorText(error) });
|
|
997
1143
|
}
|
|
998
1144
|
} catch (error) {
|
|
@@ -1088,15 +1234,17 @@ export default {
|
|
|
1088
1234
|
}
|
|
1089
1235
|
const profileEntryId = entry.options.id;
|
|
1090
1236
|
const profileBefore = await readFile(configPath, "utf8");
|
|
1091
|
-
const
|
|
1092
|
-
const
|
|
1237
|
+
const installDirectory = await marketplaceInstallDirectory(configPath, services.launch.cwd);
|
|
1238
|
+
const packageJsonPath = join(installDirectory, "package.json");
|
|
1239
|
+
const packageLockPath = join(installDirectory, "package-lock.json");
|
|
1093
1240
|
const packageJsonBefore = await readFile(packageJsonPath, "utf8").catch(() => undefined);
|
|
1094
1241
|
const packageLockBefore = await readFile(packageLockPath, "utf8").catch(() => undefined);
|
|
1095
1242
|
await entry.parent.remove(entry.options.id);
|
|
1096
1243
|
entry.parent.tree.write();
|
|
1097
1244
|
try {
|
|
1098
1245
|
await updateMarketplaceProfile(configPath, profileEntryId, { remove: true });
|
|
1099
|
-
if (needsMarketplacePackageInstall(plugin))
|
|
1246
|
+
if (needsMarketplacePackageInstall(plugin))
|
|
1247
|
+
await runProcess("npm", ["uninstall", "--package-lock=false", marketplaceNpmPackageName(plugin.packageName)], installDirectory);
|
|
1100
1248
|
sendJson(response, 200, { uninstalled: true, id: payload.id });
|
|
1101
1249
|
} catch (error) {
|
|
1102
1250
|
await writeFile(configPath, profileBefore, "utf8").catch(() => {});
|
|
@@ -1192,20 +1340,28 @@ export default {
|
|
|
1192
1340
|
const disposeFiles = services.webServer.register({
|
|
1193
1341
|
path: "/api/files",
|
|
1194
1342
|
async handler(_request, response) {
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1343
|
+
let status: GitStatusResult;
|
|
1344
|
+
try {
|
|
1345
|
+
status = await gitStatus(activeCwd(services));
|
|
1346
|
+
} catch (error) {
|
|
1347
|
+
sendJson(response, 500, { error: errorText(error) });
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
const records = status.output.split("\0");
|
|
1351
|
+
const items: { path: string; status: string; label: string }[] = [];
|
|
1352
|
+
for (let index = 0; index < records.length; index += 1) {
|
|
1353
|
+
const record = records[index] ?? "";
|
|
1354
|
+
if (record.length < 4) continue;
|
|
1355
|
+
const code = record.slice(0, 2).trim() || "??";
|
|
1356
|
+
// Rename and copy records carry the original path in a second NUL-terminated field, which is not a workspace change of its own.
|
|
1357
|
+
if (code.startsWith("R") || code.startsWith("C")) index += 1;
|
|
1358
|
+
items.push({
|
|
1359
|
+
path: record.slice(3),
|
|
1360
|
+
status: code,
|
|
1361
|
+
label: code === "??" ? "untracked" : code.includes("D") ? "deleted" : code.includes("A") ? "added" : "modified",
|
|
1207
1362
|
});
|
|
1208
|
-
|
|
1363
|
+
}
|
|
1364
|
+
sendJson(response, 200, { items, ...(status.truncated ? { truncated: true } : {}) });
|
|
1209
1365
|
},
|
|
1210
1366
|
});
|
|
1211
1367
|
const disposeFileDiff = services.webServer.register({
|
|
@@ -1224,7 +1380,7 @@ export default {
|
|
|
1224
1380
|
const root = resolve(activeCwd(services));
|
|
1225
1381
|
const absolute = resolve(root, requested);
|
|
1226
1382
|
const relativePath = relative(root, absolute);
|
|
1227
|
-
if (isAbsolute(relativePath) || relativePath === ".." || relativePath.startsWith(".." +
|
|
1383
|
+
if (isAbsolute(relativePath) || relativePath === ".." || relativePath.startsWith(".." + sep)) {
|
|
1228
1384
|
sendJson(response, 400, { error: "path must stay inside the workspace" });
|
|
1229
1385
|
return;
|
|
1230
1386
|
}
|
|
@@ -1251,11 +1407,19 @@ export default {
|
|
|
1251
1407
|
}
|
|
1252
1408
|
const root = resolve(activeCwd(services));
|
|
1253
1409
|
const add = await gitCommand(root, ["add", "-A", "--", ...paths]);
|
|
1410
|
+
if (add.terminated) {
|
|
1411
|
+
sendJson(response, 500, { error: gitTerminationMessage("add", add.terminated) });
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1254
1414
|
if (add.code !== 0) {
|
|
1255
1415
|
sendJson(response, 409, { error: add.stderr.trim() || "Unable to stage workspace files" });
|
|
1256
1416
|
return;
|
|
1257
1417
|
}
|
|
1258
1418
|
const commit = await gitCommand(root, ["commit", "-m", payload.message.trim(), "--", ...paths]);
|
|
1419
|
+
if (commit.terminated) {
|
|
1420
|
+
sendJson(response, 500, { error: gitTerminationMessage("commit", commit.terminated) });
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1259
1423
|
if (commit.code !== 0) {
|
|
1260
1424
|
sendJson(response, 409, { error: commit.stdout.trim() || commit.stderr.trim() || "Nothing to commit" });
|
|
1261
1425
|
return;
|
|
@@ -1286,16 +1450,36 @@ export default {
|
|
|
1286
1450
|
sendJson(response, 400, { error: paths.message });
|
|
1287
1451
|
return;
|
|
1288
1452
|
}
|
|
1289
|
-
const untracked = await gitCommand(root, ["ls-files", "--others", "--exclude-standard", "--", ...paths]);
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1453
|
+
const untracked = await gitCommand(root, ["ls-files", "-z", "--others", "--exclude-standard", "--", ...paths]);
|
|
1454
|
+
// `git restore` refuses the whole request when any pathspec is unknown to it, so restore only the paths git tracks. `--with-tree=HEAD` keeps staged deletions in the list; it fails on an unborn HEAD, where the plain index listing is all there is.
|
|
1455
|
+
let tracked = await gitCommand(root, ["ls-files", "-z", "--with-tree=HEAD", "--", ...paths]);
|
|
1456
|
+
if (tracked.code !== 0 && tracked.terminated === undefined) tracked = await gitCommand(root, ["ls-files", "-z", "--", ...paths]);
|
|
1457
|
+
const listing = [untracked, tracked].find((result) => result.terminated !== undefined || result.code !== 0);
|
|
1458
|
+
if (listing) {
|
|
1459
|
+
if (listing.terminated) sendJson(response, 500, { error: gitTerminationMessage("ls-files", listing.terminated) });
|
|
1460
|
+
else sendJson(response, 409, { error: listing.stderr.trim() || "Unable to inspect workspace files" });
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
const untrackedPaths = untracked.stdout.split("\0").filter(Boolean);
|
|
1464
|
+
const trackedPaths = tracked.stdout.split("\0").filter(Boolean);
|
|
1465
|
+
const known = [...untrackedPaths, ...trackedPaths];
|
|
1466
|
+
const unknown = paths.filter((path) => !known.some((candidate) => candidate === path || candidate.startsWith(path + "/")));
|
|
1467
|
+
if (unknown.length > 0) {
|
|
1468
|
+
sendJson(response, 409, { error: `Paths did not match any file known to git: ${unknown.join(", ")}` });
|
|
1293
1469
|
return;
|
|
1294
1470
|
}
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
.
|
|
1298
|
-
|
|
1471
|
+
if (trackedPaths.length > 0) {
|
|
1472
|
+
const restore = await gitCommand(root, ["restore", "--worktree", "--staged", "--", ...trackedPaths]);
|
|
1473
|
+
if (restore.terminated) {
|
|
1474
|
+
sendJson(response, 500, { error: gitTerminationMessage("restore", restore.terminated) });
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
if (restore.code !== 0) {
|
|
1478
|
+
sendJson(response, 409, { error: restore.stderr.trim() || "Unable to restore workspace files" });
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
for (const path of untrackedPaths) {
|
|
1299
1483
|
await rm(resolve(root, path), { recursive: true, force: true });
|
|
1300
1484
|
}
|
|
1301
1485
|
sendJson(response, 200, { reverted: true, paths });
|
|
@@ -1332,6 +1516,10 @@ export default {
|
|
|
1332
1516
|
const disposePrompt = services.webServer.register({
|
|
1333
1517
|
path: "/api/prompt",
|
|
1334
1518
|
async handler(request, response) {
|
|
1519
|
+
if (request.method !== "POST") {
|
|
1520
|
+
sendJson(response, 405, { error: "Method not allowed" });
|
|
1521
|
+
return;
|
|
1522
|
+
}
|
|
1335
1523
|
if (busy) {
|
|
1336
1524
|
sendJson(response, 409, { error: "Another prompt is already running" });
|
|
1337
1525
|
return;
|
|
@@ -1583,9 +1771,9 @@ export default {
|
|
|
1583
1771
|
events.length = 0;
|
|
1584
1772
|
}
|
|
1585
1773
|
await unlink(path);
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1774
|
+
await mutateSessionMetadata(manager, context.logger, (metadata) => {
|
|
1775
|
+
delete metadata[path];
|
|
1776
|
+
});
|
|
1589
1777
|
sendJson(response, 200, { deleted: true, path, sessionFile: services.runtime.session.sessionFile });
|
|
1590
1778
|
} catch (error) {
|
|
1591
1779
|
sendJson(response, 400, { error: errorText(error) });
|
|
@@ -1607,16 +1795,17 @@ export default {
|
|
|
1607
1795
|
sendJson(response, 400, { error: "Invalid session path" });
|
|
1608
1796
|
return;
|
|
1609
1797
|
}
|
|
1610
|
-
const
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1798
|
+
const nextMetadata = await mutateSessionMetadata(manager, context.logger, (metadata) => {
|
|
1799
|
+
const currentMetadata = metadata[path] ?? {};
|
|
1800
|
+
const updated: SessionMetadata = {
|
|
1801
|
+
...currentMetadata,
|
|
1802
|
+
...(payload.archived === undefined ? {} : { archived: payload.archived === true }),
|
|
1803
|
+
...(payload.pinned === undefined ? {} : { pinned: payload.pinned === true }),
|
|
1804
|
+
};
|
|
1805
|
+
metadata[path] = updated;
|
|
1806
|
+
return updated;
|
|
1807
|
+
});
|
|
1808
|
+
sendJson(response, 200, { path, metadata: nextMetadata });
|
|
1620
1809
|
} catch (error) {
|
|
1621
1810
|
sendJson(response, 400, { error: errorText(error) });
|
|
1622
1811
|
}
|
|
@@ -1654,12 +1843,37 @@ export default {
|
|
|
1654
1843
|
sendJson(response, 409, { error: "Cannot batch-delete the active session" });
|
|
1655
1844
|
return;
|
|
1656
1845
|
}
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1846
|
+
if (action === "delete") {
|
|
1847
|
+
// The unlinks run outside the metadata mutation: a failure halfway through must not discard the key removals for the files that are already gone from disk.
|
|
1848
|
+
const removed: string[] = [];
|
|
1849
|
+
const failed: { path: string; error: string }[] = [];
|
|
1850
|
+
for (const path of paths) {
|
|
1851
|
+
try {
|
|
1852
|
+
await unlink(path);
|
|
1853
|
+
removed.push(path);
|
|
1854
|
+
} catch (error) {
|
|
1855
|
+
// A session another tab already deleted leaves nothing to unlink, but its metadata entry still has to go.
|
|
1856
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") removed.push(path);
|
|
1857
|
+
else failed.push({ path, error: errorText(error) });
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
await mutateSessionMetadata(manager, context.logger, (metadata) => {
|
|
1861
|
+
for (const path of removed) delete metadata[path];
|
|
1862
|
+
});
|
|
1863
|
+
if (failed.length > 0) {
|
|
1864
|
+
sendJson(response, 409, {
|
|
1865
|
+
error: `Some sessions could not be deleted: ${failed.map((item) => item.path).join(", ")}`,
|
|
1866
|
+
action,
|
|
1867
|
+
count: removed.length,
|
|
1868
|
+
failed,
|
|
1869
|
+
});
|
|
1870
|
+
return;
|
|
1871
|
+
}
|
|
1872
|
+
sendJson(response, 200, { action, count: removed.length });
|
|
1873
|
+
return;
|
|
1874
|
+
}
|
|
1875
|
+
await mutateSessionMetadata(manager, context.logger, (metadata) => {
|
|
1876
|
+
for (const path of paths) {
|
|
1663
1877
|
const current = metadata[path] ?? {};
|
|
1664
1878
|
metadata[path] = {
|
|
1665
1879
|
...current,
|
|
@@ -1667,8 +1881,7 @@ export default {
|
|
|
1667
1881
|
...(action === "pin" || action === "unpin" ? { pinned: action === "pin" } : {}),
|
|
1668
1882
|
};
|
|
1669
1883
|
}
|
|
1670
|
-
}
|
|
1671
|
-
await writeSessionMetadata(manager, metadata);
|
|
1884
|
+
});
|
|
1672
1885
|
sendJson(response, 200, { action, count: paths.length });
|
|
1673
1886
|
} catch (error) {
|
|
1674
1887
|
sendJson(response, 400, { error: errorText(error) });
|
|
@@ -1716,11 +1929,21 @@ export default {
|
|
|
1716
1929
|
return;
|
|
1717
1930
|
}
|
|
1718
1931
|
try {
|
|
1719
|
-
const payload = JSON.parse(await bodyText(request)) as {
|
|
1932
|
+
const payload = JSON.parse(await bodyText(request, IMPORT_BODY_LIMIT_BYTES)) as {
|
|
1933
|
+
path?: unknown;
|
|
1934
|
+
content?: unknown;
|
|
1935
|
+
filename?: unknown;
|
|
1936
|
+
cwd?: unknown;
|
|
1937
|
+
};
|
|
1720
1938
|
const suppliedPath = typeof payload.path === "string" ? payload.path : "";
|
|
1721
1939
|
const content = typeof payload.content === "string" ? payload.content : undefined;
|
|
1722
|
-
|
|
1723
|
-
|
|
1940
|
+
// `path` reads an existing file and `content` is written to a private temporary file; accepting both would let the request choose where the content lands.
|
|
1941
|
+
if (suppliedPath && content !== undefined) {
|
|
1942
|
+
sendJson(response, 400, { error: "Provide either a JSONL file path or file content, not both" });
|
|
1943
|
+
return;
|
|
1944
|
+
}
|
|
1945
|
+
if (content === undefined && (!suppliedPath || !isAbsolute(suppliedPath) || !suppliedPath.endsWith(".jsonl"))) {
|
|
1946
|
+
sendJson(response, 400, { error: "An absolute .jsonl file path or file content is required" });
|
|
1724
1947
|
return;
|
|
1725
1948
|
}
|
|
1726
1949
|
const manager = services.runtime.session.sessionManager;
|
|
@@ -1730,12 +1953,12 @@ export default {
|
|
|
1730
1953
|
}
|
|
1731
1954
|
const targetCwd = typeof payload.cwd === "string" && payload.cwd.trim() ? resolve(payload.cwd) : activeCwd(services);
|
|
1732
1955
|
const temporaryDirectory = content === undefined ? undefined : await mkdtemp(join(tmpdir(), "pi-harness-import-"));
|
|
1733
|
-
const
|
|
1734
|
-
|
|
1735
|
-
|
|
1956
|
+
const requestedName = basename(typeof payload.filename === "string" && payload.filename.trim() ? payload.filename : "import.jsonl");
|
|
1957
|
+
const importName = requestedName === "" || requestedName === "." || requestedName === ".." ? "import.jsonl" : requestedName;
|
|
1958
|
+
const importPath = temporaryDirectory === undefined ? suppliedPath : join(temporaryDirectory, importName);
|
|
1736
1959
|
try {
|
|
1737
1960
|
if (content !== undefined) {
|
|
1738
|
-
if (Buffer.byteLength(content, "utf8") >
|
|
1961
|
+
if (Buffer.byteLength(content, "utf8") > IMPORT_CONTENT_LIMIT_BYTES) {
|
|
1739
1962
|
sendJson(response, 413, { error: "Imported session must be at most 10 MiB" });
|
|
1740
1963
|
return;
|
|
1741
1964
|
}
|
|
@@ -1759,7 +1982,7 @@ export default {
|
|
|
1759
1982
|
if (temporaryDirectory) await rm(temporaryDirectory, { recursive: true, force: true });
|
|
1760
1983
|
}
|
|
1761
1984
|
} catch (error) {
|
|
1762
|
-
sendJson(response, 400, { error: errorText(error) });
|
|
1985
|
+
sendJson(response, error instanceof PayloadTooLargeError ? 413 : 400, { error: errorText(error) });
|
|
1763
1986
|
}
|
|
1764
1987
|
},
|
|
1765
1988
|
});
|
|
@@ -1796,7 +2019,7 @@ export default {
|
|
|
1796
2019
|
const page = Math.max(0, Number.parseInt(url.searchParams.get("page") ?? "0", 10) || 0);
|
|
1797
2020
|
const pageSize = Math.min(100, Math.max(1, Number.parseInt(url.searchParams.get("pageSize") ?? "50", 10) || 50));
|
|
1798
2021
|
const includeArchived = url.searchParams.get("includeArchived") === "true";
|
|
1799
|
-
const metadata = await readSessionMetadata(manager);
|
|
2022
|
+
const metadata = await readSessionMetadata(manager, context.logger);
|
|
1800
2023
|
const items =
|
|
1801
2024
|
typeof manager.isPersisted === "function" && manager.isPersisted() ? await SessionManager.list(services.launch.cwd, manager.getSessionDir()) : [];
|
|
1802
2025
|
const filtered = items.filter((item) => includeArchived || metadata[item.path]?.archived !== true);
|