gsd-pi 2.54.0 → 2.55.0-dev.9ec7cdf
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/dist/cli.js +19 -19
- package/dist/headless-ui.d.ts +27 -1
- package/dist/headless-ui.js +203 -13
- package/dist/headless.js +60 -3
- package/dist/resources/extensions/bg-shell/bg-shell-lifecycle.js +2 -2
- package/dist/resources/extensions/bg-shell/utilities.js +34 -5
- package/dist/resources/extensions/gsd/auto/phases.js +19 -3
- package/dist/resources/extensions/gsd/auto-dispatch.js +1 -1
- package/dist/resources/extensions/gsd/auto-model-selection.js +17 -1
- package/dist/resources/extensions/gsd/auto-prompts.js +9 -0
- package/dist/resources/extensions/gsd/auto-start.js +12 -5
- package/dist/resources/extensions/gsd/auto-worktree.js +39 -14
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +5 -1
- package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +18 -0
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +18 -5
- package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +20 -0
- package/dist/resources/extensions/gsd/commands/catalog.js +2 -1
- package/dist/resources/extensions/gsd/commands/handlers/parallel.js +15 -1
- package/dist/resources/extensions/gsd/crash-recovery.js +2 -2
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +413 -0
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +5 -1
- package/dist/resources/extensions/gsd/session-lock.js +46 -12
- package/dist/resources/extensions/gsd/skill-health.js +2 -2
- package/dist/resources/extensions/gsd/visualizer-overlay.js +3 -3
- package/dist/resources/extensions/shared/format-utils.js +1 -1
- package/dist/resources/extensions/subagent/worker-registry.js +2 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
- package/dist/web/standalone/.next/build-manifest.json +4 -4
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +2 -2
- package/dist/web/standalone/.next/required-server-files.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
- package/dist/web/standalone/.next/server/chunks/2229.js +2 -2
- package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware.js +2 -2
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/6502.2305d0afd2385711.js +9 -0
- package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/page-0c485498795110d6.js +1 -0
- package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-bca0e732db0dcec3.js → webpack-4332cbd5dd1be584.js} +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
- package/dist/web/standalone/server.js +1 -1
- package/package.json +6 -4
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +14 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/model-registry.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +16 -2
- package/pkg/package.json +1 -1
- package/scripts/ensure-workspace-builds.cjs +45 -41
- package/src/resources/extensions/bg-shell/bg-shell-lifecycle.ts +2 -2
- package/src/resources/extensions/bg-shell/utilities.ts +39 -4
- package/src/resources/extensions/gsd/auto/phases.ts +25 -4
- package/src/resources/extensions/gsd/auto-dispatch.ts +1 -1
- package/src/resources/extensions/gsd/auto-model-selection.ts +21 -1
- package/src/resources/extensions/gsd/auto-prompts.ts +15 -0
- package/src/resources/extensions/gsd/auto-start.ts +13 -5
- package/src/resources/extensions/gsd/auto-worktree.ts +46 -13
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +5 -4
- package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +53 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +19 -6
- package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +24 -0
- package/src/resources/extensions/gsd/commands/catalog.ts +2 -1
- package/src/resources/extensions/gsd/commands/handlers/parallel.ts +19 -1
- package/src/resources/extensions/gsd/crash-recovery.ts +2 -3
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +497 -0
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +6 -1
- package/src/resources/extensions/gsd/session-lock.ts +46 -12
- package/src/resources/extensions/gsd/skill-health.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +28 -0
- package/src/resources/extensions/gsd/tests/{all-milestones-complete-merge.test.ts → integration/all-milestones-complete-merge.test.ts} +3 -3
- package/src/resources/extensions/gsd/tests/{atomic-task-closeout.test.ts → integration/atomic-task-closeout.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{auto-preflight.test.ts → integration/auto-preflight.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{auto-recovery.test.ts → integration/auto-recovery.test.ts} +7 -7
- package/src/resources/extensions/gsd/tests/{auto-secrets-gate.test.ts → integration/auto-secrets-gate.test.ts} +2 -2
- package/src/resources/extensions/gsd/tests/{auto-stash-merge.test.ts → integration/auto-stash-merge.test.ts} +3 -3
- package/src/resources/extensions/gsd/tests/{auto-worktree-milestone-merge.test.ts → integration/auto-worktree-milestone-merge.test.ts} +4 -4
- package/src/resources/extensions/gsd/tests/{auto-worktree.test.ts → integration/auto-worktree.test.ts} +5 -5
- package/src/resources/extensions/gsd/tests/{continue-here.test.ts → integration/continue-here.test.ts} +3 -3
- package/src/resources/extensions/gsd/tests/{doctor-completion-deferral.test.ts → integration/doctor-completion-deferral.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{doctor-delimiter-fix.test.ts → integration/doctor-delimiter-fix.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{doctor-enhancements.test.ts → integration/doctor-enhancements.test.ts} +3 -3
- package/src/resources/extensions/gsd/tests/{doctor-environment-worktree.test.ts → integration/doctor-environment-worktree.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{doctor-environment.test.ts → integration/doctor-environment.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{doctor-fixlevel.test.ts → integration/doctor-fixlevel.test.ts} +2 -2
- package/src/resources/extensions/gsd/tests/{doctor-git.test.ts → integration/doctor-git.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{doctor-proactive.test.ts → integration/doctor-proactive.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{doctor-roadmap-summary-atomicity.test.ts → integration/doctor-roadmap-summary-atomicity.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{doctor-runtime.test.ts → integration/doctor-runtime.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{doctor.test.ts → integration/doctor.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{e2e-workflow-pipeline-integration.test.ts → integration/e2e-workflow-pipeline-integration.test.ts} +5 -5
- package/src/resources/extensions/gsd/tests/{feature-branch-lifecycle-integration.test.ts → integration/feature-branch-lifecycle-integration.test.ts} +4 -4
- package/src/resources/extensions/gsd/tests/{git-locale.test.ts → integration/git-locale.test.ts} +4 -4
- package/src/resources/extensions/gsd/tests/{git-self-heal.test.ts → integration/git-self-heal.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{git-service.test.ts → integration/git-service.test.ts} +4 -4
- package/src/resources/extensions/gsd/tests/{gitignore-tracked-gsd.test.ts → integration/gitignore-tracked-gsd.test.ts} +2 -2
- package/src/resources/extensions/gsd/tests/{idle-recovery.test.ts → integration/idle-recovery.test.ts} +3 -3
- package/src/resources/extensions/gsd/tests/{inherited-repo-home-dir.test.ts → integration/inherited-repo-home-dir.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{integration-lifecycle.test.ts → integration/integration-lifecycle.test.ts} +4 -4
- package/src/resources/extensions/gsd/tests/{integration-mixed-milestones.test.ts → integration/integration-mixed-milestones.test.ts} +6 -6
- package/src/resources/extensions/gsd/tests/{integration-proof.test.ts → integration/integration-proof.test.ts} +12 -12
- package/src/resources/extensions/gsd/tests/{migrate-command.test.ts → integration/migrate-command.test.ts} +2 -2
- package/src/resources/extensions/gsd/tests/{milestone-transition-worktree.test.ts → integration/milestone-transition-worktree.test.ts} +3 -3
- package/src/resources/extensions/gsd/tests/{parallel-merge.test.ts → integration/parallel-merge.test.ts} +3 -3
- package/src/resources/extensions/gsd/tests/{parallel-workers-multi-milestone-e2e.test.ts → integration/parallel-workers-multi-milestone-e2e.test.ts} +3 -3
- package/src/resources/extensions/gsd/tests/{paths.test.ts → integration/paths.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{plugin-importer-live.test.ts → integration/plugin-importer-live.test.ts} +2 -2
- package/src/resources/extensions/gsd/tests/{queue-completed-milestone-perf.test.ts → integration/queue-completed-milestone-perf.test.ts} +3 -3
- package/src/resources/extensions/gsd/tests/{queue-reorder-e2e.test.ts → integration/queue-reorder-e2e.test.ts} +5 -5
- package/src/resources/extensions/gsd/tests/{quick-branch-lifecycle.test.ts → integration/quick-branch-lifecycle.test.ts} +5 -5
- package/src/resources/extensions/gsd/tests/{run-uat.test.ts → integration/run-uat.test.ts} +4 -4
- package/src/resources/extensions/gsd/tests/{token-savings.test.ts → integration/token-savings.test.ts} +3 -3
- package/src/resources/extensions/gsd/tests/{worktree-e2e.test.ts → integration/worktree-e2e.test.ts} +4 -4
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +60 -0
- package/src/resources/extensions/gsd/tests/parallel-worker-lock-contention.test.ts +226 -0
- package/src/resources/extensions/gsd/tests/plan-milestone-queue-context.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +61 -19
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/register-extension-guard.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +49 -24
- package/src/resources/extensions/gsd/visualizer-overlay.ts +3 -3
- package/src/resources/extensions/shared/format-utils.ts +1 -1
- package/src/resources/extensions/subagent/worker-registry.ts +2 -1
- package/dist/web/standalone/.next/static/chunks/4024.87fd909ae0110f50.js +0 -9
- package/dist/web/standalone/.next/static/chunks/app/page-fbecd1237e2d6d1f.js +0 -1
- package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
- /package/dist/web/standalone/.next/static/{KixbEdSRlU9zzYdZdrJ7A → k92jvAf8IfV4dZE3nnrAr}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{KixbEdSRlU9zzYdZdrJ7A → k92jvAf8IfV4dZE3nnrAr}/_ssgManifest.js +0 -0
|
@@ -22,6 +22,8 @@ import { logWarning } from "./workflow-logger.js";
|
|
|
22
22
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
23
23
|
import { nativeGetCurrentBranch, nativeDetectMainBranch, nativeWorkingTreeStatus, nativeAddAllWithExclusions, nativeCommit, nativeCheckoutBranch, nativeMergeSquash, nativeConflictFiles, nativeCheckoutTheirs, nativeAddPaths, nativeRmForce, nativeBranchDelete, nativeBranchExists, nativeDiffNumstat, nativeUpdateRef, nativeIsAncestor, } from "./native-git-bridge.js";
|
|
24
24
|
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
25
|
+
const PROJECT_PREFERENCES_FILE = "PREFERENCES.md";
|
|
26
|
+
const LEGACY_PROJECT_PREFERENCES_FILE = "preferences.md";
|
|
25
27
|
// ─── Shared Constants & Helpers ─────────────────────────────────────────────
|
|
26
28
|
/**
|
|
27
29
|
* Root-level .gsd/ state files synced between worktree and project root.
|
|
@@ -37,7 +39,7 @@ const ROOT_STATE_FILES = [
|
|
|
37
39
|
"QUEUE.md",
|
|
38
40
|
"completed-units.json",
|
|
39
41
|
"metrics.json",
|
|
40
|
-
// NOTE: preferences
|
|
42
|
+
// NOTE: project preferences are intentionally NOT in ROOT_STATE_FILES.
|
|
41
43
|
// Forward-sync (main → worktree) is handled explicitly in syncGsdStateToWorktree().
|
|
42
44
|
// Back-sync (worktree → main) must NEVER overwrite the project root's copy
|
|
43
45
|
// because the project root is authoritative for preferences (#2684).
|
|
@@ -134,6 +136,11 @@ export function syncProjectRootToWorktree(projectRoot, worktreePath_, milestoneI
|
|
|
134
136
|
return;
|
|
135
137
|
const prGsd = join(projectRoot, ".gsd");
|
|
136
138
|
const wtGsd = join(worktreePath_, ".gsd");
|
|
139
|
+
// When .gsd is a symlink to the same external directory in both locations,
|
|
140
|
+
// cpSync rejects the copy because source === destination (ERR_FS_CP_EINVAL).
|
|
141
|
+
// Compare realpaths and skip when they resolve to the same physical path (#2184).
|
|
142
|
+
if (isSamePath(prGsd, wtGsd))
|
|
143
|
+
return;
|
|
137
144
|
// Copy milestone directory from project root to worktree — additive only.
|
|
138
145
|
// force:false prevents cpSync from overwriting existing worktree files.
|
|
139
146
|
// Without this, worktree-authoritative files (e.g. VALIDATION.md written
|
|
@@ -169,6 +176,11 @@ export function syncStateToProjectRoot(worktreePath_, projectRoot, milestoneId)
|
|
|
169
176
|
return;
|
|
170
177
|
const wtGsd = join(worktreePath_, ".gsd");
|
|
171
178
|
const prGsd = join(projectRoot, ".gsd");
|
|
179
|
+
// When .gsd is a symlink to the same external directory in both locations,
|
|
180
|
+
// cpSync rejects the copy because source === destination (ERR_FS_CP_EINVAL).
|
|
181
|
+
// Compare realpaths and skip when they resolve to the same physical path (#2184).
|
|
182
|
+
if (isSamePath(wtGsd, prGsd))
|
|
183
|
+
return;
|
|
172
184
|
// 1. STATE.md — the quick-glance status used by initial deriveState()
|
|
173
185
|
safeCopy(join(wtGsd, "STATE.md"), join(prGsd, "STATE.md"), { force: true });
|
|
174
186
|
// 2. Milestone directory — ROADMAP, slice PLANs, task summaries
|
|
@@ -338,19 +350,26 @@ export function syncGsdStateToWorktree(mainBasePath, worktreePath_) {
|
|
|
338
350
|
}
|
|
339
351
|
}
|
|
340
352
|
}
|
|
341
|
-
// Forward-sync preferences
|
|
342
|
-
//
|
|
343
|
-
//
|
|
353
|
+
// Forward-sync project preferences from project root to worktree (additive only).
|
|
354
|
+
// Prefer the canonical uppercase file name, but keep the legacy lowercase
|
|
355
|
+
// fallback so older repos still work on case-sensitive filesystems.
|
|
344
356
|
{
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
if (
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
357
|
+
const worktreeHasPreferences = existsSync(join(wtGsd, PROJECT_PREFERENCES_FILE))
|
|
358
|
+
|| existsSync(join(wtGsd, LEGACY_PROJECT_PREFERENCES_FILE));
|
|
359
|
+
if (!worktreeHasPreferences) {
|
|
360
|
+
for (const file of [PROJECT_PREFERENCES_FILE, LEGACY_PROJECT_PREFERENCES_FILE]) {
|
|
361
|
+
const src = join(mainGsd, file);
|
|
362
|
+
const dst = join(wtGsd, file);
|
|
363
|
+
if (existsSync(src)) {
|
|
364
|
+
try {
|
|
365
|
+
cpSync(src, dst);
|
|
366
|
+
synced.push(file);
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
/* non-fatal */
|
|
370
|
+
}
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
354
373
|
}
|
|
355
374
|
}
|
|
356
375
|
}
|
|
@@ -836,10 +855,16 @@ function copyPlanningArtifacts(srcBase, wtPath) {
|
|
|
836
855
|
"STATE.md",
|
|
837
856
|
"KNOWLEDGE.md",
|
|
838
857
|
"OVERRIDES.md",
|
|
839
|
-
"preferences.md",
|
|
840
858
|
]) {
|
|
841
859
|
safeCopy(join(srcGsd, file), join(dstGsd, file), { force: true });
|
|
842
860
|
}
|
|
861
|
+
// Seed canonical PREFERENCES.md when available; fall back to legacy lowercase.
|
|
862
|
+
if (existsSync(join(srcGsd, PROJECT_PREFERENCES_FILE))) {
|
|
863
|
+
safeCopy(join(srcGsd, PROJECT_PREFERENCES_FILE), join(dstGsd, PROJECT_PREFERENCES_FILE), { force: true });
|
|
864
|
+
}
|
|
865
|
+
else if (existsSync(join(srcGsd, LEGACY_PROJECT_PREFERENCES_FILE))) {
|
|
866
|
+
safeCopy(join(srcGsd, LEGACY_PROJECT_PREFERENCES_FILE), join(dstGsd, LEGACY_PROJECT_PREFERENCES_FILE), { force: true });
|
|
867
|
+
}
|
|
843
868
|
// Shared WAL (R012): worktrees use the project root's DB directly.
|
|
844
869
|
// No longer copy gsd.db into the worktree — the DB path resolver in
|
|
845
870
|
// ensureDbOpen() detects the worktree location and opens the root DB.
|
|
@@ -5,6 +5,7 @@ import { pauseAutoForProviderError } from "../provider-error-pause.js";
|
|
|
5
5
|
import { isSessionSwitchInFlight, resolveAgentEnd } from "../auto-loop.js";
|
|
6
6
|
import { resolveModelId } from "../auto-model-selection.js";
|
|
7
7
|
import { clearDiscussionFlowState } from "./write-gate.js";
|
|
8
|
+
import { resumeAutoAfterProviderDelay } from "./provider-error-resume.js";
|
|
8
9
|
import { classifyError, createRetryState, resetRetryState, isTransient, } from "../error-classifier.js";
|
|
9
10
|
const retryState = createRetryState();
|
|
10
11
|
const MAX_NETWORK_RETRIES = 2;
|
|
@@ -28,7 +29,10 @@ async function pauseTransientWithBackoff(cls, pi, ctx, errorDetail, isRateLimit)
|
|
|
28
29
|
retryAfterMs,
|
|
29
30
|
resume: allowAutoResume
|
|
30
31
|
? () => {
|
|
31
|
-
pi
|
|
32
|
+
void resumeAutoAfterProviderDelay(pi, ctx).catch((err) => {
|
|
33
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
34
|
+
ctx.ui.notify(`Provider error recovery delay elapsed, but auto-mode failed to resume: ${message}`, "error");
|
|
35
|
+
});
|
|
32
36
|
}
|
|
33
37
|
: undefined,
|
|
34
38
|
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getAutoDashboardData, startAuto } from "../auto.js";
|
|
2
|
+
const defaultDeps = {
|
|
3
|
+
getSnapshot: () => getAutoDashboardData(),
|
|
4
|
+
startAuto,
|
|
5
|
+
};
|
|
6
|
+
export async function resumeAutoAfterProviderDelay(pi, ctx, deps = defaultDeps) {
|
|
7
|
+
const snapshot = deps.getSnapshot();
|
|
8
|
+
if (snapshot.active)
|
|
9
|
+
return "already-active";
|
|
10
|
+
if (!snapshot.paused)
|
|
11
|
+
return "not-paused";
|
|
12
|
+
if (!snapshot.basePath) {
|
|
13
|
+
ctx.ui.notify("Provider error recovery delay elapsed, but no paused auto-mode base path was available. Leaving auto-mode paused.", "warning");
|
|
14
|
+
return "missing-base";
|
|
15
|
+
}
|
|
16
|
+
await deps.startAuto(ctx, pi, snapshot.basePath, false, { step: snapshot.stepMode });
|
|
17
|
+
return "resumed";
|
|
18
|
+
}
|
|
@@ -6,14 +6,27 @@ import { registerDynamicTools } from "./dynamic-tools.js";
|
|
|
6
6
|
import { registerJournalTools } from "./journal-tools.js";
|
|
7
7
|
import { registerHooks } from "./register-hooks.js";
|
|
8
8
|
import { registerShortcuts } from "./register-shortcuts.js";
|
|
9
|
+
export function handleRecoverableExtensionProcessError(err) {
|
|
10
|
+
if (err.code === "EPIPE") {
|
|
11
|
+
process.exit(0);
|
|
12
|
+
}
|
|
13
|
+
if (err.code === "ENOENT") {
|
|
14
|
+
const syscall = err.syscall;
|
|
15
|
+
if (syscall?.startsWith("spawn")) {
|
|
16
|
+
process.stderr.write(`[gsd] spawn ENOENT: ${err.path ?? "unknown"} — command not found\n`);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (syscall === "uv_cwd") {
|
|
20
|
+
process.stderr.write(`[gsd] ENOENT (${syscall}): ${err.message}\n`);
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
9
26
|
function installEpipeGuard() {
|
|
10
27
|
if (!process.listeners("uncaughtException").some((listener) => listener.name === "_gsdEpipeGuard")) {
|
|
11
28
|
const _gsdEpipeGuard = (err) => {
|
|
12
|
-
if (err
|
|
13
|
-
process.exit(0);
|
|
14
|
-
}
|
|
15
|
-
if (err.code === "ENOENT" && err.syscall?.startsWith("spawn")) {
|
|
16
|
-
process.stderr.write(`[gsd] spawn ENOENT: ${err.path ?? "unknown"} — command not found\n`);
|
|
29
|
+
if (handleRecoverableExtensionProcessError(err)) {
|
|
17
30
|
return;
|
|
18
31
|
}
|
|
19
32
|
throw err;
|
|
@@ -2,6 +2,7 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { Key } from "@gsd/pi-tui";
|
|
4
4
|
import { GSDDashboardOverlay } from "../dashboard-overlay.js";
|
|
5
|
+
import { ParallelMonitorOverlay } from "../parallel-monitor-overlay.js";
|
|
5
6
|
import { shortcutDesc } from "../../shared/mod.js";
|
|
6
7
|
export function registerShortcuts(pi) {
|
|
7
8
|
pi.registerShortcut(Key.ctrlAlt("g"), {
|
|
@@ -22,4 +23,23 @@ export function registerShortcuts(pi) {
|
|
|
22
23
|
});
|
|
23
24
|
},
|
|
24
25
|
});
|
|
26
|
+
pi.registerShortcut(Key.ctrlAlt("p"), {
|
|
27
|
+
description: shortcutDesc("Open parallel worker monitor", "/gsd parallel watch"),
|
|
28
|
+
handler: async (ctx) => {
|
|
29
|
+
const parallelDir = join(process.cwd(), ".gsd", "parallel");
|
|
30
|
+
if (!existsSync(parallelDir)) {
|
|
31
|
+
ctx.ui.notify("No parallel workers found. Run /gsd parallel start first.", "info");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
await ctx.ui.custom((tui, theme, _kb, done) => new ParallelMonitorOverlay(tui, theme, () => done()), {
|
|
35
|
+
overlay: true,
|
|
36
|
+
overlayOptions: {
|
|
37
|
+
width: "90%",
|
|
38
|
+
minWidth: 80,
|
|
39
|
+
maxHeight: "92%",
|
|
40
|
+
anchor: "center",
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
});
|
|
25
45
|
}
|
|
@@ -47,7 +47,7 @@ export const TOP_LEVEL_SUBCOMMANDS = [
|
|
|
47
47
|
{ cmd: "inspect", desc: "Show SQLite DB diagnostics" },
|
|
48
48
|
{ cmd: "knowledge", desc: "Add persistent project knowledge (rule, pattern, or lesson)" },
|
|
49
49
|
{ cmd: "new-milestone", desc: "Create a milestone from a specification document (headless)" },
|
|
50
|
-
{ cmd: "parallel", desc: "Parallel milestone orchestration (start, status, stop, merge)" },
|
|
50
|
+
{ cmd: "parallel", desc: "Parallel milestone orchestration (start, status, stop, merge, watch)" },
|
|
51
51
|
{ cmd: "cmux", desc: "Manage cmux integration (status, sidebar, notifications, splits)" },
|
|
52
52
|
{ cmd: "park", desc: "Park a milestone — skip without deleting" },
|
|
53
53
|
{ cmd: "unpark", desc: "Reactivate a parked milestone" },
|
|
@@ -87,6 +87,7 @@ const NESTED_COMPLETIONS = {
|
|
|
87
87
|
{ cmd: "pause", desc: "Pause a specific worker" },
|
|
88
88
|
{ cmd: "resume", desc: "Resume a paused worker" },
|
|
89
89
|
{ cmd: "merge", desc: "Merge completed milestone branches" },
|
|
90
|
+
{ cmd: "watch", desc: "Live TUI dashboard monitoring all workers" },
|
|
90
91
|
],
|
|
91
92
|
setup: [
|
|
92
93
|
{ cmd: "llm", desc: "Configure LLM provider settings" },
|
|
@@ -87,6 +87,20 @@ export async function handleParallelCommand(trimmed, _ctx, pi) {
|
|
|
87
87
|
emitParallelMessage(pi, formatMergeResults(results));
|
|
88
88
|
return true;
|
|
89
89
|
}
|
|
90
|
-
|
|
90
|
+
if (subcommand === "watch") {
|
|
91
|
+
const root = projectRoot();
|
|
92
|
+
const { ParallelMonitorOverlay } = await import("../../parallel-monitor-overlay.js");
|
|
93
|
+
await _ctx.ui.custom((tui, theme, _kb, done) => new ParallelMonitorOverlay(tui, theme, () => done(), root), {
|
|
94
|
+
overlay: true,
|
|
95
|
+
overlayOptions: {
|
|
96
|
+
width: "90%",
|
|
97
|
+
minWidth: 80,
|
|
98
|
+
maxHeight: "92%",
|
|
99
|
+
anchor: "center",
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
emitParallelMessage(pi, `Unknown parallel subcommand "${subcommand}". Usage: /gsd parallel [start|status|stop|pause|resume|merge|watch]`);
|
|
91
105
|
return true;
|
|
92
106
|
}
|
|
@@ -13,9 +13,9 @@ import { readFileSync, unlinkSync, existsSync } from "node:fs";
|
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
import { gsdRoot } from "./paths.js";
|
|
15
15
|
import { atomicWriteSync } from "./atomic-write.js";
|
|
16
|
-
|
|
16
|
+
import { effectiveLockFile } from "./session-lock.js";
|
|
17
17
|
function lockPath(basePath) {
|
|
18
|
-
return join(gsdRoot(basePath),
|
|
18
|
+
return join(gsdRoot(basePath), effectiveLockFile());
|
|
19
19
|
}
|
|
20
20
|
/** Write or update the lock file with current auto-mode state. */
|
|
21
21
|
export function writeLock(basePath, unitType, unitId, sessionFile) {
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Parallel Monitor Overlay
|
|
3
|
+
*
|
|
4
|
+
* Full-screen TUI overlay showing real-time parallel worker progress.
|
|
5
|
+
* Opened via `/gsd parallel watch` or Ctrl+Alt+P.
|
|
6
|
+
* Reads the same data sources as `scripts/parallel-monitor.mjs` but
|
|
7
|
+
* renders as a native pi-tui overlay with theme integration.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, statSync, readFileSync, openSync, readSync, closeSync, readdirSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { spawnSync } from "node:child_process";
|
|
12
|
+
import { matchesKey, Key } from "@gsd/pi-tui";
|
|
13
|
+
import { formatDuration } from "../shared/mod.js";
|
|
14
|
+
// ─── Data Helpers ─────────────────────────────────────────────────────────
|
|
15
|
+
function readJsonSafe(filePath) {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(readFileSync(filePath, "utf-8"));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function isPidAlive(pid) {
|
|
24
|
+
try {
|
|
25
|
+
process.kill(pid, 0);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function tailRead(filePath, maxBytes) {
|
|
33
|
+
try {
|
|
34
|
+
const stat = statSync(filePath);
|
|
35
|
+
const readSize = Math.min(stat.size, maxBytes);
|
|
36
|
+
const fd = openSync(filePath, "r");
|
|
37
|
+
const buf = Buffer.alloc(readSize);
|
|
38
|
+
readSync(fd, buf, 0, readSize, Math.max(0, stat.size - readSize));
|
|
39
|
+
closeSync(fd);
|
|
40
|
+
return buf.toString("utf-8");
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return "";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function discoverWorkers(basePath) {
|
|
47
|
+
const parallelDir = join(basePath, ".gsd", "parallel");
|
|
48
|
+
const worktreeDir = join(basePath, ".gsd", "worktrees");
|
|
49
|
+
const mids = new Set();
|
|
50
|
+
if (existsSync(parallelDir)) {
|
|
51
|
+
try {
|
|
52
|
+
for (const f of readdirSync(parallelDir)) {
|
|
53
|
+
if (f.endsWith(".status.json"))
|
|
54
|
+
mids.add(f.replace(".status.json", ""));
|
|
55
|
+
const m = f.match(/^(M\d+)\.(stderr|stdout)\.log$/);
|
|
56
|
+
if (m)
|
|
57
|
+
mids.add(m[1]);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch { /* skip */ }
|
|
61
|
+
}
|
|
62
|
+
if (existsSync(worktreeDir)) {
|
|
63
|
+
try {
|
|
64
|
+
for (const d of readdirSync(worktreeDir)) {
|
|
65
|
+
if (d.startsWith("M") && existsSync(join(worktreeDir, d, ".gsd", "auto.lock"))) {
|
|
66
|
+
mids.add(d);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch { /* skip */ }
|
|
71
|
+
}
|
|
72
|
+
return [...mids].sort();
|
|
73
|
+
}
|
|
74
|
+
function querySliceProgress(basePath, mid) {
|
|
75
|
+
const dbPath = join(basePath, ".gsd", "worktrees", mid, ".gsd", "gsd.db");
|
|
76
|
+
if (!existsSync(dbPath))
|
|
77
|
+
return [];
|
|
78
|
+
try {
|
|
79
|
+
const sql = `SELECT s.id, s.status, COUNT(t.id), SUM(CASE WHEN t.status='complete' THEN 1 ELSE 0 END) FROM slices s LEFT JOIN tasks t ON s.milestone_id=t.milestone_id AND s.id=t.slice_id WHERE s.milestone_id='${mid}' GROUP BY s.id ORDER BY s.id`;
|
|
80
|
+
const result = spawnSync("sqlite3", [dbPath, sql], { timeout: 3000, encoding: "utf-8" });
|
|
81
|
+
const out = (result.stdout || "").trim();
|
|
82
|
+
if (!out || result.status !== 0)
|
|
83
|
+
return [];
|
|
84
|
+
return out.split("\n").map((line) => {
|
|
85
|
+
const [id, status, total, done] = line.split("|");
|
|
86
|
+
return { id, status, total: parseInt(total, 10), done: parseInt(done || "0", 10) };
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function extractCostFromNdjson(basePath, mid) {
|
|
94
|
+
const stdoutPath = join(basePath, ".gsd", "parallel", `${mid}.stdout.log`);
|
|
95
|
+
if (!existsSync(stdoutPath))
|
|
96
|
+
return 0;
|
|
97
|
+
try {
|
|
98
|
+
const content = readFileSync(stdoutPath, "utf-8");
|
|
99
|
+
let total = 0;
|
|
100
|
+
for (const line of content.split("\n")) {
|
|
101
|
+
if (!line.includes("message_end"))
|
|
102
|
+
continue;
|
|
103
|
+
try {
|
|
104
|
+
const obj = JSON.parse(line);
|
|
105
|
+
if (obj.type === "message_end") {
|
|
106
|
+
const cost = obj.message?.usage?.cost?.total;
|
|
107
|
+
if (typeof cost === "number")
|
|
108
|
+
total += cost;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch { /* skip */ }
|
|
112
|
+
}
|
|
113
|
+
return total;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function queryRecentCompletions(basePath, mid) {
|
|
120
|
+
const dbPath = join(basePath, ".gsd", "worktrees", mid, ".gsd", "gsd.db");
|
|
121
|
+
if (!existsSync(dbPath))
|
|
122
|
+
return [];
|
|
123
|
+
try {
|
|
124
|
+
const sql = `SELECT id, slice_id, one_liner FROM tasks WHERE milestone_id='${mid}' AND status='complete' AND completed_at IS NOT NULL ORDER BY completed_at DESC LIMIT 5`;
|
|
125
|
+
const result = spawnSync("sqlite3", [dbPath, sql], { timeout: 3000, encoding: "utf-8" });
|
|
126
|
+
const out = (result.stdout || "").trim();
|
|
127
|
+
if (!out || result.status !== 0)
|
|
128
|
+
return [];
|
|
129
|
+
return out.split("\n").map((line) => {
|
|
130
|
+
const [taskId, sliceId, oneLiner] = line.split("|");
|
|
131
|
+
return `✓ ${mid}/${sliceId}/${taskId}${oneLiner ? ": " + oneLiner : ""}`;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function collectWorkerData(basePath) {
|
|
139
|
+
const mids = discoverWorkers(basePath);
|
|
140
|
+
const parallelDir = join(basePath, ".gsd", "parallel");
|
|
141
|
+
const workers = [];
|
|
142
|
+
for (const mid of mids) {
|
|
143
|
+
const status = readJsonSafe(join(parallelDir, `${mid}.status.json`));
|
|
144
|
+
const lock = readJsonSafe(join(basePath, ".gsd", "worktrees", mid, ".gsd", "auto.lock"));
|
|
145
|
+
const slices = querySliceProgress(basePath, mid);
|
|
146
|
+
const pid = lock?.pid || status?.pid || 0;
|
|
147
|
+
const alive = pid ? isPidAlive(pid) : false;
|
|
148
|
+
// Heartbeat: prefer status.json if PID matches, else use file mtime
|
|
149
|
+
let heartbeatAge = Infinity;
|
|
150
|
+
const statusPidMatches = status?.pid === pid && status?.lastHeartbeat;
|
|
151
|
+
if (statusPidMatches) {
|
|
152
|
+
heartbeatAge = Date.now() - status.lastHeartbeat;
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
const mtimes = [];
|
|
156
|
+
const stdoutLog = join(parallelDir, `${mid}.stdout.log`);
|
|
157
|
+
const stderrLog = join(parallelDir, `${mid}.stderr.log`);
|
|
158
|
+
if (existsSync(stdoutLog))
|
|
159
|
+
mtimes.push(statSync(stdoutLog).mtimeMs);
|
|
160
|
+
if (existsSync(stderrLog))
|
|
161
|
+
mtimes.push(statSync(stderrLog).mtimeMs);
|
|
162
|
+
if (lock?.unitStartedAt)
|
|
163
|
+
mtimes.push(new Date(lock.unitStartedAt).getTime());
|
|
164
|
+
if (mtimes.length > 0)
|
|
165
|
+
heartbeatAge = Date.now() - Math.max(...mtimes);
|
|
166
|
+
}
|
|
167
|
+
let cost = status?.cost || 0;
|
|
168
|
+
if (cost === 0)
|
|
169
|
+
cost = extractCostFromNdjson(basePath, mid);
|
|
170
|
+
const totalTasks = slices.reduce((sum, s) => sum + s.total, 0);
|
|
171
|
+
const doneTasks = slices.reduce((sum, s) => sum + s.done, 0);
|
|
172
|
+
const doneSlices = slices.filter((s) => s.status === "complete").length;
|
|
173
|
+
const elapsed = status?.startedAt
|
|
174
|
+
? Date.now() - status.startedAt
|
|
175
|
+
: lock?.startedAt
|
|
176
|
+
? Date.now() - new Date(lock.startedAt).getTime()
|
|
177
|
+
: 0;
|
|
178
|
+
// Errors from stderr (last 4KB, only new content)
|
|
179
|
+
const errors = [];
|
|
180
|
+
const stderrLog = join(parallelDir, `${mid}.stderr.log`);
|
|
181
|
+
if (existsSync(stderrLog)) {
|
|
182
|
+
const content = tailRead(stderrLog, 4096);
|
|
183
|
+
for (const line of content.trim().split("\n").slice(-5)) {
|
|
184
|
+
if (line.includes("error") || line.includes("Error") || line.includes("exited")) {
|
|
185
|
+
errors.push(line.trim());
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
workers.push({
|
|
190
|
+
mid,
|
|
191
|
+
pid,
|
|
192
|
+
alive,
|
|
193
|
+
state: alive ? "running" : (status?.state || "dead"),
|
|
194
|
+
cost,
|
|
195
|
+
heartbeatAge,
|
|
196
|
+
currentUnit: lock?.unitId || null,
|
|
197
|
+
unitType: lock?.unitType || null,
|
|
198
|
+
unitElapsed: lock?.unitStartedAt ? Date.now() - new Date(lock.unitStartedAt).getTime() : 0,
|
|
199
|
+
elapsed,
|
|
200
|
+
totalTasks,
|
|
201
|
+
doneTasks,
|
|
202
|
+
totalSlices: slices.length,
|
|
203
|
+
doneSlices,
|
|
204
|
+
slices,
|
|
205
|
+
errors,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return workers;
|
|
209
|
+
}
|
|
210
|
+
// ─── Rendering Helpers ────────────────────────────────────────────────────
|
|
211
|
+
function unitTypeLabel(unitType) {
|
|
212
|
+
const labels = {
|
|
213
|
+
"execute-task": "EXEC",
|
|
214
|
+
"research-slice": "RSRCH",
|
|
215
|
+
"plan-slice": "PLAN",
|
|
216
|
+
"complete-slice": "DONE",
|
|
217
|
+
"complete-task": "DONE",
|
|
218
|
+
"reassess": "ASSESS",
|
|
219
|
+
"validate": "VALID",
|
|
220
|
+
"reassess-roadmap": "ASSESS",
|
|
221
|
+
};
|
|
222
|
+
return labels[unitType || ""] || (unitType || "---").toUpperCase().slice(0, 5);
|
|
223
|
+
}
|
|
224
|
+
function progressBar(done, total, width) {
|
|
225
|
+
if (total === 0)
|
|
226
|
+
return "░".repeat(width);
|
|
227
|
+
const filled = Math.round((done / total) * width);
|
|
228
|
+
return "█".repeat(filled) + "░".repeat(width - filled);
|
|
229
|
+
}
|
|
230
|
+
function healthGlyph(alive, heartbeatAge) {
|
|
231
|
+
if (!alive)
|
|
232
|
+
return "○";
|
|
233
|
+
return "●";
|
|
234
|
+
}
|
|
235
|
+
// ─── Overlay Class ────────────────────────────────────────────────────────
|
|
236
|
+
export class ParallelMonitorOverlay {
|
|
237
|
+
tui;
|
|
238
|
+
theme;
|
|
239
|
+
onClose;
|
|
240
|
+
basePath;
|
|
241
|
+
refreshTimer;
|
|
242
|
+
workers = [];
|
|
243
|
+
events = [];
|
|
244
|
+
cachedLines;
|
|
245
|
+
scrollOffset = 0;
|
|
246
|
+
disposed = false;
|
|
247
|
+
resizeHandler = null;
|
|
248
|
+
constructor(tui, theme, onClose, basePath) {
|
|
249
|
+
this.tui = tui;
|
|
250
|
+
this.theme = theme;
|
|
251
|
+
this.onClose = onClose;
|
|
252
|
+
this.basePath = basePath || process.cwd();
|
|
253
|
+
this.resizeHandler = () => {
|
|
254
|
+
if (this.disposed)
|
|
255
|
+
return;
|
|
256
|
+
this.invalidate();
|
|
257
|
+
this.tui.requestRender();
|
|
258
|
+
};
|
|
259
|
+
process.stdout.on("resize", this.resizeHandler);
|
|
260
|
+
this.refresh();
|
|
261
|
+
this.refreshTimer = setInterval(() => this.refresh(), 5000);
|
|
262
|
+
}
|
|
263
|
+
refresh() {
|
|
264
|
+
if (this.disposed)
|
|
265
|
+
return;
|
|
266
|
+
this.workers = collectWorkerData(this.basePath);
|
|
267
|
+
// Collect completion events
|
|
268
|
+
for (const wk of this.workers) {
|
|
269
|
+
const completions = queryRecentCompletions(this.basePath, wk.mid);
|
|
270
|
+
for (const evt of completions) {
|
|
271
|
+
if (!this.events.includes(evt))
|
|
272
|
+
this.events.push(evt);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
this.events = this.events.slice(-10);
|
|
276
|
+
this.cachedLines = undefined;
|
|
277
|
+
this.tui.requestRender();
|
|
278
|
+
}
|
|
279
|
+
dispose() {
|
|
280
|
+
this.disposed = true;
|
|
281
|
+
clearInterval(this.refreshTimer);
|
|
282
|
+
if (this.resizeHandler) {
|
|
283
|
+
process.stdout.removeListener("resize", this.resizeHandler);
|
|
284
|
+
this.resizeHandler = null;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
handleInput(data) {
|
|
288
|
+
if (matchesKey(data, Key.escape) || data === "q") {
|
|
289
|
+
this.dispose();
|
|
290
|
+
this.onClose();
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (matchesKey(data, Key.down) || data === "j") {
|
|
294
|
+
this.scrollOffset++;
|
|
295
|
+
this.invalidate();
|
|
296
|
+
this.tui.requestRender();
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (matchesKey(data, Key.up) || data === "k") {
|
|
300
|
+
this.scrollOffset = Math.max(0, this.scrollOffset - 1);
|
|
301
|
+
this.invalidate();
|
|
302
|
+
this.tui.requestRender();
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
invalidate() {
|
|
307
|
+
this.cachedLines = undefined;
|
|
308
|
+
}
|
|
309
|
+
render(width) {
|
|
310
|
+
if (this.cachedLines)
|
|
311
|
+
return this.cachedLines;
|
|
312
|
+
const t = this.theme;
|
|
313
|
+
const lines = [];
|
|
314
|
+
const w = Math.max(width, 60);
|
|
315
|
+
// Header
|
|
316
|
+
const totalCost = this.workers.reduce((s, wk) => s + wk.cost, 0);
|
|
317
|
+
const aliveCount = this.workers.filter((wk) => wk.alive).length;
|
|
318
|
+
const now = new Date().toLocaleTimeString();
|
|
319
|
+
lines.push(t.bold(t.fg("accent", " GSD Parallel Monitor ")));
|
|
320
|
+
lines.push(t.fg("muted", ` ${now} │ ${aliveCount}/${this.workers.length} alive │ Total: `) +
|
|
321
|
+
t.bold(`$${totalCost.toFixed(2)}`) +
|
|
322
|
+
t.fg("muted", " │ 5s refresh"));
|
|
323
|
+
lines.push(t.fg("muted", "─".repeat(w)));
|
|
324
|
+
if (this.workers.length === 0) {
|
|
325
|
+
lines.push("");
|
|
326
|
+
lines.push(t.fg("warning", " No parallel workers found."));
|
|
327
|
+
lines.push(t.fg("muted", " Run /gsd parallel start to begin."));
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
for (const wk of this.workers) {
|
|
331
|
+
lines.push("");
|
|
332
|
+
// Health + ID + state
|
|
333
|
+
const healthColor = wk.alive ? "success" : "error";
|
|
334
|
+
const glyph = healthGlyph(wk.alive, wk.heartbeatAge);
|
|
335
|
+
const stateText = wk.alive
|
|
336
|
+
? t.fg("success", "RUNNING")
|
|
337
|
+
: t.fg("error", t.bold("DEAD"));
|
|
338
|
+
const heartbeatText = wk.heartbeatAge === Infinity
|
|
339
|
+
? "never"
|
|
340
|
+
: formatDuration(wk.heartbeatAge) + " ago";
|
|
341
|
+
lines.push(` ${t.fg(healthColor, glyph)} ${t.bold(wk.mid)} ${stateText} ` +
|
|
342
|
+
t.fg("muted", `PID ${wk.pid} │ elapsed ${formatDuration(wk.elapsed)} │ `) +
|
|
343
|
+
`cost ${t.bold("$" + wk.cost.toFixed(2))} ` +
|
|
344
|
+
t.fg("muted", "│ heartbeat ") + t.fg(healthColor, heartbeatText));
|
|
345
|
+
// Current unit
|
|
346
|
+
if (wk.currentUnit) {
|
|
347
|
+
const phaseColor = wk.unitType === "execute-task" ? "accent"
|
|
348
|
+
: wk.unitType === "research-slice" ? "warning"
|
|
349
|
+
: wk.unitType?.includes("complete") ? "success"
|
|
350
|
+
: "text";
|
|
351
|
+
lines.push(` ${t.fg("muted", "▸")} ${t.fg(phaseColor, unitTypeLabel(wk.unitType))} ${wk.currentUnit} ` +
|
|
352
|
+
t.fg("muted", `(${formatDuration(wk.unitElapsed)})`));
|
|
353
|
+
}
|
|
354
|
+
else if (!wk.alive) {
|
|
355
|
+
lines.push(` ${t.fg("muted", "▸")} ${t.fg("error", "stopped")}`);
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
lines.push(` ${t.fg("muted", "▸ idle / between units")}`);
|
|
359
|
+
}
|
|
360
|
+
// Slice progress chips
|
|
361
|
+
if (wk.slices.length > 0) {
|
|
362
|
+
const chips = wk.slices.map((s) => {
|
|
363
|
+
const pct = s.total > 0 ? s.done / s.total : 0;
|
|
364
|
+
const color = s.status === "complete" ? "success" : pct > 0 ? "warning" : "muted";
|
|
365
|
+
return t.fg(color, `${s.id}:${s.done}/${s.total}`);
|
|
366
|
+
});
|
|
367
|
+
lines.push(` ${t.fg("muted", "slices")} ${chips.join(" ")}`);
|
|
368
|
+
// Task progress bar
|
|
369
|
+
const bar = progressBar(wk.doneTasks, wk.totalTasks, 25);
|
|
370
|
+
const pct = wk.totalTasks > 0 ? Math.round((wk.doneTasks / wk.totalTasks) * 100) : 0;
|
|
371
|
+
lines.push(` ${t.fg("muted", "tasks")} ${t.fg("success", bar)} ${wk.doneTasks}/${wk.totalTasks} ` +
|
|
372
|
+
t.fg("muted", `(${pct}%) │ slices done ${wk.doneSlices}/${wk.totalSlices}`));
|
|
373
|
+
}
|
|
374
|
+
// Errors
|
|
375
|
+
for (const err of wk.errors.slice(-2)) {
|
|
376
|
+
const truncated = err.length > w - 10 ? err.slice(0, w - 11) + "…" : err;
|
|
377
|
+
lines.push(` ${t.fg("error", "⚠ " + truncated)}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
// Event feed
|
|
382
|
+
lines.push("");
|
|
383
|
+
lines.push(t.fg("muted", "─".repeat(w)));
|
|
384
|
+
lines.push(` ${t.bold("Recent Events")}`);
|
|
385
|
+
if (this.events.length === 0) {
|
|
386
|
+
lines.push(t.fg("muted", " No events yet..."));
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
for (const evt of this.events.slice(-8)) {
|
|
390
|
+
const mid = evt.match(/^✓ (M\d+)\//)?.[1] || "";
|
|
391
|
+
const truncated = evt.length > w - 10 ? evt.slice(0, w - 11) + "…" : evt;
|
|
392
|
+
lines.push(` ${t.fg("muted", "│")} ${t.fg("accent", mid)} ${truncated.replace(/^✓ M\d+\//, "")}`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
// Footer
|
|
396
|
+
lines.push("");
|
|
397
|
+
const allDone = this.workers.length > 0 && this.workers.every((wk) => !wk.alive);
|
|
398
|
+
if (allDone) {
|
|
399
|
+
lines.push(t.bold(t.fg("success", " ALL WORKERS COMPLETE")));
|
|
400
|
+
for (const wk of this.workers) {
|
|
401
|
+
lines.push(` ${wk.mid} $${wk.cost.toFixed(2)} │ ${wk.doneSlices}/${wk.totalSlices} slices ` +
|
|
402
|
+
`${wk.doneTasks}/${wk.totalTasks} tasks │ ${formatDuration(wk.elapsed)}`);
|
|
403
|
+
}
|
|
404
|
+
lines.push(` ${t.bold("Total: $" + this.workers.reduce((s, wk) => s + wk.cost, 0).toFixed(2))}`);
|
|
405
|
+
}
|
|
406
|
+
lines.push(t.fg("muted", " ESC/q to close │ ↑↓ scroll"));
|
|
407
|
+
// Apply scroll — use terminal rows as height estimate
|
|
408
|
+
const termHeight = process.stdout.rows || 40;
|
|
409
|
+
const visible = lines.slice(this.scrollOffset, this.scrollOffset + termHeight);
|
|
410
|
+
this.cachedLines = visible;
|
|
411
|
+
return visible;
|
|
412
|
+
}
|
|
413
|
+
}
|