@pi-harness/pi-harness 0.1.23 → 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 +11 -9
- 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 +27 -2
- package/apps/web/server-dist/bin.js.map +1 -1
- package/apps/web/src/server/bin.ts +27 -2
- package/apps/web/test/bin.test.ts +228 -5
- package/node_modules/@pi-harness/api-gateway/dist/index.d.ts.map +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/index.js +113 -42
- 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 +1 -1
- 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/relaunch.d.ts.map +1 -1
- package/node_modules/@pi-harness/cli/dist/relaunch.js +2 -4
- 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 +39 -5
- 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.map +1 -1
- package/node_modules/@pi-harness/web-app/dist/index.js +12 -3
- 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 +7 -7
- package/packages/api-gateway/dist/index.d.ts.map +1 -1
- package/packages/api-gateway/dist/index.js +113 -42
- 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 +1 -1
- 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 +104 -40
- 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 +1 -1
- 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 +438 -4
- 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-package.test.ts +32 -1
- package/packages/api-gateway/test/release-version.test.ts +40 -0
- package/packages/bundle-web-app/dist/index.d.ts.map +1 -1
- package/packages/bundle-web-app/dist/index.js +12 -3
- 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 +13 -3
- package/packages/bundle-web-app/test/index.test.ts +83 -2
- 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/relaunch.d.ts.map +1 -1
- package/packages/cli/dist/relaunch.js +2 -4
- 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/relaunch.ts +2 -3
- package/packages/cli/test/cli.test.ts +3 -1
- package/packages/cli/test/main.test.ts +134 -0
- package/packages/cli/test/relaunch.test.ts +86 -11
- 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 +39 -5
- 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 +44 -6
- package/packages/host-webserver/test/index.test.ts +36 -5
- package/apps/web/dist/assets/index-BVI_IS-R.css +0 -2
- package/apps/web/dist/assets/index-Bo9CNI_U.js +0 -22
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { mkdir, mkdtemp, rm } from "node:fs/promises";
|
|
4
|
+
import { request as httpRequest } from "node:http";
|
|
5
|
+
import { createServer } from "node:net";
|
|
3
6
|
import { tmpdir } from "node:os";
|
|
4
7
|
import { join } from "node:path";
|
|
8
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
5
9
|
import { afterEach, describe, expect, test } from "vitest";
|
|
6
10
|
|
|
7
11
|
const BIN = join(import.meta.dirname, "..", "server-dist", "bin.js");
|
|
@@ -14,18 +18,35 @@ interface LauncherRun {
|
|
|
14
18
|
readonly stderr: string;
|
|
15
19
|
}
|
|
16
20
|
|
|
21
|
+
interface LauncherExit {
|
|
22
|
+
readonly code: number | null;
|
|
23
|
+
readonly stdout: string;
|
|
24
|
+
readonly stderr: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
17
27
|
const directories: string[] = [];
|
|
18
28
|
|
|
19
29
|
afterEach(async () => {
|
|
20
30
|
await Promise.all(directories.splice(0).map(async (directory) => rm(directory, { recursive: true, force: true })));
|
|
21
31
|
});
|
|
22
32
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
33
|
+
async function makeTempDir(label: string): Promise<string> {
|
|
34
|
+
const directory = await mkdtemp(join(tmpdir(), `pi-harness-web-bin-${label}-`));
|
|
35
|
+
directories.push(directory);
|
|
36
|
+
return directory;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Boots the built launcher on an ephemeral port with an isolated agent directory, waits for the console URL, runs the optional probe against the live server, sends the signal, and reports how the process exited.
|
|
40
|
+
async function runLauncher(
|
|
41
|
+
signal: NodeJS.Signals,
|
|
42
|
+
extraEnv: Record<string, string> = {},
|
|
43
|
+
spawnCwd?: string,
|
|
44
|
+
probe?: (consoleUrl: string) => Promise<void>,
|
|
45
|
+
): Promise<LauncherRun> {
|
|
46
|
+
const agentDir = await makeTempDir("agent");
|
|
27
47
|
const child = spawn(process.execPath, [BIN], {
|
|
28
48
|
stdio: ["ignore", "pipe", "pipe"],
|
|
49
|
+
...(spawnCwd === undefined ? {} : { cwd: spawnCwd }),
|
|
29
50
|
env: {
|
|
30
51
|
...process.env,
|
|
31
52
|
PI_HARNESS_PORT: "0",
|
|
@@ -57,6 +78,12 @@ async function runLauncher(signal: NodeJS.Signals, extraEnv: Record<string, stri
|
|
|
57
78
|
child.stdout.on("data", check);
|
|
58
79
|
void exit.then(({ code }) => reject(new Error("launcher exited before printing the console URL (code " + String(code) + "): " + stderr)));
|
|
59
80
|
});
|
|
81
|
+
try {
|
|
82
|
+
await probe?.(consoleUrl);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
child.kill("SIGKILL");
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
60
87
|
const started = Date.now();
|
|
61
88
|
child.kill(signal);
|
|
62
89
|
const timeout = setTimeout(() => child.kill("SIGKILL"), 15_000);
|
|
@@ -65,6 +92,59 @@ async function runLauncher(signal: NodeJS.Signals, extraEnv: Record<string, stri
|
|
|
65
92
|
return { consoleUrl, ...result, elapsedMs: Date.now() - started, stderr };
|
|
66
93
|
}
|
|
67
94
|
|
|
95
|
+
// Runs the built launcher until it exits on its own, for the startup guards that must abort before a console URL is ever printed.
|
|
96
|
+
async function runLauncherToExit(extraEnv: Record<string, string>): Promise<LauncherExit> {
|
|
97
|
+
const agentDir = await makeTempDir("guard");
|
|
98
|
+
const child = spawn(process.execPath, [BIN], {
|
|
99
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
100
|
+
env: {
|
|
101
|
+
...process.env,
|
|
102
|
+
PI_HARNESS_PORT: "0",
|
|
103
|
+
PI_AGENT_DIR: agentDir,
|
|
104
|
+
PI_HARNESS_PROVIDER: "anthropic",
|
|
105
|
+
PI_HARNESS_MODEL: "claude-sonnet-4-5",
|
|
106
|
+
PI_HARNESS_DISABLE_UPDATE_CHECK: "1",
|
|
107
|
+
...extraEnv,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
let stdout = "";
|
|
111
|
+
let stderr = "";
|
|
112
|
+
child.stdout.setEncoding("utf8");
|
|
113
|
+
child.stderr.setEncoding("utf8");
|
|
114
|
+
child.stdout.on("data", (chunk: string) => {
|
|
115
|
+
stdout += chunk;
|
|
116
|
+
});
|
|
117
|
+
child.stderr.on("data", (chunk: string) => {
|
|
118
|
+
stderr += chunk;
|
|
119
|
+
});
|
|
120
|
+
const timeout = setTimeout(() => child.kill("SIGKILL"), 30_000);
|
|
121
|
+
const code = await new Promise<number | null>((resolve) => child.once("exit", (exitCode) => resolve(exitCode)));
|
|
122
|
+
clearTimeout(timeout);
|
|
123
|
+
return { code, stdout, stderr };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// fetch() drops a caller-supplied Host header because "host" is a forbidden header name, so a request that has to carry a proxy hostname goes out over node:http instead.
|
|
127
|
+
function statusWithHostHeader(port: number, hostHeader: string): Promise<number> {
|
|
128
|
+
return new Promise((resolve, reject) => {
|
|
129
|
+
const outgoing = httpRequest({ host: "127.0.0.1", port, path: "/api/status", headers: { host: hostHeader } }, (response) => {
|
|
130
|
+
response.resume();
|
|
131
|
+
resolve(response.statusCode ?? 0);
|
|
132
|
+
});
|
|
133
|
+
outgoing.once("error", reject);
|
|
134
|
+
outgoing.end();
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Takes a loopback port from the kernel and releases it again, so a launcher run can be probed over HTTP without reading the console URL from stdout.
|
|
139
|
+
async function reserveLoopbackPort(): Promise<number> {
|
|
140
|
+
const server = createServer();
|
|
141
|
+
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
142
|
+
const address = server.address();
|
|
143
|
+
const port = typeof address === "object" && address !== null ? address.port : 0;
|
|
144
|
+
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
145
|
+
return port;
|
|
146
|
+
}
|
|
147
|
+
|
|
68
148
|
describe("web launcher", () => {
|
|
69
149
|
test("completes graceful shutdown on SIGINT and exits with code 130 instead of dying by signal", async () => {
|
|
70
150
|
const run = await runLauncher("SIGINT");
|
|
@@ -87,4 +167,147 @@ describe("web launcher", () => {
|
|
|
87
167
|
expect(run.consoleUrl).toMatch(/^http:\/\/\[::1\]:\d+$/u);
|
|
88
168
|
expect(run.code).toBe(130);
|
|
89
169
|
}, 30_000);
|
|
170
|
+
|
|
171
|
+
test("resolves a relative PI_AGENT_DIR against the working directory and ignores surrounding whitespace", async () => {
|
|
172
|
+
const workingDir = await makeTempDir("cwd");
|
|
173
|
+
const home = await makeTempDir("home");
|
|
174
|
+
await mkdir(join(workingDir, "agent-state"));
|
|
175
|
+
|
|
176
|
+
const run = await runLauncher("SIGINT", { PI_AGENT_DIR: " ./agent-state ", HOME: home }, workingDir);
|
|
177
|
+
|
|
178
|
+
expect(run.consoleUrl).toMatch(/^http:\/\/127\.0\.0\.1:\d+$/u);
|
|
179
|
+
expect(run.stderr).not.toContain("must be an absolute path");
|
|
180
|
+
// Booting writes the model store into the agent directory, so these two paths pin which base the relative value resolved against: the working directory, not the home directory.
|
|
181
|
+
expect(existsSync(join(workingDir, "agent-state", "models-store.json"))).toBe(true);
|
|
182
|
+
expect(existsSync(join(home, "agent-state"))).toBe(false);
|
|
183
|
+
expect(run.code).toBe(130);
|
|
184
|
+
}, 60_000);
|
|
185
|
+
|
|
186
|
+
test("treats a blank PI_AGENT_DIR as unset and falls back to the home agent directory", async () => {
|
|
187
|
+
const workingDir = await makeTempDir("cwd");
|
|
188
|
+
const home = await makeTempDir("home");
|
|
189
|
+
await mkdir(join(home, ".pi", "agent"), { recursive: true });
|
|
190
|
+
|
|
191
|
+
const run = await runLauncher("SIGINT", { PI_AGENT_DIR: " ", HOME: home }, workingDir);
|
|
192
|
+
|
|
193
|
+
expect(run.stderr).not.toContain("must be an absolute path");
|
|
194
|
+
expect(existsSync(join(home, ".pi", "agent", "models-store.json"))).toBe(true);
|
|
195
|
+
expect(existsSync(join(workingDir, ".pi"))).toBe(false);
|
|
196
|
+
expect(run.code).toBe(130);
|
|
197
|
+
}, 60_000);
|
|
198
|
+
|
|
199
|
+
test("keeps serving and shuts down gracefully when a closed stdout pipe breaks the console URL write", async () => {
|
|
200
|
+
const agentDir = await makeTempDir("epipe");
|
|
201
|
+
const port = await reserveLoopbackPort();
|
|
202
|
+
const child = spawn(process.execPath, [BIN], {
|
|
203
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
204
|
+
env: {
|
|
205
|
+
...process.env,
|
|
206
|
+
PI_HARNESS_PORT: String(port),
|
|
207
|
+
PI_AGENT_DIR: agentDir,
|
|
208
|
+
PI_HARNESS_PROVIDER: "anthropic",
|
|
209
|
+
PI_HARNESS_MODEL: "claude-sonnet-4-5",
|
|
210
|
+
PI_HARNESS_DISABLE_UPDATE_CHECK: "1",
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
let stderr = "";
|
|
214
|
+
let exited = false;
|
|
215
|
+
child.stderr.setEncoding("utf8");
|
|
216
|
+
child.stderr.on("data", (chunk: string) => {
|
|
217
|
+
stderr += chunk;
|
|
218
|
+
});
|
|
219
|
+
const exit = new Promise<{ code: number | null; signal: NodeJS.Signals | null }>((resolve) =>
|
|
220
|
+
child.once("exit", (code, signal) => {
|
|
221
|
+
exited = true;
|
|
222
|
+
resolve({ code, signal });
|
|
223
|
+
}),
|
|
224
|
+
);
|
|
225
|
+
// Closing the read end of the pipe is what `pi-harness | head -1` does: the launcher's next write raises EPIPE on process.stdout.
|
|
226
|
+
child.stdout.destroy();
|
|
227
|
+
|
|
228
|
+
await expect
|
|
229
|
+
.poll(
|
|
230
|
+
async () => {
|
|
231
|
+
if (exited) return "exited";
|
|
232
|
+
try {
|
|
233
|
+
await fetch(`http://127.0.0.1:${String(port)}/`);
|
|
234
|
+
return "listening";
|
|
235
|
+
} catch {
|
|
236
|
+
return "starting";
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
{ interval: 200, timeout: 40_000 },
|
|
240
|
+
)
|
|
241
|
+
.not.toBe("starting");
|
|
242
|
+
// The console URL is written shortly after the server starts listening, so wait past that point before deciding the process survived the broken pipe.
|
|
243
|
+
await delay(3_000);
|
|
244
|
+
|
|
245
|
+
expect(stderr).not.toContain("EPIPE");
|
|
246
|
+
expect(exited).toBe(false);
|
|
247
|
+
child.kill("SIGINT");
|
|
248
|
+
const result = await exit;
|
|
249
|
+
expect(result.code).toBe(130);
|
|
250
|
+
}, 60_000);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe("web launcher startup guards", () => {
|
|
254
|
+
test("refuses a non-loopback PI_HARNESS_HOST while remote access is not opted in", async () => {
|
|
255
|
+
for (const remoteHost of ["0.0.0.0", "192.0.2.10"]) {
|
|
256
|
+
const run = await runLauncherToExit({ PI_HARNESS_HOST: remoteHost });
|
|
257
|
+
|
|
258
|
+
expect(run.stderr).toContain("Refusing non-loopback PI_HARNESS_HOST");
|
|
259
|
+
expect(run.stdout).not.toContain("Pi Harness web console:");
|
|
260
|
+
expect(run.code).not.toBe(0);
|
|
261
|
+
}
|
|
262
|
+
}, 60_000);
|
|
263
|
+
|
|
264
|
+
test("binds a host outside the loopback allowlist once PI_HARNESS_ALLOW_REMOTE opts in", async () => {
|
|
265
|
+
// "LOCALHOST" is not one of the literals the launcher treats as loopback, so it takes the opt-in path, yet getaddrinfo still resolves it to the loopback address: the bind succeeds without putting the console on the network.
|
|
266
|
+
const run = await runLauncher("SIGINT", { PI_HARNESS_HOST: "LOCALHOST", PI_HARNESS_ALLOW_REMOTE: "1" });
|
|
267
|
+
|
|
268
|
+
expect(run.stderr).not.toContain("Refusing non-loopback PI_HARNESS_HOST");
|
|
269
|
+
expect(run.consoleUrl).toMatch(/^http:\/\/LOCALHOST:\d+$/u);
|
|
270
|
+
expect(run.code).toBe(130);
|
|
271
|
+
}, 60_000);
|
|
272
|
+
|
|
273
|
+
test("hands an opted-in host to listen and reports the bind failure when no interface owns it", async () => {
|
|
274
|
+
// 192.0.2.10 is TEST-NET-1 and is assigned to no interface, so this pins that the guard let the host through: the run dies in listen rather than in the guard.
|
|
275
|
+
const run = await runLauncherToExit({ PI_HARNESS_HOST: "192.0.2.10", PI_HARNESS_ALLOW_REMOTE: "1" });
|
|
276
|
+
|
|
277
|
+
expect(run.stderr).not.toContain("Refusing non-loopback PI_HARNESS_HOST");
|
|
278
|
+
expect(run.stderr).toContain("EADDRNOTAVAIL");
|
|
279
|
+
expect(run.code).toBe(1);
|
|
280
|
+
}, 60_000);
|
|
281
|
+
|
|
282
|
+
test("accepts a proxy hostname in the Host header only when PI_HARNESS_ALLOWED_HOSTS names it", async () => {
|
|
283
|
+
const statuses: number[] = [];
|
|
284
|
+
const run = await runLauncher("SIGINT", { PI_HARNESS_ALLOWED_HOSTS: " proxy.internal.example , " }, undefined, async (consoleUrl) => {
|
|
285
|
+
const port = Number(new URL(consoleUrl).port);
|
|
286
|
+
statuses.push(await statusWithHostHeader(port, `proxy.internal.example:${String(port)}`));
|
|
287
|
+
statuses.push(await statusWithHostHeader(port, `other.internal.example:${String(port)}`));
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
expect(statuses).toEqual([200, 400]);
|
|
291
|
+
expect(run.code).toBe(130);
|
|
292
|
+
}, 60_000);
|
|
293
|
+
|
|
294
|
+
test("rejects a PI_HARNESS_ALLOWED_HOSTS entry that is not a bare hostname", async () => {
|
|
295
|
+
for (const entry of ["https://proxy.example", "proxy.example:8443", "proxy.example/app"]) {
|
|
296
|
+
const run = await runLauncherToExit({ PI_HARNESS_ALLOWED_HOSTS: entry });
|
|
297
|
+
|
|
298
|
+
expect(run.stderr).toContain("PI_HARNESS_ALLOWED_HOSTS entries must be bare hostnames");
|
|
299
|
+
expect(run.stdout).not.toContain("Pi Harness web console:");
|
|
300
|
+
expect(run.code).not.toBe(0);
|
|
301
|
+
}
|
|
302
|
+
}, 60_000);
|
|
303
|
+
|
|
304
|
+
test("rejects a PI_HARNESS_PORT that is not an integer inside the valid range", async () => {
|
|
305
|
+
for (const badPort of ["70000", "-1", "abc"]) {
|
|
306
|
+
const run = await runLauncherToExit({ PI_HARNESS_PORT: badPort });
|
|
307
|
+
|
|
308
|
+
expect(run.stderr).toContain("PI_HARNESS_PORT must be an integer between 0 and 65535");
|
|
309
|
+
expect(run.stdout).not.toContain("Pi Harness web console:");
|
|
310
|
+
expect(run.code).not.toBe(0);
|
|
311
|
+
}
|
|
312
|
+
}, 60_000);
|
|
90
313
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;;;;mBAkqBlC,OAAO;;AAHxB,wBAg5CE"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { mkdir, mkdtemp, readFile, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
3
|
-
import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { atomicWriteFile } from "@pi-harness/core";
|
|
7
|
-
import { MARKETPLACE_CAPABILITIES, MARKETPLACE_CATEGORIES, MARKETPLACE_PLUGINS, attachMarketplaceStatistics, createMarketplaceStatisticsLoader, paginateMarketplace, searchMarketplace, needsMarketplacePackageInstall, sortMarketplaceByRecommendation, } from "./marketplace.js";
|
|
7
|
+
import { MARKETPLACE_CAPABILITIES, MARKETPLACE_CATEGORIES, MARKETPLACE_PLUGINS, attachMarketplaceStatistics, createMarketplaceStatisticsLoader, paginateMarketplace, searchMarketplace, needsMarketplacePackageInstall, marketplaceNpmPackageName, sortMarketplaceByRecommendation, } from "./marketplace.js";
|
|
8
8
|
import { parseGitWorktrees } from "./workspaces.js";
|
|
9
9
|
const loadMarketplaceStatistics = createMarketplaceStatisticsLoader();
|
|
10
10
|
const DEFAULT_BODY_LIMIT_BYTES = 64 * 1024;
|
|
@@ -25,18 +25,19 @@ function sendJson(response, status, payload) {
|
|
|
25
25
|
response.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store", "content-length": Buffer.byteLength(body) });
|
|
26
26
|
response.end(body);
|
|
27
27
|
}
|
|
28
|
+
// 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.
|
|
28
29
|
async function bodyText(request, maxBytes = DEFAULT_BODY_LIMIT_BYTES) {
|
|
29
30
|
const chunks = [];
|
|
30
31
|
let length = 0;
|
|
31
32
|
for await (const chunk of request) {
|
|
32
33
|
const input = chunk;
|
|
33
|
-
const
|
|
34
|
-
length +=
|
|
34
|
+
const buffer = typeof input === "string" ? Buffer.from(input, "utf8") : Buffer.isBuffer(input) ? input : Buffer.from(input);
|
|
35
|
+
length += buffer.length;
|
|
35
36
|
if (length > maxBytes)
|
|
36
37
|
throw new PayloadTooLargeError();
|
|
37
|
-
chunks.push(
|
|
38
|
+
chunks.push(buffer);
|
|
38
39
|
}
|
|
39
|
-
return chunks.
|
|
40
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
40
41
|
}
|
|
41
42
|
function errorText(error) {
|
|
42
43
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -56,10 +57,16 @@ function jsonSafe(value, seen = new WeakSet()) {
|
|
|
56
57
|
return Object.prototype.toString.call(value);
|
|
57
58
|
if (seen.has(value))
|
|
58
59
|
return "[Circular]";
|
|
60
|
+
// 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.
|
|
59
61
|
seen.add(value);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
try {
|
|
63
|
+
if (Array.isArray(value))
|
|
64
|
+
return value.map((item) => jsonSafe(item, seen));
|
|
65
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, jsonSafe(item, seen)]));
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
seen.delete(value);
|
|
69
|
+
}
|
|
63
70
|
}
|
|
64
71
|
function createStatus(services, events) {
|
|
65
72
|
const activeModel = services.runtime.session.model ?? services.models.model;
|
|
@@ -306,6 +313,7 @@ function sanitizeLogValue(value, state, depth = 0) {
|
|
|
306
313
|
return "[Max Depth]";
|
|
307
314
|
if (state.seen.has(value))
|
|
308
315
|
return "[Circular]";
|
|
316
|
+
// Unmarked once its subtree is done, so an object logged twice as a sibling is rendered twice instead of being mistaken for a cycle.
|
|
309
317
|
state.seen.add(value);
|
|
310
318
|
try {
|
|
311
319
|
if (value instanceof Date)
|
|
@@ -339,6 +347,9 @@ function sanitizeLogValue(value, state, depth = 0) {
|
|
|
339
347
|
catch {
|
|
340
348
|
return "[Unavailable]";
|
|
341
349
|
}
|
|
350
|
+
finally {
|
|
351
|
+
state.seen.delete(value);
|
|
352
|
+
}
|
|
342
353
|
}
|
|
343
354
|
function sanitizeLogArguments(args) {
|
|
344
355
|
const sanitized = sanitizeLogValue(args, { remaining: loggerPanelNodeLimit, seen: new WeakSet() });
|
|
@@ -478,17 +489,18 @@ function gitTerminationMessage(command, termination) {
|
|
|
478
489
|
? `git ${command} timed out after ${GIT_TIMEOUT_MS / 1000} seconds`
|
|
479
490
|
: `git ${command} produced more output than the buffer limit allows`;
|
|
480
491
|
}
|
|
492
|
+
// `-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.
|
|
481
493
|
function gitStatus(cwd) {
|
|
482
494
|
return new Promise((resolveStatus, rejectStatus) => {
|
|
483
|
-
execFile("git", ["status", "--short", "--untracked-files=all"], { cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: 512 * 1024 }, (error, stdout, stderr) => {
|
|
495
|
+
execFile("git", ["status", "--short", "-z", "--untracked-files=all"], { cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: 512 * 1024 }, (error, stdout, stderr) => {
|
|
484
496
|
if (error === null) {
|
|
485
497
|
resolveStatus({ output: stdout, truncated: false });
|
|
486
498
|
return;
|
|
487
499
|
}
|
|
488
500
|
const termination = gitTermination(error);
|
|
489
501
|
if (termination === "output-limit") {
|
|
490
|
-
// Keep the complete
|
|
491
|
-
resolveStatus({ output: stdout.slice(0, stdout.lastIndexOf("\
|
|
502
|
+
// Keep the complete records that arrived before the kill; the final one may have been cut mid-path.
|
|
503
|
+
resolveStatus({ output: stdout.slice(0, stdout.lastIndexOf("\0") + 1), truncated: true });
|
|
492
504
|
return;
|
|
493
505
|
}
|
|
494
506
|
if (termination === undefined && error.code === 128 && /not a git repository/i.test(stderr)) {
|
|
@@ -539,6 +551,26 @@ async function isDirectory(path) {
|
|
|
539
551
|
return false;
|
|
540
552
|
}
|
|
541
553
|
}
|
|
554
|
+
async function isFile(path) {
|
|
555
|
+
try {
|
|
556
|
+
return (await stat(path)).isFile();
|
|
557
|
+
}
|
|
558
|
+
catch {
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
// 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.
|
|
563
|
+
async function marketplaceInstallDirectory(configPath, fallback) {
|
|
564
|
+
let directory = dirname(resolve(configPath));
|
|
565
|
+
let parent = dirname(directory);
|
|
566
|
+
while (parent !== directory) {
|
|
567
|
+
if (await isFile(join(directory, "package.json")))
|
|
568
|
+
return directory;
|
|
569
|
+
directory = parent;
|
|
570
|
+
parent = dirname(directory);
|
|
571
|
+
}
|
|
572
|
+
return fallback;
|
|
573
|
+
}
|
|
542
574
|
function pickDirectory() {
|
|
543
575
|
return new Promise((resolvePath, reject) => {
|
|
544
576
|
execFile("osascript", ["-e", 'POSIX path of (choose folder with prompt "选择 Pi 工作区")'], { timeout: 120_000, maxBuffer: 64 * 1024 }, (error, stdout, stderr) => {
|
|
@@ -989,15 +1021,17 @@ export default {
|
|
|
989
1021
|
sendJson(response, 409, { error: "Plugin is already installed", plugin });
|
|
990
1022
|
return;
|
|
991
1023
|
}
|
|
992
|
-
const
|
|
993
|
-
const
|
|
1024
|
+
const installDirectory = await marketplaceInstallDirectory(configPath, services.launch.cwd);
|
|
1025
|
+
const packageJsonPath = join(installDirectory, "package.json");
|
|
1026
|
+
const packageLockPath = join(installDirectory, "package-lock.json");
|
|
994
1027
|
const packageJsonBefore = await readFile(packageJsonPath, "utf8").catch(() => undefined);
|
|
995
1028
|
const packageLockBefore = await readFile(packageLockPath, "utf8").catch(() => undefined);
|
|
996
1029
|
let profileBefore;
|
|
997
1030
|
let entryId;
|
|
998
1031
|
try {
|
|
999
1032
|
if (needsMarketplacePackageInstall(plugin)) {
|
|
1000
|
-
|
|
1033
|
+
const specifier = `${marketplaceNpmPackageName(plugin.packageName)}@${plugin.version}`;
|
|
1034
|
+
await runProcess("npm", ["install", "--save-exact", "--package-lock=false", specifier], installDirectory);
|
|
1001
1035
|
}
|
|
1002
1036
|
profileBefore = await appendMarketplaceProfile(configPath, plugin);
|
|
1003
1037
|
if (!needsMarketplacePackageInstall(plugin)) {
|
|
@@ -1021,9 +1055,14 @@ export default {
|
|
|
1021
1055
|
await loader.remove(entryId).catch(() => { });
|
|
1022
1056
|
if (profileBefore !== undefined)
|
|
1023
1057
|
await writeFile(configPath, profileBefore, "utf8").catch(() => { });
|
|
1024
|
-
|
|
1058
|
+
// A manifest npm created for this install is removed rather than left behind; restoring is only possible when one existed before.
|
|
1059
|
+
if (packageJsonBefore === undefined)
|
|
1060
|
+
await unlink(packageJsonPath).catch(() => { });
|
|
1061
|
+
else
|
|
1025
1062
|
await writeFile(packageJsonPath, packageJsonBefore, "utf8").catch(() => { });
|
|
1026
|
-
if (packageLockBefore
|
|
1063
|
+
if (packageLockBefore === undefined)
|
|
1064
|
+
await unlink(packageLockPath).catch(() => { });
|
|
1065
|
+
else
|
|
1027
1066
|
await writeFile(packageLockPath, packageLockBefore, "utf8").catch(() => { });
|
|
1028
1067
|
sendJson(response, 502, { error: errorText(error) });
|
|
1029
1068
|
}
|
|
@@ -1125,8 +1164,9 @@ export default {
|
|
|
1125
1164
|
}
|
|
1126
1165
|
const profileEntryId = entry.options.id;
|
|
1127
1166
|
const profileBefore = await readFile(configPath, "utf8");
|
|
1128
|
-
const
|
|
1129
|
-
const
|
|
1167
|
+
const installDirectory = await marketplaceInstallDirectory(configPath, services.launch.cwd);
|
|
1168
|
+
const packageJsonPath = join(installDirectory, "package.json");
|
|
1169
|
+
const packageLockPath = join(installDirectory, "package-lock.json");
|
|
1130
1170
|
const packageJsonBefore = await readFile(packageJsonPath, "utf8").catch(() => undefined);
|
|
1131
1171
|
const packageLockBefore = await readFile(packageLockPath, "utf8").catch(() => undefined);
|
|
1132
1172
|
await entry.parent.remove(entry.options.id);
|
|
@@ -1134,7 +1174,7 @@ export default {
|
|
|
1134
1174
|
try {
|
|
1135
1175
|
await updateMarketplaceProfile(configPath, profileEntryId, { remove: true });
|
|
1136
1176
|
if (needsMarketplacePackageInstall(plugin))
|
|
1137
|
-
await runProcess("npm", ["uninstall", "--package-lock=false", plugin.packageName],
|
|
1177
|
+
await runProcess("npm", ["uninstall", "--package-lock=false", marketplaceNpmPackageName(plugin.packageName)], installDirectory);
|
|
1138
1178
|
sendJson(response, 200, { uninstalled: true, id: payload.id });
|
|
1139
1179
|
}
|
|
1140
1180
|
catch (error) {
|
|
@@ -1246,18 +1286,22 @@ export default {
|
|
|
1246
1286
|
sendJson(response, 500, { error: errorText(error) });
|
|
1247
1287
|
return;
|
|
1248
1288
|
}
|
|
1249
|
-
const
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
.
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1289
|
+
const records = status.output.split("\0");
|
|
1290
|
+
const items = [];
|
|
1291
|
+
for (let index = 0; index < records.length; index += 1) {
|
|
1292
|
+
const record = records[index] ?? "";
|
|
1293
|
+
if (record.length < 4)
|
|
1294
|
+
continue;
|
|
1295
|
+
const code = record.slice(0, 2).trim() || "??";
|
|
1296
|
+
// Rename and copy records carry the original path in a second NUL-terminated field, which is not a workspace change of its own.
|
|
1297
|
+
if (code.startsWith("R") || code.startsWith("C"))
|
|
1298
|
+
index += 1;
|
|
1299
|
+
items.push({
|
|
1300
|
+
path: record.slice(3),
|
|
1301
|
+
status: code,
|
|
1302
|
+
label: code === "??" ? "untracked" : code.includes("D") ? "deleted" : code.includes("A") ? "added" : "modified",
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1261
1305
|
sendJson(response, 200, { items, ...(status.truncated ? { truncated: true } : {}) });
|
|
1262
1306
|
},
|
|
1263
1307
|
});
|
|
@@ -1744,21 +1788,48 @@ export default {
|
|
|
1744
1788
|
sendJson(response, 409, { error: "Cannot batch-delete the active session" });
|
|
1745
1789
|
return;
|
|
1746
1790
|
}
|
|
1747
|
-
|
|
1791
|
+
if (action === "delete") {
|
|
1792
|
+
// 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.
|
|
1793
|
+
const removed = [];
|
|
1794
|
+
const failed = [];
|
|
1748
1795
|
for (const path of paths) {
|
|
1749
|
-
|
|
1796
|
+
try {
|
|
1750
1797
|
await unlink(path);
|
|
1751
|
-
|
|
1798
|
+
removed.push(path);
|
|
1752
1799
|
}
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
};
|
|
1800
|
+
catch (error) {
|
|
1801
|
+
// A session another tab already deleted leaves nothing to unlink, but its metadata entry still has to go.
|
|
1802
|
+
if (error.code === "ENOENT")
|
|
1803
|
+
removed.push(path);
|
|
1804
|
+
else
|
|
1805
|
+
failed.push({ path, error: errorText(error) });
|
|
1760
1806
|
}
|
|
1761
1807
|
}
|
|
1808
|
+
await mutateSessionMetadata(manager, context.logger, (metadata) => {
|
|
1809
|
+
for (const path of removed)
|
|
1810
|
+
delete metadata[path];
|
|
1811
|
+
});
|
|
1812
|
+
if (failed.length > 0) {
|
|
1813
|
+
sendJson(response, 409, {
|
|
1814
|
+
error: `Some sessions could not be deleted: ${failed.map((item) => item.path).join(", ")}`,
|
|
1815
|
+
action,
|
|
1816
|
+
count: removed.length,
|
|
1817
|
+
failed,
|
|
1818
|
+
});
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
sendJson(response, 200, { action, count: removed.length });
|
|
1822
|
+
return;
|
|
1823
|
+
}
|
|
1824
|
+
await mutateSessionMetadata(manager, context.logger, (metadata) => {
|
|
1825
|
+
for (const path of paths) {
|
|
1826
|
+
const current = metadata[path] ?? {};
|
|
1827
|
+
metadata[path] = {
|
|
1828
|
+
...current,
|
|
1829
|
+
...(action === "archive" || action === "unarchive" ? { archived: action === "archive" } : {}),
|
|
1830
|
+
...(action === "pin" || action === "unpin" ? { pinned: action === "pin" } : {}),
|
|
1831
|
+
};
|
|
1832
|
+
}
|
|
1762
1833
|
});
|
|
1763
1834
|
sendJson(response, 200, { action, count: paths.length });
|
|
1764
1835
|
}
|