@opengsd/gsd-pi 1.4.0 → 1.5.0
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 -11
- package/dist/onboarding.js +11 -0
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +20 -25
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +118 -53
- package/dist/resources/extensions/cursor-cli/index.js +16 -0
- package/dist/resources/extensions/cursor-cli/models.js +63 -0
- package/dist/resources/extensions/cursor-cli/package.json +11 -0
- package/dist/resources/extensions/cursor-cli/readiness.js +128 -0
- package/dist/resources/extensions/cursor-cli/stream-adapter.js +353 -0
- package/dist/resources/extensions/gsd/activity-log.js +4 -1
- package/dist/resources/extensions/gsd/artifact-verification.js +25 -9
- package/dist/resources/extensions/gsd/auto/closeout.js +45 -42
- package/dist/resources/extensions/gsd/auto/finalize.js +9 -0
- package/dist/resources/extensions/gsd/auto/orchestrator.js +1 -1
- package/dist/resources/extensions/gsd/auto/run-unit.js +5 -2
- package/dist/resources/extensions/gsd/auto/schedule-wakeup.js +3 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +15 -5
- package/dist/resources/extensions/gsd/auto-model-selection.js +36 -18
- package/dist/resources/extensions/gsd/auto-prompts.js +76 -2
- package/dist/resources/extensions/gsd/auto-start.js +4 -3
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +4 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +38 -11
- package/dist/resources/extensions/gsd/auto.js +23 -3
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +114 -5
- package/dist/resources/extensions/gsd/bootstrap/schedule-wakeup-tool.js +58 -9
- package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +10 -2
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +14 -1
- package/dist/resources/extensions/gsd/codebase-generator.js +157 -19
- package/dist/resources/extensions/gsd/commands/handlers/core.js +40 -4
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +153 -1
- package/dist/resources/extensions/gsd/commands-worktree.js +11 -2
- package/dist/resources/extensions/gsd/db/queries.js +39 -0
- package/dist/resources/extensions/gsd/discussion-handoff.js +8 -3
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +8 -2
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +206 -14
- package/dist/resources/extensions/gsd/doctor-providers.js +38 -10
- package/dist/resources/extensions/gsd/doctor.js +93 -3
- package/dist/resources/extensions/gsd/exec-sandbox.js +6 -2
- package/dist/resources/extensions/gsd/flat-phase-migration.js +74 -23
- package/dist/resources/extensions/gsd/git-service.js +14 -18
- package/dist/resources/extensions/gsd/gsd-db.js +52 -0
- package/dist/resources/extensions/gsd/guidance.js +1 -0
- package/dist/resources/extensions/gsd/guided-flow.js +40 -2
- package/dist/resources/extensions/gsd/key-manager.js +1 -0
- package/dist/resources/extensions/gsd/markdown-renderer.js +25 -8
- package/dist/resources/extensions/gsd/migrate-external.js +24 -7
- package/dist/resources/extensions/gsd/milestone-merge-transaction.js +3 -3
- package/dist/resources/extensions/gsd/preferences-diagnostics.js +24 -1
- package/dist/resources/extensions/gsd/preferences-models.js +6 -4
- package/dist/resources/extensions/gsd/preferences-validation.js +25 -2
- package/dist/resources/extensions/gsd/preferences.js +29 -2
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +4 -2
- package/dist/resources/extensions/gsd/redact-secrets.js +37 -0
- package/dist/resources/extensions/gsd/repo-identity.js +16 -0
- package/dist/resources/extensions/gsd/repository-registry.js +6 -1
- package/dist/resources/extensions/gsd/root-write-leak-guard.js +4 -2
- package/dist/resources/extensions/gsd/safe-fs.js +23 -2
- package/dist/resources/extensions/gsd/skills/gsd-headless/SKILL.md +7 -9
- package/dist/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -0
- package/dist/resources/extensions/gsd/tools/plan-task.js +76 -8
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +40 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +5 -4
- package/dist/resources/extensions/gsd/unit-registry.js +27 -2
- package/dist/resources/extensions/gsd/unit-runtime.js +21 -0
- package/dist/resources/extensions/gsd/workspace.js +26 -8
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +67 -5
- package/dist/resources/extensions/gsd/worktree-manager.js +97 -4
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- 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.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- 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/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +7 -7
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/wizard.d.ts +8 -0
- package/dist/wizard.js +8 -1
- package/integrations/hermes/CONTEXT.md +47 -0
- package/integrations/hermes/README.md +21 -0
- package/integrations/hermes/__init__.py +31 -0
- package/integrations/hermes/docs/issue-1162-grilling.md +197 -0
- package/integrations/hermes/docs/setup.md +4 -0
- package/integrations/hermes/open_gsd_hermes/commands.py +141 -0
- package/integrations/hermes/open_gsd_hermes/config.py +2 -0
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +594 -31
- package/integrations/hermes/open_gsd_hermes/notifications.py +10 -1
- package/integrations/hermes/plugin.yaml +4 -0
- package/integrations/hermes/tests/test_config.py +6 -0
- package/integrations/hermes/tests/test_directory_loader.py +37 -0
- package/integrations/hermes/tests/test_gsd_client_cache.py +3 -0
- package/integrations/hermes/tests/test_gsd_client_execute.py +35 -0
- package/integrations/hermes/tests/test_gsd_client_process_cwd.py +22 -0
- package/integrations/hermes/tests/test_gsd_client_timeout.py +98 -0
- package/integrations/hermes/tests/test_milestone_client.py +447 -0
- package/integrations/hermes/tests/test_new_milestone_command.py +313 -0
- package/integrations/hermes/tests/test_supervisor_fsm.py +40 -0
- package/package.json +8 -8
- package/packages/cloud-mcp-gateway/dist/auth-store.d.ts +1 -0
- package/packages/cloud-mcp-gateway/dist/auth-store.d.ts.map +1 -1
- package/packages/cloud-mcp-gateway/dist/auth-store.js +21 -1
- package/packages/cloud-mcp-gateway/dist/auth-store.js.map +1 -1
- package/packages/cloud-mcp-gateway/dist/auth-store.test.js +34 -0
- package/packages/cloud-mcp-gateway/dist/auth-store.test.js.map +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/dist/cloud-runtime.d.ts +2 -0
- package/packages/daemon/dist/cloud-runtime.d.ts.map +1 -1
- package/packages/daemon/dist/cloud-runtime.js +25 -1
- package/packages/daemon/dist/cloud-runtime.js.map +1 -1
- package/packages/daemon/dist/cloud-runtime.test.js +51 -0
- package/packages/daemon/dist/cloud-runtime.test.js.map +1 -1
- package/packages/daemon/dist/message-batcher.d.ts +7 -0
- package/packages/daemon/dist/message-batcher.d.ts.map +1 -1
- package/packages/daemon/dist/message-batcher.js +13 -0
- package/packages/daemon/dist/message-batcher.js.map +1 -1
- package/packages/daemon/dist/message-batcher.test.js +48 -0
- package/packages/daemon/dist/message-batcher.test.js.map +1 -1
- package/packages/daemon/dist/session-manager.js +1 -1
- package/packages/daemon/dist/session-manager.js.map +1 -1
- package/packages/daemon/package.json +5 -5
- package/packages/gsd-agent-core/dist/compaction/compaction.d.ts +13 -1
- package/packages/gsd-agent-core/dist/compaction/compaction.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/compaction/compaction.js +15 -1
- package/packages/gsd-agent-core/dist/compaction/compaction.js.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-navigation.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-navigation.js +4 -1
- package/packages/gsd-agent-core/dist/session/agent-session-navigation.js.map +1 -1
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js +1 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/server.d.ts +18 -5
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +46 -5
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/session-manager.js +1 -1
- package/packages/mcp-server/dist/session-manager.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/env-api-keys.d.ts.map +1 -1
- package/packages/pi-ai/dist/env-api-keys.js +3 -0
- package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +2 -2
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager-types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js +3 -0
- package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/dist/rpc-client.js +1 -1
- package/packages/rpc-client/dist/rpc-client.js.map +1 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/scripts/link-workspace-packages.cjs +14 -4
- package/src/resources/GSD-WORKFLOW.md +20 -25
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +130 -51
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +371 -39
- package/src/resources/extensions/cursor-cli/index.ts +18 -0
- package/src/resources/extensions/cursor-cli/models.ts +65 -0
- package/src/resources/extensions/cursor-cli/package.json +11 -0
- package/src/resources/extensions/cursor-cli/readiness.ts +143 -0
- package/src/resources/extensions/cursor-cli/stream-adapter.ts +440 -0
- package/src/resources/extensions/cursor-cli/tests/index.test.ts +54 -0
- package/src/resources/extensions/cursor-cli/tests/readiness.test.ts +29 -0
- package/src/resources/extensions/cursor-cli/tests/stream-adapter.test.ts +166 -0
- package/src/resources/extensions/gsd/activity-log.ts +4 -1
- package/src/resources/extensions/gsd/artifact-verification.ts +25 -9
- package/src/resources/extensions/gsd/auto/closeout.ts +54 -56
- package/src/resources/extensions/gsd/auto/finalize.ts +8 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/orchestrator.ts +1 -1
- package/src/resources/extensions/gsd/auto/run-unit.ts +5 -2
- package/src/resources/extensions/gsd/auto/schedule-wakeup.ts +8 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +17 -5
- package/src/resources/extensions/gsd/auto-model-selection.ts +49 -18
- package/src/resources/extensions/gsd/auto-prompts.ts +73 -2
- package/src/resources/extensions/gsd/auto-start.ts +4 -3
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +4 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +50 -11
- package/src/resources/extensions/gsd/auto.ts +22 -3
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +128 -5
- package/src/resources/extensions/gsd/bootstrap/schedule-wakeup-tool.ts +80 -12
- package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +12 -2
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +23 -1
- package/src/resources/extensions/gsd/codebase-generator.ts +179 -18
- package/src/resources/extensions/gsd/commands/handlers/core.ts +39 -3
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +135 -1
- package/src/resources/extensions/gsd/commands-worktree.ts +13 -2
- package/src/resources/extensions/gsd/db/queries.ts +40 -0
- package/src/resources/extensions/gsd/discussion-handoff.ts +10 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +8 -2
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +269 -18
- package/src/resources/extensions/gsd/doctor-providers.ts +39 -10
- package/src/resources/extensions/gsd/doctor-types.ts +5 -1
- package/src/resources/extensions/gsd/doctor.ts +95 -3
- package/src/resources/extensions/gsd/exec-sandbox.ts +6 -2
- package/src/resources/extensions/gsd/flat-phase-migration.ts +98 -22
- package/src/resources/extensions/gsd/git-service.ts +14 -17
- package/src/resources/extensions/gsd/gsd-db.ts +72 -0
- package/src/resources/extensions/gsd/guidance.ts +2 -0
- package/src/resources/extensions/gsd/guided-flow.ts +45 -2
- package/src/resources/extensions/gsd/key-manager.ts +1 -0
- package/src/resources/extensions/gsd/markdown-renderer.ts +25 -7
- package/src/resources/extensions/gsd/migrate-external.ts +23 -6
- package/src/resources/extensions/gsd/milestone-merge-transaction.ts +4 -4
- package/src/resources/extensions/gsd/model-router.ts +4 -5
- package/src/resources/extensions/gsd/preferences-diagnostics.ts +24 -0
- package/src/resources/extensions/gsd/preferences-models.ts +6 -3
- package/src/resources/extensions/gsd/preferences-validation.ts +28 -2
- package/src/resources/extensions/gsd/preferences.ts +38 -4
- package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -2
- package/src/resources/extensions/gsd/redact-secrets.ts +40 -0
- package/src/resources/extensions/gsd/repo-identity.ts +17 -0
- package/src/resources/extensions/gsd/repository-registry.ts +7 -1
- package/src/resources/extensions/gsd/root-write-leak-guard.ts +3 -2
- package/src/resources/extensions/gsd/safe-fs.ts +22 -2
- package/src/resources/extensions/gsd/skills/gsd-headless/SKILL.md +7 -9
- package/src/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +131 -5
- package/src/resources/extensions/gsd/tests/auto-migrating-recovery.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +178 -0
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +43 -1
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/auto-remote-session-lock-cleanup.test.ts +41 -0
- package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +41 -0
- package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/auto-timeout-recovery-harness-abort.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/batch-task-query.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/check-auto-start-ready-guard.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +365 -0
- package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +62 -1
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +138 -1
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +397 -5
- package/src/resources/extensions/gsd/tests/doctor-workspace.test.ts +207 -0
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +38 -1
- package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +87 -19
- package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +89 -0
- package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +122 -0
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/migrate-external-worktree.test.ts +116 -0
- package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +152 -22
- package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +50 -6
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +264 -1
- package/src/resources/extensions/gsd/tests/preferences.test.ts +150 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +142 -0
- package/src/resources/extensions/gsd/tests/recovery-verify-logs.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/redact-secrets.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/register-hooks-flat-phase-migration.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/register-hooks-loop-guard-auto.test.ts +275 -0
- package/src/resources/extensions/gsd/tests/repository-registry.test.ts +41 -2
- package/src/resources/extensions/gsd/tests/require-slice-discussion-dispatch.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/root-write-leak-guard.test.ts +31 -9
- package/src/resources/extensions/gsd/tests/schedule-wakeup-tool.test.ts +167 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/status-per-repo.test.ts +94 -0
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +66 -18
- package/src/resources/extensions/gsd/tests/unit-registry.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/uok-gitops-turn-action.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +53 -1
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +208 -1
- package/src/resources/extensions/gsd/tests/workspace.test.ts +18 -4
- package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +31 -1
- package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +32 -0
- package/src/resources/extensions/gsd/tools/plan-task.ts +107 -9
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +58 -0
- package/src/resources/extensions/gsd/unit-context-manifest.ts +11 -5
- package/src/resources/extensions/gsd/unit-registry.ts +34 -2
- package/src/resources/extensions/gsd/unit-runtime.ts +42 -0
- package/src/resources/extensions/gsd/workspace.ts +42 -8
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +135 -11
- package/src/resources/extensions/gsd/worktree-manager.ts +143 -5
- /package/dist/web/standalone/.next/static/{zQLcXm0Za8lexoXtgDpMG → bCg3aYrFN1DKZr2dqgE7L}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{zQLcXm0Za8lexoXtgDpMG → bCg3aYrFN1DKZr2dqgE7L}/_ssgManifest.js +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// gsd-pi - Claude Code stream adapter regression tests
|
|
2
|
-
import { describe, test } from "node:test";
|
|
2
|
+
import { describe, mock, test } from "node:test";
|
|
3
3
|
import { clearGuidedUnitContext, setGuidedUnitContext } from "../../gsd/guided-unit-context.ts";
|
|
4
4
|
import assert from "node:assert/strict";
|
|
5
5
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
buildWorkflowMcpReadinessProgressMessage,
|
|
47
47
|
pushWorkflowMcpReadinessProgressEvent,
|
|
48
48
|
resolveWorkflowMcpPreflightServerConfig,
|
|
49
|
+
CLAUDE_CODE_INTERVIEW_FORM_TIMEOUT_MS,
|
|
49
50
|
} from "../stream-adapter.ts";
|
|
50
51
|
import { CLAUDE_CODE_MODELS } from "../models.ts";
|
|
51
52
|
import type { AssistantMessage, Context, Message } from "@gsd/pi-ai";
|
|
@@ -107,6 +108,51 @@ function setWorkflowMcpEnv(
|
|
|
107
108
|
};
|
|
108
109
|
}
|
|
109
110
|
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// Shared MCP elicitation fixture — a representative ask_user_questions
|
|
113
|
+
// elicitation request. Used both by the external-tool-results interview
|
|
114
|
+
// lifecycle tests and by the MCP elicitation bridge tests, so it lives at
|
|
115
|
+
// module scope.
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
const askUserQuestionsRequest = {
|
|
119
|
+
serverName: "gsd-workflow",
|
|
120
|
+
message: "Please answer the following question(s).",
|
|
121
|
+
mode: "form" as const,
|
|
122
|
+
requestedSchema: {
|
|
123
|
+
type: "object" as const,
|
|
124
|
+
properties: {
|
|
125
|
+
storage_scope: {
|
|
126
|
+
type: "string",
|
|
127
|
+
title: "Storage",
|
|
128
|
+
description: "Does this app need to sync across devices?",
|
|
129
|
+
oneOf: [
|
|
130
|
+
{ const: "Local-only (Recommended)", title: "Local-only (Recommended)" },
|
|
131
|
+
{ const: "Cloud-synced", title: "Cloud-synced" },
|
|
132
|
+
{ const: "None of the above", title: "None of the above" },
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
storage_scope__note: {
|
|
136
|
+
type: "string",
|
|
137
|
+
title: "Storage Note",
|
|
138
|
+
description: "Optional note for None of the above.",
|
|
139
|
+
},
|
|
140
|
+
platform: {
|
|
141
|
+
type: "array",
|
|
142
|
+
title: "Platform",
|
|
143
|
+
description: "Where should it run?",
|
|
144
|
+
items: {
|
|
145
|
+
anyOf: [
|
|
146
|
+
{ const: "Web", title: "Web" },
|
|
147
|
+
{ const: "Desktop", title: "Desktop" },
|
|
148
|
+
{ const: "Mobile", title: "Mobile" },
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
|
|
110
156
|
// ---------------------------------------------------------------------------
|
|
111
157
|
// Existing tests — exhausted stream fallback (#2575)
|
|
112
158
|
// ---------------------------------------------------------------------------
|
|
@@ -661,6 +707,293 @@ describe("stream-adapter — no transcript fabrication (#4102)", () => {
|
|
|
661
707
|
});
|
|
662
708
|
|
|
663
709
|
describe("stream-adapter — Claude Code external tool results", () => {
|
|
710
|
+
test("emits one synthetic completion per tool when SDK sends consecutive user result messages (#1153)", async () => {
|
|
711
|
+
// Capture synthetic completions through onExternalToolResult, which the
|
|
712
|
+
// adapter invokes exactly once per tool at emission time. Filtering the
|
|
713
|
+
// collected toolcall_end events afterwards is unreliable here: the streaming
|
|
714
|
+
// toolcall_end (content_block_stop) and the synthetic completion reference
|
|
715
|
+
// the SAME tool-call block, and attachExternalResultsToToolBlocks mutates
|
|
716
|
+
// that block later, so a post-hoc `event.toolCall.externalResult` check also
|
|
717
|
+
// matches the earlier streaming ends and double-counts each tool.
|
|
718
|
+
const syntheticCompletions: Array<{ id: string; text: string | undefined }> = [];
|
|
719
|
+
const stream = streamViaClaudeCode(
|
|
720
|
+
{ id: "claude-sonnet-4-6" } as any,
|
|
721
|
+
{ messages: [{ role: "user", content: "Read and grep." } as Message] },
|
|
722
|
+
{
|
|
723
|
+
_skipWorkflowMcpPreflightForTest: true,
|
|
724
|
+
onExternalToolResult(event: {
|
|
725
|
+
toolCall: { id: string };
|
|
726
|
+
result: { content?: Array<{ text?: string }> };
|
|
727
|
+
}) {
|
|
728
|
+
syntheticCompletions.push({
|
|
729
|
+
id: event.toolCall.id,
|
|
730
|
+
text: event.result.content?.[0]?.text,
|
|
731
|
+
});
|
|
732
|
+
},
|
|
733
|
+
async *_sdkQueryForTest() {
|
|
734
|
+
yield {
|
|
735
|
+
type: "stream_event",
|
|
736
|
+
event: { type: "message_start", message: { model: "claude-sonnet-4-6" } },
|
|
737
|
+
parent_tool_use_id: null,
|
|
738
|
+
uuid: "partial-1",
|
|
739
|
+
session_id: "session-1",
|
|
740
|
+
};
|
|
741
|
+
yield {
|
|
742
|
+
type: "stream_event",
|
|
743
|
+
event: {
|
|
744
|
+
type: "content_block_start",
|
|
745
|
+
index: 0,
|
|
746
|
+
content_block: { type: "tool_use", id: "tool-read-1", name: "Read", input: {} },
|
|
747
|
+
},
|
|
748
|
+
parent_tool_use_id: null,
|
|
749
|
+
uuid: "partial-1",
|
|
750
|
+
session_id: "session-1",
|
|
751
|
+
};
|
|
752
|
+
yield {
|
|
753
|
+
type: "stream_event",
|
|
754
|
+
event: {
|
|
755
|
+
type: "content_block_delta",
|
|
756
|
+
index: 0,
|
|
757
|
+
delta: { type: "input_json_delta", partial_json: "{\"file_path\":\"a.txt\"}" },
|
|
758
|
+
},
|
|
759
|
+
parent_tool_use_id: null,
|
|
760
|
+
uuid: "partial-1",
|
|
761
|
+
session_id: "session-1",
|
|
762
|
+
};
|
|
763
|
+
yield {
|
|
764
|
+
type: "stream_event",
|
|
765
|
+
event: { type: "content_block_stop", index: 0 },
|
|
766
|
+
parent_tool_use_id: null,
|
|
767
|
+
uuid: "partial-1",
|
|
768
|
+
session_id: "session-1",
|
|
769
|
+
};
|
|
770
|
+
yield {
|
|
771
|
+
type: "stream_event",
|
|
772
|
+
event: {
|
|
773
|
+
type: "content_block_start",
|
|
774
|
+
index: 1,
|
|
775
|
+
content_block: { type: "tool_use", id: "tool-grep-1", name: "Grep", input: {} },
|
|
776
|
+
},
|
|
777
|
+
parent_tool_use_id: null,
|
|
778
|
+
uuid: "partial-1",
|
|
779
|
+
session_id: "session-1",
|
|
780
|
+
};
|
|
781
|
+
yield {
|
|
782
|
+
type: "stream_event",
|
|
783
|
+
event: {
|
|
784
|
+
type: "content_block_delta",
|
|
785
|
+
index: 1,
|
|
786
|
+
delta: { type: "input_json_delta", partial_json: "{\"pattern\":\"needle\"}" },
|
|
787
|
+
},
|
|
788
|
+
parent_tool_use_id: null,
|
|
789
|
+
uuid: "partial-1",
|
|
790
|
+
session_id: "session-1",
|
|
791
|
+
};
|
|
792
|
+
yield {
|
|
793
|
+
type: "stream_event",
|
|
794
|
+
event: { type: "content_block_stop", index: 1 },
|
|
795
|
+
parent_tool_use_id: null,
|
|
796
|
+
uuid: "partial-1",
|
|
797
|
+
session_id: "session-1",
|
|
798
|
+
};
|
|
799
|
+
yield {
|
|
800
|
+
type: "user",
|
|
801
|
+
uuid: "user-result-1",
|
|
802
|
+
session_id: "session-1",
|
|
803
|
+
parent_tool_use_id: "tool-read-1",
|
|
804
|
+
message: {
|
|
805
|
+
role: "user",
|
|
806
|
+
content: [{
|
|
807
|
+
type: "tool_result",
|
|
808
|
+
tool_use_id: "tool-read-1",
|
|
809
|
+
content: "read result",
|
|
810
|
+
is_error: false,
|
|
811
|
+
}],
|
|
812
|
+
},
|
|
813
|
+
};
|
|
814
|
+
yield {
|
|
815
|
+
type: "user",
|
|
816
|
+
uuid: "user-result-2",
|
|
817
|
+
session_id: "session-1",
|
|
818
|
+
parent_tool_use_id: "tool-grep-1",
|
|
819
|
+
message: {
|
|
820
|
+
role: "user",
|
|
821
|
+
content: [{
|
|
822
|
+
type: "tool_result",
|
|
823
|
+
tool_use_id: "tool-grep-1",
|
|
824
|
+
content: "grep result",
|
|
825
|
+
is_error: false,
|
|
826
|
+
}],
|
|
827
|
+
},
|
|
828
|
+
};
|
|
829
|
+
yield {
|
|
830
|
+
type: "result",
|
|
831
|
+
subtype: "success",
|
|
832
|
+
uuid: "result-1",
|
|
833
|
+
session_id: "session-1",
|
|
834
|
+
duration_ms: 1,
|
|
835
|
+
duration_api_ms: 1,
|
|
836
|
+
is_error: false,
|
|
837
|
+
num_turns: 1,
|
|
838
|
+
result: "done",
|
|
839
|
+
stop_reason: "end_turn",
|
|
840
|
+
total_cost_usd: 0,
|
|
841
|
+
usage: {
|
|
842
|
+
input_tokens: 0,
|
|
843
|
+
output_tokens: 0,
|
|
844
|
+
cache_read_input_tokens: 0,
|
|
845
|
+
cache_creation_input_tokens: 0,
|
|
846
|
+
},
|
|
847
|
+
};
|
|
848
|
+
},
|
|
849
|
+
} as any,
|
|
850
|
+
);
|
|
851
|
+
|
|
852
|
+
// Drain the stream so the adapter processes every SDK message and fires
|
|
853
|
+
// onExternalToolResult for each synthetic completion.
|
|
854
|
+
const events: any[] = [];
|
|
855
|
+
for await (const event of stream) {
|
|
856
|
+
events.push(event);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
assert.deepEqual(
|
|
860
|
+
syntheticCompletions.map((completion) => completion.id),
|
|
861
|
+
["tool-read-1", "tool-grep-1"],
|
|
862
|
+
);
|
|
863
|
+
assert.deepEqual(
|
|
864
|
+
syntheticCompletions.map((completion) => completion.text),
|
|
865
|
+
["read result", "grep result"],
|
|
866
|
+
);
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
test("closes an in-flight interview when ask_user_questions reports timed_out first", async (t) => {
|
|
870
|
+
const notifications: Array<{ message: string; type?: string }> = [];
|
|
871
|
+
let elicitationPromise: Promise<unknown> | undefined;
|
|
872
|
+
let elicitationSignalController: AbortController | undefined;
|
|
873
|
+
let customOpened = false;
|
|
874
|
+
const ui = {
|
|
875
|
+
custom: async (factory: any) => new Promise((resolve) => {
|
|
876
|
+
customOpened = true;
|
|
877
|
+
factory({ requestRender() {} } as any, {} as any, {} as any, resolve);
|
|
878
|
+
}),
|
|
879
|
+
notify: (message: string, type?: string) => {
|
|
880
|
+
notifications.push({ message, type });
|
|
881
|
+
},
|
|
882
|
+
};
|
|
883
|
+
t.after(() => {
|
|
884
|
+
elicitationSignalController?.abort();
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
const stream = streamViaClaudeCode(
|
|
888
|
+
{ id: "claude-sonnet-4-6" } as any,
|
|
889
|
+
{ messages: [{ role: "user", content: "Ask me." } as Message] },
|
|
890
|
+
{
|
|
891
|
+
extensionUIContext: ui as any,
|
|
892
|
+
_skipWorkflowMcpPreflightForTest: true,
|
|
893
|
+
async *_sdkQueryForTest(args: {
|
|
894
|
+
prompt: string | AsyncIterable<unknown>;
|
|
895
|
+
options?: Record<string, unknown>;
|
|
896
|
+
}) {
|
|
897
|
+
const onElicitation = args.options?.onElicitation as
|
|
898
|
+
| ((request: unknown, options: { signal: AbortSignal }) => Promise<unknown>)
|
|
899
|
+
| undefined;
|
|
900
|
+
assert.equal(typeof onElicitation, "function");
|
|
901
|
+
elicitationSignalController = new AbortController();
|
|
902
|
+
elicitationPromise = onElicitation!(askUserQuestionsRequest, {
|
|
903
|
+
signal: elicitationSignalController.signal,
|
|
904
|
+
});
|
|
905
|
+
assert.equal(customOpened, true, "interview overlay should be open before the tool result arrives");
|
|
906
|
+
|
|
907
|
+
// The adapter only assembles tool-call blocks after a
|
|
908
|
+
// message_start has created the partial-message builder. Real
|
|
909
|
+
// SDK streams always emit it before a tool_use content block;
|
|
910
|
+
// without it the timed_out result is never matched to the
|
|
911
|
+
// in-flight ask_user_questions block, so the interview would
|
|
912
|
+
// never be closed.
|
|
913
|
+
yield {
|
|
914
|
+
type: "stream_event",
|
|
915
|
+
event: { type: "message_start", message: { model: "claude-sonnet-4-6" } },
|
|
916
|
+
parent_tool_use_id: null,
|
|
917
|
+
uuid: "partial-1",
|
|
918
|
+
session_id: "session-1",
|
|
919
|
+
};
|
|
920
|
+
yield {
|
|
921
|
+
type: "stream_event",
|
|
922
|
+
event: {
|
|
923
|
+
type: "content_block_start",
|
|
924
|
+
index: 0,
|
|
925
|
+
content_block: {
|
|
926
|
+
type: "tool_use",
|
|
927
|
+
id: "ask-1",
|
|
928
|
+
name: "mcp__gsd-workflow__ask_user_questions",
|
|
929
|
+
input: {},
|
|
930
|
+
},
|
|
931
|
+
},
|
|
932
|
+
parent_tool_use_id: null,
|
|
933
|
+
uuid: "partial-1",
|
|
934
|
+
session_id: "session-1",
|
|
935
|
+
};
|
|
936
|
+
yield {
|
|
937
|
+
type: "user",
|
|
938
|
+
uuid: "user-result-1",
|
|
939
|
+
session_id: "session-1",
|
|
940
|
+
parent_tool_use_id: "ask-1",
|
|
941
|
+
message: {
|
|
942
|
+
role: "user",
|
|
943
|
+
content: [{
|
|
944
|
+
type: "mcp_tool_result",
|
|
945
|
+
tool_use_id: "ask-1",
|
|
946
|
+
content: [{ type: "text", text: "timed out" }],
|
|
947
|
+
structuredContent: { timed_out: true },
|
|
948
|
+
is_error: false,
|
|
949
|
+
}],
|
|
950
|
+
},
|
|
951
|
+
};
|
|
952
|
+
yield {
|
|
953
|
+
type: "result",
|
|
954
|
+
subtype: "success",
|
|
955
|
+
uuid: "result-1",
|
|
956
|
+
session_id: "session-1",
|
|
957
|
+
duration_ms: 1,
|
|
958
|
+
duration_api_ms: 1,
|
|
959
|
+
is_error: false,
|
|
960
|
+
num_turns: 1,
|
|
961
|
+
result: "done",
|
|
962
|
+
stop_reason: "end_turn",
|
|
963
|
+
total_cost_usd: 0,
|
|
964
|
+
usage: {
|
|
965
|
+
input_tokens: 0,
|
|
966
|
+
output_tokens: 0,
|
|
967
|
+
cache_read_input_tokens: 0,
|
|
968
|
+
cache_creation_input_tokens: 0,
|
|
969
|
+
},
|
|
970
|
+
};
|
|
971
|
+
},
|
|
972
|
+
} as any,
|
|
973
|
+
);
|
|
974
|
+
|
|
975
|
+
await stream.result();
|
|
976
|
+
assert.ok(elicitationPromise);
|
|
977
|
+
let guard: ReturnType<typeof setTimeout> | undefined;
|
|
978
|
+
const elicitationResult = await Promise.race([
|
|
979
|
+
elicitationPromise,
|
|
980
|
+
new Promise<never>((_, reject) => {
|
|
981
|
+
guard = setTimeout(
|
|
982
|
+
() => reject(new Error("timed out waiting for in-flight interview to close")),
|
|
983
|
+
250,
|
|
984
|
+
);
|
|
985
|
+
}),
|
|
986
|
+
]).finally(() => {
|
|
987
|
+
if (guard) clearTimeout(guard);
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
assert.deepEqual(elicitationResult, { action: "decline" });
|
|
991
|
+
assert.deepEqual(notifications, [{
|
|
992
|
+
message: "Question expired before you answered - the form was closed and no answers were sent.",
|
|
993
|
+
type: "warning",
|
|
994
|
+
}]);
|
|
995
|
+
});
|
|
996
|
+
|
|
664
997
|
test("serverToolUseToToolCallLike preserves object input for extension tool_result routing", () => {
|
|
665
998
|
const toolCall = serverToolUseToToolCallLike({
|
|
666
999
|
id: "srv-1",
|
|
@@ -2436,44 +2769,6 @@ describe("stream-adapter — workflow MCP readiness", () => {
|
|
|
2436
2769
|
});
|
|
2437
2770
|
|
|
2438
2771
|
describe("stream-adapter — MCP elicitation bridge", () => {
|
|
2439
|
-
const askUserQuestionsRequest = {
|
|
2440
|
-
serverName: "gsd-workflow",
|
|
2441
|
-
message: "Please answer the following question(s).",
|
|
2442
|
-
mode: "form" as const,
|
|
2443
|
-
requestedSchema: {
|
|
2444
|
-
type: "object" as const,
|
|
2445
|
-
properties: {
|
|
2446
|
-
storage_scope: {
|
|
2447
|
-
type: "string",
|
|
2448
|
-
title: "Storage",
|
|
2449
|
-
description: "Does this app need to sync across devices?",
|
|
2450
|
-
oneOf: [
|
|
2451
|
-
{ const: "Local-only (Recommended)", title: "Local-only (Recommended)" },
|
|
2452
|
-
{ const: "Cloud-synced", title: "Cloud-synced" },
|
|
2453
|
-
{ const: "None of the above", title: "None of the above" },
|
|
2454
|
-
],
|
|
2455
|
-
},
|
|
2456
|
-
storage_scope__note: {
|
|
2457
|
-
type: "string",
|
|
2458
|
-
title: "Storage Note",
|
|
2459
|
-
description: "Optional note for None of the above.",
|
|
2460
|
-
},
|
|
2461
|
-
platform: {
|
|
2462
|
-
type: "array",
|
|
2463
|
-
title: "Platform",
|
|
2464
|
-
description: "Where should it run?",
|
|
2465
|
-
items: {
|
|
2466
|
-
anyOf: [
|
|
2467
|
-
{ const: "Web", title: "Web" },
|
|
2468
|
-
{ const: "Desktop", title: "Desktop" },
|
|
2469
|
-
{ const: "Mobile", title: "Mobile" },
|
|
2470
|
-
],
|
|
2471
|
-
},
|
|
2472
|
-
},
|
|
2473
|
-
},
|
|
2474
|
-
},
|
|
2475
|
-
};
|
|
2476
|
-
|
|
2477
2772
|
test("parseAskUserQuestionsElicitation rebuilds interview questions from the MCP schema", () => {
|
|
2478
2773
|
const questions = parseAskUserQuestionsElicitation(askUserQuestionsRequest);
|
|
2479
2774
|
assert.deepEqual(questions, [
|
|
@@ -2882,6 +3177,43 @@ describe("stream-adapter — MCP elicitation bridge", () => {
|
|
|
2882
3177
|
assert.deepEqual(result, { action: "decline" });
|
|
2883
3178
|
});
|
|
2884
3179
|
|
|
3180
|
+
test("expires the interview overlay before the MCP elicitation response is dropped", async (t) => {
|
|
3181
|
+
mock.timers.enable({ apis: ["setTimeout"] });
|
|
3182
|
+
t.after(() => {
|
|
3183
|
+
mock.timers.reset();
|
|
3184
|
+
});
|
|
3185
|
+
|
|
3186
|
+
const notifications: Array<{ message: string; type?: string }> = [];
|
|
3187
|
+
let customOpened = false;
|
|
3188
|
+
const handler = createClaudeCodeElicitationHandler({
|
|
3189
|
+
custom: async (factory: any) => new Promise((resolve) => {
|
|
3190
|
+
customOpened = true;
|
|
3191
|
+
factory({ requestRender() {} } as any, {} as any, {} as any, resolve);
|
|
3192
|
+
}),
|
|
3193
|
+
notify: (message: string, type?: string) => {
|
|
3194
|
+
notifications.push({ message, type });
|
|
3195
|
+
},
|
|
3196
|
+
select: async () => {
|
|
3197
|
+
throw new Error("expired interview must not re-open dialog fallback");
|
|
3198
|
+
},
|
|
3199
|
+
} as any);
|
|
3200
|
+
assert.ok(handler);
|
|
3201
|
+
|
|
3202
|
+
const resultPromise = handler!(askUserQuestionsRequest, { signal: new AbortController().signal });
|
|
3203
|
+
await Promise.resolve();
|
|
3204
|
+
assert.equal(customOpened, true);
|
|
3205
|
+
|
|
3206
|
+
mock.timers.tick(CLAUDE_CODE_INTERVIEW_FORM_TIMEOUT_MS);
|
|
3207
|
+
const result = await resultPromise;
|
|
3208
|
+
|
|
3209
|
+
assert.deepEqual(result, { action: "decline" });
|
|
3210
|
+
assert.deepEqual(notifications, [{
|
|
3211
|
+
message: "Question expired before you answered - the form was closed and no answers were sent.",
|
|
3212
|
+
type: "warning",
|
|
3213
|
+
}]);
|
|
3214
|
+
assert.equal(isInteractiveElicitationInFlight(), false, "marker must clear after deadline expiry");
|
|
3215
|
+
});
|
|
3216
|
+
|
|
2885
3217
|
test("returns cancel (today's semantics) when the user genuinely dismisses", async () => {
|
|
2886
3218
|
const handler = createClaudeCodeElicitationHandler({
|
|
2887
3219
|
custom: async () => ({ endInterview: false, answers: {} }),
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@gsd/pi-coding-agent";
|
|
2
|
+
import { CURSOR_AGENT_MODELS } from "./models.js";
|
|
3
|
+
import { isCursorAgentReady } from "./readiness.js";
|
|
4
|
+
import { streamViaCursorAgent } from "./stream-adapter.js";
|
|
5
|
+
|
|
6
|
+
export default function cursorCli(pi: ExtensionAPI): void {
|
|
7
|
+
if (process.env.GSD_CURSOR_DISABLE === "1") return;
|
|
8
|
+
|
|
9
|
+
pi.registerProvider("cursor-agent", {
|
|
10
|
+
name: "Cursor Agent",
|
|
11
|
+
authMode: "externalCli",
|
|
12
|
+
api: "cursor-stream-json",
|
|
13
|
+
baseUrl: "local://cursor-agent",
|
|
14
|
+
isReady: isCursorAgentReady,
|
|
15
|
+
streamSimple: streamViaCursorAgent,
|
|
16
|
+
models: CURSOR_AGENT_MODELS,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const ZERO_COST = {
|
|
2
|
+
input: 0,
|
|
3
|
+
output: 0,
|
|
4
|
+
cacheRead: 0,
|
|
5
|
+
cacheWrite: 0,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const CURSOR_INPUT: ("text" | "image")[] = ["text"];
|
|
9
|
+
|
|
10
|
+
export const CURSOR_AGENT_MODELS = [
|
|
11
|
+
{
|
|
12
|
+
id: "composer-2.5",
|
|
13
|
+
name: "Composer 2.5 (via Cursor)",
|
|
14
|
+
reasoning: true,
|
|
15
|
+
input: CURSOR_INPUT,
|
|
16
|
+
cost: ZERO_COST,
|
|
17
|
+
contextWindow: 1_000_000,
|
|
18
|
+
maxTokens: 64_000,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: "claude-sonnet-4-6",
|
|
22
|
+
name: "Claude Sonnet 4.6 (via Cursor)",
|
|
23
|
+
reasoning: true,
|
|
24
|
+
input: CURSOR_INPUT,
|
|
25
|
+
cost: ZERO_COST,
|
|
26
|
+
contextWindow: 1_000_000,
|
|
27
|
+
maxTokens: 64_000,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "claude-opus-4-7",
|
|
31
|
+
name: "Claude Opus 4.7 (via Cursor)",
|
|
32
|
+
reasoning: true,
|
|
33
|
+
input: CURSOR_INPUT,
|
|
34
|
+
cost: ZERO_COST,
|
|
35
|
+
contextWindow: 1_000_000,
|
|
36
|
+
maxTokens: 128_000,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "gpt-5.5",
|
|
40
|
+
name: "GPT-5.5 (via Cursor)",
|
|
41
|
+
reasoning: true,
|
|
42
|
+
input: CURSOR_INPUT,
|
|
43
|
+
cost: ZERO_COST,
|
|
44
|
+
contextWindow: 1_000_000,
|
|
45
|
+
maxTokens: 128_000,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "gemini-2.5-pro",
|
|
49
|
+
name: "Gemini 2.5 Pro (via Cursor)",
|
|
50
|
+
reasoning: true,
|
|
51
|
+
input: CURSOR_INPUT,
|
|
52
|
+
cost: ZERO_COST,
|
|
53
|
+
contextWindow: 1_000_000,
|
|
54
|
+
maxTokens: 65_536,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: "grok-4",
|
|
58
|
+
name: "Grok 4 (via Cursor)",
|
|
59
|
+
reasoning: true,
|
|
60
|
+
input: CURSOR_INPUT,
|
|
61
|
+
cost: ZERO_COST,
|
|
62
|
+
contextWindow: 256_000,
|
|
63
|
+
maxTokens: 64_000,
|
|
64
|
+
},
|
|
65
|
+
];
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
const VERSION_TIMEOUT_MS = 5_000;
|
|
4
|
+
const STATUS_TIMEOUT_MS = 10_000;
|
|
5
|
+
const CHECK_INTERVAL_MS = 30_000;
|
|
6
|
+
|
|
7
|
+
let cachedBinaryPresent: boolean | null = null;
|
|
8
|
+
let cachedAuthed: boolean | null = null;
|
|
9
|
+
let lastCheckMs = 0;
|
|
10
|
+
|
|
11
|
+
export function getCursorAgentCommand(platform: NodeJS.Platform = process.platform): string {
|
|
12
|
+
return platform === "win32" ? "cursor-agent.cmd" : "cursor-agent";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getCursorAgentCommandCandidates(platform: NodeJS.Platform = process.platform): string[] {
|
|
16
|
+
const command = getCursorAgentCommand(platform);
|
|
17
|
+
return platform === "win32" ? [command, "cursor-agent.exe", "cursor-agent"] : [command];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function buildCursorAgentSpawnInvocation(
|
|
21
|
+
command: string,
|
|
22
|
+
args: string[],
|
|
23
|
+
platform: NodeJS.Platform = process.platform,
|
|
24
|
+
): { command: string; args: string[] } {
|
|
25
|
+
if (platform === "win32") {
|
|
26
|
+
return { command: "cmd", args: ["/c", command, ...args] };
|
|
27
|
+
}
|
|
28
|
+
return { command, args };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function debugLog(...parts: unknown[]): void {
|
|
32
|
+
if (process.env.GSD_CURSOR_DEBUG) {
|
|
33
|
+
process.stderr.write(`[cursor-readiness] ${parts.map((part) => String(part)).join(" ")}\n`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function spawnCursorAgent(command: string, args: string[], timeout: number): Buffer {
|
|
38
|
+
const invocation = buildCursorAgentSpawnInvocation(command, args);
|
|
39
|
+
return execFileSync(invocation.command, invocation.args, { timeout, stdio: "pipe" });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function findWorkingCommand(): string | null {
|
|
43
|
+
for (const command of getCursorAgentCommandCandidates()) {
|
|
44
|
+
try {
|
|
45
|
+
spawnCursorAgent(command, ["--version"], VERSION_TIMEOUT_MS);
|
|
46
|
+
debugLog("version probe ok via", command);
|
|
47
|
+
return command;
|
|
48
|
+
} catch (error) {
|
|
49
|
+
debugLog("version probe failed for", command, (error as Error).message?.slice(0, 200));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function parseCursorAgentStatus(output: string): boolean | null {
|
|
56
|
+
const trimmed = output.trim();
|
|
57
|
+
if (!trimmed) return null;
|
|
58
|
+
|
|
59
|
+
if (trimmed.startsWith("{")) {
|
|
60
|
+
try {
|
|
61
|
+
const parsed = JSON.parse(trimmed) as Record<string, unknown>;
|
|
62
|
+
for (const key of ["authenticated", "loggedIn", "logged_in", "isAuthenticated"]) {
|
|
63
|
+
if (typeof parsed[key] === "boolean") return parsed[key];
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
// Fall through to text heuristics.
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const lower = trimmed.toLowerCase();
|
|
71
|
+
if (/not authenticated|not logged in|no credentials|logged out|unauthenticated/.test(lower)) return false;
|
|
72
|
+
if (/authenticated|logged in|signed in|cursor account|subscription/.test(lower)) return true;
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function isCursorAgentApiKeyValue(value: string | undefined): boolean {
|
|
77
|
+
const trimmed = value?.trim();
|
|
78
|
+
return Boolean(trimmed && trimmed !== "cli");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function hasCursorApiKey(): boolean {
|
|
82
|
+
return isCursorAgentApiKeyValue(process.env.CURSOR_API_KEY);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function probeAuth(command: string): boolean | null {
|
|
86
|
+
if (hasCursorApiKey()) return true;
|
|
87
|
+
|
|
88
|
+
for (const args of [["agent", "status", "--json"], ["status", "--json"], ["agent", "status"], ["status"]]) {
|
|
89
|
+
try {
|
|
90
|
+
const out = spawnCursorAgent(command, args, STATUS_TIMEOUT_MS).toString();
|
|
91
|
+
debugLog("status output", args.join(" "), out.slice(0, 200));
|
|
92
|
+
const parsed = parseCursorAgentStatus(out);
|
|
93
|
+
if (parsed !== null) return parsed;
|
|
94
|
+
} catch (error) {
|
|
95
|
+
debugLog("status failed", args.join(" "), (error as Error).message?.slice(0, 200));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function isCursorAgentReadyUncached(): boolean {
|
|
103
|
+
const command = findWorkingCommand();
|
|
104
|
+
if (!command) return false;
|
|
105
|
+
return probeAuth(command) === true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function refreshCache(): void {
|
|
109
|
+
const now = Date.now();
|
|
110
|
+
if (cachedBinaryPresent !== null && now - lastCheckMs < CHECK_INTERVAL_MS) return;
|
|
111
|
+
lastCheckMs = now;
|
|
112
|
+
|
|
113
|
+
const command = findWorkingCommand();
|
|
114
|
+
if (!command) {
|
|
115
|
+
cachedBinaryPresent = false;
|
|
116
|
+
cachedAuthed = false;
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
cachedBinaryPresent = true;
|
|
121
|
+
const authed = probeAuth(command);
|
|
122
|
+
if (authed === null) {
|
|
123
|
+
if (cachedAuthed === null) cachedAuthed = false;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
cachedAuthed = authed;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function isCursorAgentBinaryPresent(): boolean {
|
|
130
|
+
refreshCache();
|
|
131
|
+
return cachedBinaryPresent ?? false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function isCursorAgentReady(): boolean {
|
|
135
|
+
refreshCache();
|
|
136
|
+
return (cachedBinaryPresent ?? false) && (cachedAuthed ?? false);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function clearCursorAgentReadinessCache(): void {
|
|
140
|
+
cachedBinaryPresent = null;
|
|
141
|
+
cachedAuthed = null;
|
|
142
|
+
lastCheckMs = 0;
|
|
143
|
+
}
|