@opengsd/gsd-pi 1.2.0-dev.5457a158 → 1.2.0-dev.822c9439
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/headless-events.js +7 -5
- package/dist/mcp-server.js +2 -1
- package/dist/resource-loader.d.ts +9 -5
- package/dist/resource-loader.js +114 -6
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +5 -4
- package/dist/resources/extensions/async-jobs/async-bash-tool.js +30 -64
- package/dist/resources/extensions/async-jobs/await-tool.js +80 -12
- package/dist/resources/extensions/async-jobs/index.js +65 -0
- package/dist/resources/extensions/async-jobs/job-manager.js +12 -1
- package/dist/resources/extensions/bg-shell/bg-shell-command.js +6 -6
- package/dist/resources/extensions/bg-shell/bg-shell-tool.js +10 -7
- package/dist/resources/extensions/bg-shell/overlay.js +9 -6
- package/dist/resources/extensions/bg-shell/process-manager.js +54 -25
- package/dist/resources/extensions/bg-shell/readiness-detector.js +11 -0
- package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +209 -88
- package/dist/resources/extensions/browser-tools/engine/selection.js +73 -5
- package/dist/resources/extensions/browser-tools/index.js +69 -12
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +3 -2
- package/dist/resources/extensions/gsd/auto/custom-verify-retry-store.js +17 -2
- package/dist/resources/extensions/gsd/auto/detect-stuck.js +33 -13
- package/dist/resources/extensions/gsd/auto/dispatch-history.js +105 -0
- package/dist/resources/extensions/gsd/auto/dispatch-key.js +37 -0
- package/dist/resources/extensions/gsd/auto/loop.js +4 -1
- package/dist/resources/extensions/gsd/auto/orchestrator.js +89 -54
- package/dist/resources/extensions/gsd/auto/phases.js +49 -6
- package/dist/resources/extensions/gsd/auto/session.js +3 -0
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +11 -34
- package/dist/resources/extensions/gsd/auto-dispatch.js +50 -58
- package/dist/resources/extensions/gsd/auto-model-selection.js +36 -13
- package/dist/resources/extensions/gsd/auto-post-unit.js +30 -12
- package/dist/resources/extensions/gsd/auto-prompts.js +78 -19
- package/dist/resources/extensions/gsd/auto-start.js +12 -12
- package/dist/resources/extensions/gsd/auto-unit-closeout.js +45 -21
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +5 -4
- package/dist/resources/extensions/gsd/auto-verification.js +23 -30
- package/dist/resources/extensions/gsd/auto-worktree.js +14 -1
- package/dist/resources/extensions/gsd/auto.js +37 -1
- package/dist/resources/extensions/gsd/blocked-models.js +28 -0
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +26 -6
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +23 -6
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -2
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +19 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +172 -59
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +302 -80
- package/dist/resources/extensions/gsd/browser-daemon-auto-prep.js +83 -0
- package/dist/resources/extensions/gsd/browser-evidence.js +8 -2
- package/dist/resources/extensions/gsd/closeout-wizard.js +92 -0
- package/dist/resources/extensions/gsd/commands/context.js +16 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +46 -3
- package/dist/resources/extensions/gsd/consent-question.js +353 -0
- package/dist/resources/extensions/gsd/consent-verdict.js +63 -0
- package/dist/resources/extensions/gsd/constants.js +0 -2
- package/dist/resources/extensions/gsd/crash-recovery.js +8 -3
- package/dist/resources/extensions/gsd/db/queries.js +26 -0
- package/dist/resources/extensions/gsd/dispatch-guard.js +10 -35
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +5 -5
- package/dist/resources/extensions/gsd/doctor-git-checks.js +2 -18
- package/dist/resources/extensions/gsd/engine-hook-contract.js +70 -0
- package/dist/resources/extensions/gsd/exec-sandbox.js +30 -10
- package/dist/resources/extensions/gsd/files.js +33 -19
- package/dist/resources/extensions/gsd/gsd-command-home.js +22 -12
- package/dist/resources/extensions/gsd/gsd-db.js +2 -1
- package/dist/resources/extensions/gsd/guidance.js +60 -0
- package/dist/resources/extensions/gsd/guided-flow.js +6 -3
- package/dist/resources/extensions/gsd/markdown-renderer.js +10 -0
- package/dist/resources/extensions/gsd/mcp-filter.js +2 -19
- package/dist/resources/extensions/gsd/milestone-closeout.js +85 -24
- package/dist/resources/extensions/gsd/milestone-planning-persistence.js +2 -2
- package/dist/resources/extensions/gsd/milestone-reopen-events.js +3 -5
- package/dist/resources/extensions/gsd/parsers-legacy.js +16 -4
- package/dist/resources/extensions/gsd/preferences-models.js +2 -2
- package/dist/resources/extensions/gsd/projection-flush.js +7 -0
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +3 -3
- package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
- package/dist/resources/extensions/gsd/prompts/run-uat.md +7 -5
- package/dist/resources/extensions/gsd/prompts/system.md +5 -2
- package/dist/resources/extensions/gsd/prompts/triage-captures.md +1 -1
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +1 -1
- package/dist/resources/extensions/gsd/reactive-graph.js +8 -1
- package/dist/resources/extensions/gsd/roadmap-slices.js +25 -3
- package/dist/resources/extensions/gsd/safety/destructive-confirmation.js +108 -0
- package/dist/resources/extensions/gsd/session-lock.js +1 -1
- package/dist/resources/extensions/gsd/state.js +5 -0
- package/dist/resources/extensions/gsd/tool-contract.js +14 -3
- package/dist/resources/extensions/gsd/tool-presentation-plan.js +4 -4
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -2
- package/dist/resources/extensions/gsd/tools/complete-slice.js +22 -12
- package/dist/resources/extensions/gsd/tools/complete-task.js +3 -2
- package/dist/resources/extensions/gsd/tools/exec-tool.js +5 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +2 -2
- package/dist/resources/extensions/gsd/tools/plan-task.js +2 -2
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +2 -2
- package/dist/resources/extensions/gsd/tools/reopen-milestone.js +2 -2
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +2 -2
- package/dist/resources/extensions/gsd/tools/reopen-task.js +2 -2
- package/dist/resources/extensions/gsd/tools/replan-slice.js +2 -2
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +67 -2
- package/dist/resources/extensions/gsd/uat-policy.js +42 -16
- package/dist/resources/extensions/gsd/unit-context-composer.js +65 -0
- package/dist/resources/extensions/gsd/unit-registry.js +7 -20
- package/dist/resources/extensions/gsd/verdict-parser.js +1 -1
- package/dist/resources/extensions/gsd/verification-verdict.js +2 -1
- package/dist/resources/extensions/gsd/web-app-uat.js +45 -8
- package/dist/resources/extensions/gsd/workflow-event-ledger.js +91 -0
- package/dist/resources/extensions/gsd/workflow-event-vocabulary.js +46 -0
- package/dist/resources/extensions/gsd/workflow-events.js +6 -18
- package/dist/resources/extensions/gsd/workflow-reconcile.js +21 -56
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +3 -2
- package/dist/resources/extensions/gsd/worktree-manager.js +7 -1
- package/dist/resources/extensions/gsd/worktree.js +8 -1
- package/dist/resources/extensions/search-the-web/native-search.js +5 -3
- package/dist/resources/extensions/shared/browser-contract.js +59 -0
- package/dist/resources/extensions/shared/gsd-browser-cli.js +116 -6
- package/dist/resources/shared/gsd-browser-path-sync.js +214 -0
- package/dist/resources/shared/package-manager-detection.js +1 -1
- package/dist/resources/shared/package.json +3 -0
- package/dist/resources/skills/create-skill/SKILL.md +3 -0
- package/dist/resources/skills/create-skill/references/executable-code.md +1 -1
- package/dist/resources/skills/create-skill/references/skill-structure.md +1 -0
- package/dist/resources/skills/create-skill/workflows/add-reference.md +8 -3
- package/dist/resources/skills/create-skill/workflows/add-script.md +4 -2
- package/dist/resources/skills/create-skill/workflows/add-template.md +3 -1
- package/dist/resources/skills/create-skill/workflows/add-workflow.md +8 -3
- package/dist/resources/skills/create-skill/workflows/upgrade-to-router.md +10 -5
- package/dist/resources/skills/create-skill/workflows/verify-skill.md +9 -4
- package/dist/resources/skills/spike-wrap-up/SKILL.md +9 -9
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/update-check.d.ts +2 -0
- package/dist/update-check.js +24 -1
- package/dist/update-cmd.js +20 -3
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
- 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/api/update/route.js.nft.json +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 +11 -11
- package/dist/web/standalone/.next/server/chunks/8357.js +2 -2
- 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/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/{796.cf859a427a2cb2ac.js → 796.e0bdc932325d7e03.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/{webpack-fbea77b5f9953368.js → webpack-f0285ce91d4ec9ef.js} +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/dist/web/standalone/package.json +1 -1
- package/package.json +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/dist/rpc.d.ts +1 -0
- package/packages/contracts/dist/rpc.d.ts.map +1 -1
- package/packages/contracts/dist/rpc.js.map +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +5 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +5 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +7 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js +8 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js +11 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js +4 -4
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js +3 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/cli.js +10 -5
- package/packages/mcp-server/dist/cli.js.map +1 -1
- package/packages/mcp-server/dist/moonshot-tool-schema.d.ts +29 -0
- package/packages/mcp-server/dist/moonshot-tool-schema.d.ts.map +1 -0
- package/packages/mcp-server/dist/moonshot-tool-schema.js +50 -0
- package/packages/mcp-server/dist/moonshot-tool-schema.js.map +1 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +4 -0
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +18 -18
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +99 -38
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +5 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/harness/env/nodejs.d.ts +1 -0
- package/packages/pi-agent-core/dist/harness/env/nodejs.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/harness/env/nodejs.js +34 -3
- package/packages/pi-agent-core/dist/harness/env/nodejs.js.map +1 -1
- package/packages/pi-agent-core/dist/index.d.ts +1 -0
- package/packages/pi-agent-core/dist/index.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/index.js +3 -0
- package/packages/pi-agent-core/dist/index.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/README.md +1 -0
- package/packages/pi-ai/dist/image-models.generated.d.ts +2 -2
- package/packages/pi-ai/dist/image-models.generated.js +6 -6
- package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
- package/packages/pi-ai/dist/index.d.ts +2 -0
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +2 -0
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +239 -153
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +256 -145
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +12 -7
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-shared.d.ts +5 -0
- package/packages/pi-ai/dist/providers/google-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-shared.js +12 -3
- package/packages/pi-ai/dist/providers/google-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +7 -3
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/utils/moonshot-tool-schema.d.ts +9 -0
- package/packages/pi-ai/dist/utils/moonshot-tool-schema.d.ts.map +1 -0
- package/packages/pi-ai/dist/utils/moonshot-tool-schema.js +34 -0
- package/packages/pi-ai/dist/utils/moonshot-tool-schema.js.map +1 -0
- package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js +6 -2
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
- package/packages/pi-ai/package.json +3 -2
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +2 -2
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +19 -13
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/provider-readiness.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/provider-readiness.js +13 -6
- package/packages/pi-coding-agent/dist/core/provider-readiness.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +11 -0
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js +53 -11
- package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/utils/shell.d.ts +28 -2
- package/packages/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/utils/shell.js +56 -10
- package/packages/pi-coding-agent/dist/utils/shell.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +9 -0
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/GSD-WORKFLOW.md +5 -4
- package/src/resources/extensions/async-jobs/async-bash-cancel.test.ts +360 -0
- package/src/resources/extensions/async-jobs/async-bash-tool.ts +33 -56
- package/src/resources/extensions/async-jobs/await-tool.test.ts +139 -0
- package/src/resources/extensions/async-jobs/await-tool.ts +82 -12
- package/src/resources/extensions/async-jobs/index.ts +79 -0
- package/src/resources/extensions/async-jobs/job-manager.ts +21 -1
- package/src/resources/extensions/bg-shell/bg-shell-command.ts +6 -6
- package/src/resources/extensions/bg-shell/bg-shell-tool.ts +10 -6
- package/src/resources/extensions/bg-shell/overlay.ts +9 -5
- package/src/resources/extensions/bg-shell/process-manager.ts +50 -25
- package/src/resources/extensions/bg-shell/readiness-detector.ts +12 -0
- package/src/resources/extensions/bg-shell/tests/lifecycle-and-utilities.test.ts +48 -1
- package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +265 -98
- package/src/resources/extensions/browser-tools/engine/selection.ts +90 -4
- package/src/resources/extensions/browser-tools/index.ts +71 -13
- package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +83 -13
- package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +40 -1
- package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +136 -0
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +3 -2
- package/src/resources/extensions/gsd/auto/custom-verify-retry-store.ts +21 -3
- package/src/resources/extensions/gsd/auto/detect-stuck.ts +32 -9
- package/src/resources/extensions/gsd/auto/dispatch-history.ts +152 -0
- package/src/resources/extensions/gsd/auto/dispatch-key.ts +39 -0
- package/src/resources/extensions/gsd/auto/loop.ts +4 -1
- package/src/resources/extensions/gsd/auto/orchestrator.ts +98 -56
- package/src/resources/extensions/gsd/auto/phases.ts +65 -26
- package/src/resources/extensions/gsd/auto/session.ts +3 -0
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +18 -48
- package/src/resources/extensions/gsd/auto-dispatch.ts +48 -61
- package/src/resources/extensions/gsd/auto-model-selection.ts +41 -12
- package/src/resources/extensions/gsd/auto-post-unit.ts +33 -12
- package/src/resources/extensions/gsd/auto-prompts.ts +115 -35
- package/src/resources/extensions/gsd/auto-start.ts +12 -14
- package/src/resources/extensions/gsd/auto-unit-closeout.ts +83 -28
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +4 -4
- package/src/resources/extensions/gsd/auto-verification.ts +26 -28
- package/src/resources/extensions/gsd/auto-worktree.ts +14 -1
- package/src/resources/extensions/gsd/auto.ts +44 -1
- package/src/resources/extensions/gsd/blocked-models.ts +49 -0
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +34 -5
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +23 -6
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -2
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +24 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +211 -59
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +350 -86
- package/src/resources/extensions/gsd/browser-daemon-auto-prep.ts +108 -0
- package/src/resources/extensions/gsd/browser-evidence.ts +18 -2
- package/src/resources/extensions/gsd/closeout-wizard.ts +102 -0
- package/src/resources/extensions/gsd/commands/context.ts +16 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +46 -3
- package/src/resources/extensions/gsd/consent-question.ts +431 -0
- package/src/resources/extensions/gsd/consent-verdict.ts +86 -0
- package/src/resources/extensions/gsd/constants.ts +0 -3
- package/src/resources/extensions/gsd/crash-recovery.ts +10 -2
- package/src/resources/extensions/gsd/db/queries.ts +37 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +8 -31
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +5 -4
- package/src/resources/extensions/gsd/doctor-git-checks.ts +2 -19
- package/src/resources/extensions/gsd/engine-hook-contract.ts +79 -0
- package/src/resources/extensions/gsd/exec-sandbox.ts +49 -9
- package/src/resources/extensions/gsd/files.ts +33 -12
- package/src/resources/extensions/gsd/gsd-command-home.ts +13 -3
- package/src/resources/extensions/gsd/gsd-db.ts +4 -3
- package/src/resources/extensions/gsd/guidance.ts +78 -0
- package/src/resources/extensions/gsd/guided-flow.ts +21 -26
- package/src/resources/extensions/gsd/markdown-renderer.ts +11 -0
- package/src/resources/extensions/gsd/mcp-filter.ts +2 -23
- package/src/resources/extensions/gsd/milestone-closeout.ts +109 -24
- package/src/resources/extensions/gsd/milestone-planning-persistence.ts +2 -2
- package/src/resources/extensions/gsd/milestone-reopen-events.ts +3 -6
- package/src/resources/extensions/gsd/parsers-legacy.ts +16 -4
- package/src/resources/extensions/gsd/preferences-models.ts +2 -1
- package/src/resources/extensions/gsd/projection-flush.ts +20 -0
- package/src/resources/extensions/gsd/prompts/complete-slice.md +3 -3
- package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/quick-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/replan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +7 -5
- package/src/resources/extensions/gsd/prompts/system.md +5 -2
- package/src/resources/extensions/gsd/prompts/triage-captures.md +1 -1
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +1 -1
- package/src/resources/extensions/gsd/reactive-graph.ts +11 -1
- package/src/resources/extensions/gsd/roadmap-slices.ts +28 -3
- package/src/resources/extensions/gsd/safety/destructive-confirmation.ts +134 -0
- package/src/resources/extensions/gsd/session-lock.ts +1 -1
- package/src/resources/extensions/gsd/state.ts +5 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +97 -1
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +198 -26
- package/src/resources/extensions/gsd/tests/auto-remote-session-lock-cleanup.test.ts +65 -3
- package/src/resources/extensions/gsd/tests/blocked-models.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/browser-automation-contract-fixture.ts +39 -0
- package/src/resources/extensions/gsd/tests/browser-contract.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/browser-daemon-auto-prep.test.ts +144 -0
- package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/consent-question.test.ts +351 -0
- package/src/resources/extensions/gsd/tests/custom-verify-retry-store.test.ts +67 -0
- package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +10 -10
- package/src/resources/extensions/gsd/tests/destructive-confirmation.test.ts +303 -0
- package/src/resources/extensions/gsd/tests/discuss-routing-fixes.test.ts +12 -2
- package/src/resources/extensions/gsd/tests/dispatch-history.test.ts +273 -0
- package/src/resources/extensions/gsd/tests/dispatch-run-uat-browser-tools.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/doctor-git-checks-terminal.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/dynamic-bash-no-cap.test.ts +132 -0
- package/src/resources/extensions/gsd/tests/engine-hook-contract.test.ts +148 -0
- package/src/resources/extensions/gsd/tests/exec-graceful-kill.test.ts +193 -0
- package/src/resources/extensions/gsd/tests/exec-tool.test.ts +29 -1
- package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +35 -1
- package/src/resources/extensions/gsd/tests/gsd-command-home.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/guidance.test.ts +23 -0
- package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +2 -6
- package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +7 -11
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +20 -58
- package/src/resources/extensions/gsd/tests/integration/gsd-integration-fixture.ts +80 -0
- package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +199 -0
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +95 -4
- package/src/resources/extensions/gsd/tests/model-unittype-mapping.test.ts +32 -1
- package/src/resources/extensions/gsd/tests/oauth-api-model-routing.test.ts +167 -0
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +138 -0
- package/src/resources/extensions/gsd/tests/phases-terminal-complete-idempotent.test.ts +242 -0
- package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +63 -2
- package/src/resources/extensions/gsd/tests/prompt-db.test.ts +124 -6
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +19 -1
- package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +76 -0
- package/src/resources/extensions/gsd/tests/tool-unavailable-retry.test.ts +33 -0
- package/src/resources/extensions/gsd/tests/transport-gate-double-complete.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/uat-policy.test.ts +112 -29
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/uok-audit-unified.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/verification-verdict.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/web-app-uat.test.ts +44 -1
- package/src/resources/extensions/gsd/tests/workflow-events.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/workflow-reconcile.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +273 -38
- package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/worktree.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/write-gate-seam.test.ts +358 -0
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +67 -1
- package/src/resources/extensions/gsd/tool-contract.ts +38 -3
- package/src/resources/extensions/gsd/tool-presentation-plan.ts +4 -4
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -2
- package/src/resources/extensions/gsd/tools/complete-slice.ts +22 -12
- package/src/resources/extensions/gsd/tools/complete-task.ts +3 -2
- package/src/resources/extensions/gsd/tools/exec-tool.ts +4 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +2 -2
- package/src/resources/extensions/gsd/tools/plan-task.ts +2 -2
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +2 -2
- package/src/resources/extensions/gsd/tools/reopen-milestone.ts +2 -2
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +2 -2
- package/src/resources/extensions/gsd/tools/reopen-task.ts +2 -2
- package/src/resources/extensions/gsd/tools/replan-slice.ts +2 -2
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +81 -2
- package/src/resources/extensions/gsd/uat-policy.ts +62 -16
- package/src/resources/extensions/gsd/unit-context-composer.ts +99 -0
- package/src/resources/extensions/gsd/unit-registry.ts +7 -20
- package/src/resources/extensions/gsd/verdict-parser.ts +1 -1
- package/src/resources/extensions/gsd/verification-verdict.ts +4 -2
- package/src/resources/extensions/gsd/web-app-uat.ts +51 -8
- package/src/resources/extensions/gsd/workflow-event-ledger.ts +131 -0
- package/src/resources/extensions/gsd/workflow-event-vocabulary.ts +59 -0
- package/src/resources/extensions/gsd/workflow-events.ts +12 -20
- package/src/resources/extensions/gsd/workflow-reconcile.ts +29 -62
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +3 -8
- package/src/resources/extensions/gsd/worktree-manager.ts +6 -1
- package/src/resources/extensions/gsd/worktree.ts +7 -1
- package/src/resources/extensions/search-the-web/native-search.ts +5 -3
- package/src/resources/extensions/shared/browser-contract.ts +66 -0
- package/src/resources/extensions/shared/gsd-browser-cli.ts +141 -6
- package/src/resources/shared/gsd-browser-path-sync.ts +273 -0
- package/src/resources/shared/package-manager-detection.ts +1 -1
- package/src/resources/shared/package.json +3 -0
- package/src/resources/skills/create-skill/SKILL.md +3 -0
- package/src/resources/skills/create-skill/references/executable-code.md +1 -1
- package/src/resources/skills/create-skill/references/skill-structure.md +1 -0
- package/src/resources/skills/create-skill/workflows/add-reference.md +8 -3
- package/src/resources/skills/create-skill/workflows/add-script.md +4 -2
- package/src/resources/skills/create-skill/workflows/add-template.md +3 -1
- package/src/resources/skills/create-skill/workflows/add-workflow.md +8 -3
- package/src/resources/skills/create-skill/workflows/upgrade-to-router.md +10 -5
- package/src/resources/skills/create-skill/workflows/verify-skill.md +9 -4
- package/src/resources/skills/spike-wrap-up/SKILL.md +9 -9
- package/dist/resources/extensions/gsd/user-input-boundary.js +0 -218
- package/dist/resources/skills/gsd-browser/SKILL.md +0 -41
- package/src/resources/extensions/gsd/tests/user-input-boundary.test.ts +0 -173
- package/src/resources/extensions/gsd/user-input-boundary.ts +0 -216
- package/src/resources/skills/gsd-browser/SKILL.md +0 -41
- /package/dist/web/standalone/.next/static/{2p9Rv9pQflAxCBbGVI2vb → yWwBo-w09Y_W-nmeeWFRp}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{2p9Rv9pQflAxCBbGVI2vb → yWwBo-w09Y_W-nmeeWFRp}/_ssgManifest.js +0 -0
|
@@ -33,7 +33,7 @@ export function createAwaitTool(getManager: () => AsyncJobManager): ToolDefiniti
|
|
|
33
33
|
description:
|
|
34
34
|
"Wait for background jobs to complete. Provide specific job IDs or omit to wait for the next job that finishes. Returns results of completed jobs.",
|
|
35
35
|
parameters: schema,
|
|
36
|
-
async execute(_toolCallId, params,
|
|
36
|
+
async execute(_toolCallId, params, signal, _onUpdate, _ctx) {
|
|
37
37
|
const manager = getManager();
|
|
38
38
|
const { jobs: jobIds, timeout } = params;
|
|
39
39
|
const timeoutMs = ((timeout ?? DEFAULT_TIMEOUT_SECONDS) * 1000);
|
|
@@ -66,41 +66,79 @@ export function createAwaitTool(getManager: () => AsyncJobManager): ToolDefiniti
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
//
|
|
69
|
+
// If all watched jobs are already done, suppress follow-up and return immediately.
|
|
70
70
|
// suppressFollowUp() cancels the pending delivery timer (if any), which
|
|
71
71
|
// handles both the within-turn case (job completes while we await) and
|
|
72
72
|
// the cross-turn case (job already completed before await_job was called).
|
|
73
73
|
// Previously this only set j.awaited = true, which missed the cross-turn
|
|
74
74
|
// case because the queueMicrotask had already fired (#3787).
|
|
75
|
-
for (const j of watched) manager.suppressFollowUp(j.id);
|
|
76
|
-
|
|
77
|
-
// If all watched jobs are already done, return immediately
|
|
78
75
|
const running = watched.filter((j) => j.status === "running");
|
|
79
76
|
if (running.length === 0) {
|
|
80
|
-
const
|
|
81
|
-
return { content: [{ type: "text", text:
|
|
77
|
+
for (const j of watched) manager.suppressFollowUp(j.id);
|
|
78
|
+
return { content: [{ type: "text", text: renderCompleted(watched) }], details: undefined };
|
|
82
79
|
}
|
|
83
80
|
|
|
84
|
-
// Wait for at least one to complete, or
|
|
81
|
+
// Wait for at least one to complete, timeout, or abort signal
|
|
85
82
|
const TIMEOUT_SENTINEL = Symbol("timeout");
|
|
83
|
+
const ABORT_SENTINEL = Symbol("abort");
|
|
84
|
+
|
|
85
|
+
// The race timer and abort listener are explicitly torn down once the race
|
|
86
|
+
// settles (below) so a completion- or timeout-won race never leaks a pending
|
|
87
|
+
// timer or a lingering abort listener — the listener holds a closure over the
|
|
88
|
+
// race resolver, so { once: true } alone (which only detaches on fire) is not
|
|
89
|
+
// enough when ESC never happens.
|
|
90
|
+
let raceTimer: ReturnType<typeof setTimeout> | undefined;
|
|
91
|
+
let abortListener: (() => void) | undefined;
|
|
86
92
|
const timeoutPromise = new Promise<typeof TIMEOUT_SENTINEL>((resolve) => {
|
|
87
|
-
|
|
93
|
+
raceTimer = setTimeout(() => resolve(TIMEOUT_SENTINEL), timeoutMs);
|
|
88
94
|
// Allow the process to exit even if the timer is pending
|
|
89
|
-
if (typeof
|
|
95
|
+
if (typeof raceTimer === "object" && "unref" in raceTimer) raceTimer.unref();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const abortPromise = new Promise<typeof ABORT_SENTINEL>((resolve) => {
|
|
99
|
+
if (!signal || signal.aborted) {
|
|
100
|
+
resolve(ABORT_SENTINEL);
|
|
101
|
+
} else {
|
|
102
|
+
abortListener = () => resolve(ABORT_SENTINEL);
|
|
103
|
+
signal.addEventListener("abort", abortListener, { once: true });
|
|
104
|
+
}
|
|
90
105
|
});
|
|
91
106
|
|
|
92
107
|
const raceResult = await Promise.race([
|
|
93
108
|
Promise.race(running.map((j) => j.promise)).then(() => "completed" as const),
|
|
94
109
|
timeoutPromise,
|
|
110
|
+
abortPromise,
|
|
95
111
|
]);
|
|
96
112
|
|
|
113
|
+
// Tear down race resources now that a winner is decided.
|
|
114
|
+
if (raceTimer) clearTimeout(raceTimer);
|
|
115
|
+
if (abortListener && signal) signal.removeEventListener("abort", abortListener);
|
|
116
|
+
|
|
117
|
+
const aborted = raceResult === ABORT_SENTINEL;
|
|
97
118
|
const timedOut = raceResult === TIMEOUT_SENTINEL;
|
|
98
119
|
|
|
99
120
|
// Collect all completed results (more may have finished while waiting)
|
|
100
121
|
const completed = watched.filter((j) => j.status !== "running");
|
|
101
|
-
|
|
102
122
|
const stillRunning = watched.filter((j) => j.status === "running");
|
|
103
|
-
|
|
123
|
+
|
|
124
|
+
// Suppress follow-up ONLY for completed jobs — leave stillRunning unsuppressed
|
|
125
|
+
// so deliverResult/onJobComplete can resurface their results later.
|
|
126
|
+
for (const j of completed) manager.suppressFollowUp(j.id);
|
|
127
|
+
|
|
128
|
+
if (aborted) {
|
|
129
|
+
// ESC ended the wait, not the jobs: still-running jobs keep going and
|
|
130
|
+
// resurface via onJobComplete (they were deliberately not suppressed above).
|
|
131
|
+
const runningDesc = stillRunning.map((j) => `${j.id} (${j.label})`).join(", ");
|
|
132
|
+
const interrupt = stillRunning.length > 0
|
|
133
|
+
? `Wait interrupted. Still running: ${runningDesc} — results will surface when complete.`
|
|
134
|
+
: "Wait interrupted.";
|
|
135
|
+
const text = completed.length > 0
|
|
136
|
+
? `${renderCompleted(completed)}\n\n${interrupt}`
|
|
137
|
+
: interrupt;
|
|
138
|
+
return { content: [{ type: "text", text }], details: undefined };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let result = renderCompleted(completed);
|
|
104
142
|
if (stillRunning.length > 0) {
|
|
105
143
|
result += `\n\n**Still running:** ${stillRunning.map((j) => `${j.id} (${j.label})`).join(", ")}`;
|
|
106
144
|
}
|
|
@@ -115,6 +153,38 @@ export function createAwaitTool(getManager: () => AsyncJobManager): ToolDefiniti
|
|
|
115
153
|
};
|
|
116
154
|
}
|
|
117
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Render completed jobs for the await_job result, de-duplicating against
|
|
158
|
+
* follow-ups that have already been delivered to context.
|
|
159
|
+
*
|
|
160
|
+
* A job's follow-up fires ~immediately (setTimeout(0)) once it settles, so when
|
|
161
|
+
* await_job runs in a LATER turn the result is already in context. Reprinting it
|
|
162
|
+
* inline produces the same output twice. Jobs already `delivered` are therefore
|
|
163
|
+
* acknowledged on a single line instead of having their full output reprinted;
|
|
164
|
+
* not-yet-delivered jobs (the within-turn case, where suppressFollowUp won the
|
|
165
|
+
* race) are rendered in full as before.
|
|
166
|
+
*/
|
|
167
|
+
function renderCompleted(jobs: Job[]): string {
|
|
168
|
+
if (jobs.length === 0) return "No completed jobs.";
|
|
169
|
+
|
|
170
|
+
const fresh = jobs.filter((j) => !j.delivered);
|
|
171
|
+
const alreadyDelivered = jobs.filter((j) => j.delivered);
|
|
172
|
+
|
|
173
|
+
const sections: string[] = [];
|
|
174
|
+
if (fresh.length > 0) sections.push(formatResults(fresh));
|
|
175
|
+
if (alreadyDelivered.length > 0) {
|
|
176
|
+
const names = alreadyDelivered
|
|
177
|
+
.map((j) => `${j.id} (${j.label})`)
|
|
178
|
+
.join(", ");
|
|
179
|
+
const sentence =
|
|
180
|
+
alreadyDelivered.length === 1
|
|
181
|
+
? `This job already finished and its result was shown above when it completed, so there is nothing new to report: ${names}.`
|
|
182
|
+
: `These jobs already finished and their results were shown above when they completed, so there is nothing new to report: ${names}.`;
|
|
183
|
+
sections.push(sentence);
|
|
184
|
+
}
|
|
185
|
+
return sections.join("\n\n");
|
|
186
|
+
}
|
|
187
|
+
|
|
118
188
|
function formatResults(jobs: Job[]): string {
|
|
119
189
|
if (jobs.length === 0) return "No completed jobs.";
|
|
120
190
|
|
|
@@ -117,10 +117,89 @@ export default function AsyncJobs(pi: ExtensionAPI) {
|
|
|
117
117
|
return;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
const ctx = _ctx;
|
|
120
121
|
const running = manager.getRunningJobs();
|
|
121
122
|
const recent = manager.getRecentJobs(10);
|
|
122
123
|
const completed = recent.filter((j) => j.status !== "running");
|
|
123
124
|
|
|
125
|
+
// Interactive kill-picker when there are running jobs and a UI is available
|
|
126
|
+
if (running.length > 0 && ctx.hasUI) {
|
|
127
|
+
// Kill-picker loop: each iteration shows live running jobs.
|
|
128
|
+
// Step 1 picks a job (neutral label — selecting does NOT cancel yet);
|
|
129
|
+
// step 2 confirms before the destructive cancel. Labels deliberately
|
|
130
|
+
// omit a live elapsed time: ctx.ui.select renders the option strings
|
|
131
|
+
// once and never refreshes them, so a "(24s)" baked into the label
|
|
132
|
+
// would freeze and mislead. Accurate elapsed times are shown in the
|
|
133
|
+
// post-picker summary (rebuilt fresh) instead.
|
|
134
|
+
const DONE = "Close";
|
|
135
|
+
while (true) {
|
|
136
|
+
const liveJobs = manager.getRunningJobs();
|
|
137
|
+
if (liveJobs.length === 0) break;
|
|
138
|
+
|
|
139
|
+
// Map display label -> job id so we never parse ids back out of
|
|
140
|
+
// free-form label text.
|
|
141
|
+
const labelToId = new Map<string, string>();
|
|
142
|
+
for (const j of liveJobs) {
|
|
143
|
+
labelToId.set(`${j.id} — ${j.label} (running)`, j.id);
|
|
144
|
+
}
|
|
145
|
+
const options = [...labelToId.keys(), DONE];
|
|
146
|
+
|
|
147
|
+
const choice = await ctx.ui.select(
|
|
148
|
+
"Background jobs — pick one to cancel, Escape to close",
|
|
149
|
+
options,
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// ESC returns undefined; headless may return string[]; DONE closes
|
|
153
|
+
if (!choice || typeof choice !== "string" || choice === DONE) break;
|
|
154
|
+
|
|
155
|
+
const id = labelToId.get(choice);
|
|
156
|
+
if (!id) break;
|
|
157
|
+
|
|
158
|
+
const job = liveJobs.find((j) => j.id === id);
|
|
159
|
+
const confirmed = await ctx.ui.confirm(
|
|
160
|
+
"Cancel background job?",
|
|
161
|
+
`This will stop ${id}${job ? ` — ${job.label}` : ""}. Other jobs keep running.`,
|
|
162
|
+
);
|
|
163
|
+
if (!confirmed) continue;
|
|
164
|
+
|
|
165
|
+
const r = manager.cancel(id);
|
|
166
|
+
ctx.ui.notify(
|
|
167
|
+
r === "cancelled" ? `Job ${id} cancelled.` : `Job ${id}: ${r}`,
|
|
168
|
+
r === "cancelled" ? "success" : "warning",
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// After picker, send a summary of any still-running jobs
|
|
173
|
+
const stillRunning = manager.getRunningJobs();
|
|
174
|
+
const summaryLines: string[] = ["## Background Jobs"];
|
|
175
|
+
if (stillRunning.length > 0) {
|
|
176
|
+
summaryLines.push("", "### Running");
|
|
177
|
+
for (const job of stillRunning) {
|
|
178
|
+
const elapsed = ((Date.now() - job.startTime) / 1000).toFixed(0);
|
|
179
|
+
summaryLines.push(`- **${job.id}** — ${job.label} (${elapsed}s)`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const recentCompleted = manager.getRecentJobs(10).filter((j) => j.status !== "running");
|
|
183
|
+
if (recentCompleted.length > 0) {
|
|
184
|
+
summaryLines.push("", "### Recent");
|
|
185
|
+
for (const job of recentCompleted) {
|
|
186
|
+
const elapsed = ((Date.now() - job.startTime) / 1000).toFixed(1);
|
|
187
|
+
summaryLines.push(`- **${job.id}** — ${job.label} (${job.status}, ${elapsed}s)`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (stillRunning.length === 0 && recentCompleted.length === 0) {
|
|
191
|
+
summaryLines.push("", "No background jobs.");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
pi.sendMessage({
|
|
195
|
+
customType: "async_jobs_list",
|
|
196
|
+
content: summaryLines.join("\n"),
|
|
197
|
+
display: true,
|
|
198
|
+
});
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Text-only display: headless/RPC mode, or no running jobs
|
|
124
203
|
const lines: string[] = ["## Background Jobs"];
|
|
125
204
|
|
|
126
205
|
if (running.length === 0 && completed.length === 0) {
|
|
@@ -24,6 +24,14 @@ export interface Job {
|
|
|
24
24
|
errorText?: string;
|
|
25
25
|
/** Set by await_job when results are consumed. Suppresses follow-up delivery. */
|
|
26
26
|
awaited?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Set true once the follow-up notification has actually been delivered via
|
|
29
|
+
* onJobComplete. The delivery timer fires ~immediately (setTimeout(0)) after a
|
|
30
|
+
* job settles, so by a later LLM turn the follow-up is already in context.
|
|
31
|
+
* await_job reads this to avoid rendering the same result inline a second time
|
|
32
|
+
* (duplicate-in-context bug): a delivered job is acknowledged tersely, not reprinted.
|
|
33
|
+
*/
|
|
34
|
+
delivered?: boolean;
|
|
27
35
|
/**
|
|
28
36
|
* Handle for the pending follow-up delivery timer (set by deliverResult).
|
|
29
37
|
* Stored so suppressFollowUp() can cancel it before the notification fires,
|
|
@@ -102,6 +110,15 @@ export class AsyncJobManager {
|
|
|
102
110
|
|
|
103
111
|
job.promise = runFn(abortController.signal)
|
|
104
112
|
.then((resultText) => {
|
|
113
|
+
if (job.status === "cancelled") {
|
|
114
|
+
// Already cancelled by cancel(). The runFn resolves (not rejects) even
|
|
115
|
+
// when aborted — async_bash's safeResolve returns "Command aborted"
|
|
116
|
+
// rather than throwing — so without this guard the status would be
|
|
117
|
+
// clobbered back to "completed", mislabeling a user-cancelled job.
|
|
118
|
+
// Mirrors the symmetric guard in the .catch branch below.
|
|
119
|
+
this.scheduleEviction(id);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
105
122
|
job.status = "completed";
|
|
106
123
|
job.resultText = resultText;
|
|
107
124
|
this.scheduleEviction(id);
|
|
@@ -200,7 +217,10 @@ export class AsyncJobManager {
|
|
|
200
217
|
const cb = this.onJobComplete;
|
|
201
218
|
job.deliveryTimer = setTimeout(() => {
|
|
202
219
|
job.deliveryTimer = undefined;
|
|
203
|
-
if (!job.awaited)
|
|
220
|
+
if (!job.awaited) {
|
|
221
|
+
job.delivered = true;
|
|
222
|
+
cb(job);
|
|
223
|
+
}
|
|
204
224
|
}, 0);
|
|
205
225
|
// Allow process to exit even if timer is pending
|
|
206
226
|
if (typeof job.deliveryTimer === "object" && "unref" in job.deliveryTimer) {
|
|
@@ -8,7 +8,7 @@ import { shortcutDesc } from "../shared/terminal.js";
|
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
10
|
processes,
|
|
11
|
-
|
|
11
|
+
terminateProcess,
|
|
12
12
|
getGroupStatus,
|
|
13
13
|
cleanupAll,
|
|
14
14
|
} from "./process-manager.js";
|
|
@@ -150,11 +150,11 @@ export function registerBgShellCommand(pi: ExtensionAPI, state: BgShellSharedSta
|
|
|
150
150
|
ctx.ui.notify(`No process with id '${id}'`, "error");
|
|
151
151
|
return;
|
|
152
152
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
await new Promise(r => setTimeout(r,
|
|
153
|
+
// Graceful ladder (SIGTERM → grace → SIGKILL) via killProcessTree.
|
|
154
|
+
terminateProcess(id);
|
|
155
|
+
const deadline = Date.now() + 6_000; // grace (5s) + slack
|
|
156
|
+
while (bg.alive && Date.now() < deadline) {
|
|
157
|
+
await new Promise(r => setTimeout(r, 100));
|
|
158
158
|
}
|
|
159
159
|
if (!bg.alive) processes.delete(id);
|
|
160
160
|
ctx.ui.notify(`Killed process ${id} (${bg.label})`, "info");
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
processes,
|
|
13
13
|
startProcess,
|
|
14
14
|
killProcess,
|
|
15
|
+
terminateProcess,
|
|
15
16
|
restartProcess,
|
|
16
17
|
getInfo,
|
|
17
18
|
getGroupStatus,
|
|
@@ -44,7 +45,8 @@ export function registerBgShellTool(pi: ExtensionAPI, state: BgShellSharedState)
|
|
|
44
45
|
"group_status (health of a process group), highlights (significant output lines only).",
|
|
45
46
|
|
|
46
47
|
promptGuidelines: [
|
|
47
|
-
"Use bg_shell
|
|
48
|
+
"Use bg_shell for processes that STAY ALIVE and you interact with over time: servers, watchers, daemons, REPLs. For a command that runs to completion and exits (terraform apply, migrations, builds, tests, installs), use async_bash or sync bash instead — NOT bg_shell.",
|
|
49
|
+
"'wait_for_ready' is for long-lived processes that signal readiness (open a port / print a pattern). Never use it on a run-to-completion command: that command exits instead of becoming ready, so a clean exit-0 is reported as 'not ready'. If you see that, switch to async_bash.",
|
|
48
50
|
"After starting a server, use 'wait_for_ready' to efficiently block until it's listening — avoids polling loops entirely.",
|
|
49
51
|
"Use 'digest' instead of 'output' when you just need status — it returns a structured ~30-token summary instead of ~2000 tokens of raw output.",
|
|
50
52
|
"Use 'highlights' to see only significant output (errors, URLs, results) — typically 5-15 lines instead of hundreds.",
|
|
@@ -615,11 +617,13 @@ export function registerBgShellTool(pi: ExtensionAPI, state: BgShellSharedState)
|
|
|
615
617
|
};
|
|
616
618
|
}
|
|
617
619
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
620
|
+
// Graceful termination: SIGTERM → grace → SIGKILL via the shared
|
|
621
|
+
// killProcessTree ladder (same path bash/async_bash/exec use), so a
|
|
622
|
+
// stateful process gets a clean-shutdown window instead of a bare kill.
|
|
623
|
+
const killed = terminateProcess(params.id);
|
|
624
|
+
const deadline = Date.now() + 6_000; // grace (5s) + slack
|
|
625
|
+
while (bg.alive && Date.now() < deadline) {
|
|
626
|
+
await new Promise(r => setTimeout(r, 100));
|
|
623
627
|
}
|
|
624
628
|
|
|
625
629
|
const info = getInfo(bg);
|
|
@@ -9,7 +9,7 @@ import { ERROR_PATTERNS, WARNING_PATTERNS } from "./types.js";
|
|
|
9
9
|
import { formatUptime, formatTimeAgo } from "./utilities.js";
|
|
10
10
|
import {
|
|
11
11
|
processes,
|
|
12
|
-
|
|
12
|
+
terminateProcess,
|
|
13
13
|
cleanupAll,
|
|
14
14
|
restartProcess,
|
|
15
15
|
} from "./process-manager.js";
|
|
@@ -134,16 +134,20 @@ export class BgManagerOverlay {
|
|
|
134
134
|
return;
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
// x or d = kill selected
|
|
137
|
+
// x or d = kill selected (graceful ladder via killProcessTree).
|
|
138
|
+
// Use a short interactive grace: the user explicitly asked to kill, so give the
|
|
139
|
+
// process a brief clean-shutdown window then force, and re-render AFTER that grace
|
|
140
|
+
// plus slack so the row reflects the SIGKILL escalation (a 300ms re-render against
|
|
141
|
+
// the default 5s grace would show the process still alive).
|
|
138
142
|
if (data === "x" || data === "d") {
|
|
139
143
|
const proc = procs[this.selected];
|
|
140
144
|
if (proc && proc.alive) {
|
|
141
|
-
|
|
145
|
+
const OVERLAY_KILL_GRACE_MS = 500;
|
|
146
|
+
terminateProcess(proc.id, OVERLAY_KILL_GRACE_MS);
|
|
142
147
|
setTimeout(() => {
|
|
143
|
-
if (proc.alive) killProcess(proc.id, "SIGKILL");
|
|
144
148
|
this.invalidate();
|
|
145
149
|
this.tui.requestRender();
|
|
146
|
-
},
|
|
150
|
+
}, OVERLAY_KILL_GRACE_MS + 400);
|
|
147
151
|
}
|
|
148
152
|
return;
|
|
149
153
|
}
|
|
@@ -7,7 +7,7 @@ import { spawn, spawnSync } from "node:child_process";
|
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
|
8
8
|
import { writeFileSync, readFileSync, existsSync, mkdirSync } from "node:fs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
|
-
import { getShellConfig, sanitizeCommand } from "@gsd/pi-coding-agent";
|
|
10
|
+
import { getShellConfig, sanitizeCommand, killProcessTree } from "@gsd/pi-coding-agent";
|
|
11
11
|
import { rewriteCommandWithRtk } from "../shared/rtk.js";
|
|
12
12
|
import type {
|
|
13
13
|
BgProcess,
|
|
@@ -259,6 +259,34 @@ export function startProcess(opts: StartOptions): BgProcess {
|
|
|
259
259
|
|
|
260
260
|
// ── Process Kill ───────────────────────────────────────────────────────────
|
|
261
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Gracefully terminate a process and its tree using the shared killProcessTree
|
|
264
|
+
* ladder (SIGTERM → grace window → SIGKILL), the same path bash/async_bash/exec
|
|
265
|
+
* use. This is the "I want it dead, cleanly" intent — use it for the `kill`
|
|
266
|
+
* action, `restart`, and session cleanup. For sending a SPECIFIC signal the
|
|
267
|
+
* agent chose on purpose (SIGINT, SIGHUP, …) use killProcess(), which delivers
|
|
268
|
+
* that exact signal once and does not escalate.
|
|
269
|
+
*
|
|
270
|
+
* `graceMs` overrides the SIGTERM→SIGKILL window (default: killProcessTree's 5s);
|
|
271
|
+
* session cleanup passes a shorter grace so it stays snappy between units.
|
|
272
|
+
*
|
|
273
|
+
* Returns false only when the process is unknown/already dead; the actual
|
|
274
|
+
* SIGKILL escalation fires asynchronously after the grace window, so callers
|
|
275
|
+
* should not assume the process is dead the instant this returns.
|
|
276
|
+
*/
|
|
277
|
+
export function terminateProcess(id: string, graceMs?: number): boolean {
|
|
278
|
+
const bg = processes.get(id);
|
|
279
|
+
if (!bg) return false;
|
|
280
|
+
if (!bg.alive) return true;
|
|
281
|
+
if (!bg.proc.pid) {
|
|
282
|
+
// No pid to target a tree; fall back to a direct graceful signal.
|
|
283
|
+
try { bg.proc.kill("SIGTERM"); } catch { /* already gone */ }
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
killProcessTree(bg.proc.pid, graceMs !== undefined ? { graceMs } : undefined);
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
|
|
262
290
|
export function killProcess(id: string, sig: NodeJS.Signals = "SIGTERM"): boolean {
|
|
263
291
|
const bg = processes.get(id);
|
|
264
292
|
if (!bg) return false;
|
|
@@ -306,13 +334,14 @@ export async function restartProcess(id: string): Promise<BgProcess | null> {
|
|
|
306
334
|
const config = old.startConfig;
|
|
307
335
|
const restartCount = old.restartCount + 1;
|
|
308
336
|
|
|
309
|
-
// Kill old process
|
|
337
|
+
// Kill old process via the graceful ladder, then wait for it to actually die.
|
|
338
|
+
// killProcessTree escalates SIGTERM → grace → SIGKILL asynchronously, so poll
|
|
339
|
+
// for death rather than assuming a fixed sleep is enough.
|
|
310
340
|
if (old.alive) {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
await new Promise(r => setTimeout(r, 200));
|
|
341
|
+
terminateProcess(id);
|
|
342
|
+
const deadline = Date.now() + 6_000; // grace (5s) + slack
|
|
343
|
+
while (old.alive && Date.now() < deadline) {
|
|
344
|
+
await new Promise(r => setTimeout(r, 100));
|
|
316
345
|
}
|
|
317
346
|
}
|
|
318
347
|
processes.delete(id);
|
|
@@ -374,25 +403,16 @@ export function pruneDeadProcesses(): void {
|
|
|
374
403
|
}
|
|
375
404
|
|
|
376
405
|
export function cleanupAll(): void {
|
|
406
|
+
// Deliberately a bare, synchronous SIGKILL — not the graceful ladder. This runs
|
|
407
|
+
// from process 'exit'/signal handlers where timers no longer fire, so a deferred
|
|
408
|
+
// SIGKILL would never be delivered and children would be orphaned when we vanish.
|
|
409
|
+
// Immediate force-kill is the correct teardown semantics here.
|
|
377
410
|
for (const [id, bg] of processes) {
|
|
378
411
|
if (bg.alive) killProcess(id, "SIGKILL");
|
|
379
412
|
}
|
|
380
413
|
processes.clear();
|
|
381
414
|
}
|
|
382
415
|
|
|
383
|
-
/**
|
|
384
|
-
* Kill all alive, non-persistent bg processes.
|
|
385
|
-
* Called between auto-mode units to prevent orphaned servers from
|
|
386
|
-
* keeping ports bound across task boundaries (#1209).
|
|
387
|
-
*/
|
|
388
|
-
export function killSessionProcesses(): void {
|
|
389
|
-
for (const [id, bg] of processes) {
|
|
390
|
-
if (bg.alive && !bg.persistAcrossSessions) {
|
|
391
|
-
killProcess(id, "SIGTERM");
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
416
|
async function waitForProcessExit(bg: BgProcess, timeoutMs: number): Promise<boolean> {
|
|
397
417
|
if (!bg.alive) return true;
|
|
398
418
|
await new Promise<void>((resolve) => {
|
|
@@ -406,6 +426,13 @@ async function waitForProcessExit(bg: BgProcess, timeoutMs: number): Promise<boo
|
|
|
406
426
|
return !bg.alive;
|
|
407
427
|
}
|
|
408
428
|
|
|
429
|
+
/**
|
|
430
|
+
* Terminate the alive, non-persistent processes owned by a session, gracefully.
|
|
431
|
+
* Routes through the shared killProcessTree ladder (SIGTERM → grace → SIGKILL)
|
|
432
|
+
* via terminateProcess, with a short grace (default 300ms) so cleanup between
|
|
433
|
+
* units stays snappy; killProcessTree handles the SIGKILL escalation itself, so
|
|
434
|
+
* there is no separate force-kill pass here.
|
|
435
|
+
*/
|
|
409
436
|
export async function cleanupSessionProcesses(
|
|
410
437
|
sessionFile: string,
|
|
411
438
|
options?: { graceMs?: number },
|
|
@@ -417,13 +444,11 @@ export async function cleanupSessionProcesses(
|
|
|
417
444
|
if (matches.length === 0) return [];
|
|
418
445
|
|
|
419
446
|
for (const bg of matches) {
|
|
420
|
-
|
|
447
|
+
terminateProcess(bg.id, graceMs);
|
|
421
448
|
}
|
|
422
449
|
if (graceMs > 0) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
for (const bg of matches) {
|
|
426
|
-
if (bg.alive) killProcess(bg.id, "SIGKILL");
|
|
450
|
+
// Wait past the grace so the SIGKILL escalation has fired and exits are observed.
|
|
451
|
+
await Promise.all(matches.map((bg) => waitForProcessExit(bg, graceMs + 200)));
|
|
427
452
|
}
|
|
428
453
|
return matches.map((bg) => bg.id);
|
|
429
454
|
}
|
|
@@ -87,6 +87,18 @@ export async function waitForReady(bg: BgProcess, timeout: number, signal?: Abor
|
|
|
87
87
|
return { ready: false, detail: "Cancelled" };
|
|
88
88
|
}
|
|
89
89
|
if (!bg.alive) {
|
|
90
|
+
// A clean exit-0 means the command ran to completion — it was a batch
|
|
91
|
+
// command (e.g. terraform apply, a migration, a build), not a long-lived
|
|
92
|
+
// server. That is success, not a readiness failure; say so plainly and
|
|
93
|
+
// point at the right tool so the agent stops using wait_for_ready here.
|
|
94
|
+
if (bg.exitCode === 0) {
|
|
95
|
+
return {
|
|
96
|
+
ready: false,
|
|
97
|
+
detail:
|
|
98
|
+
"Process completed (exit 0) before signaling readiness — it ran to completion rather than staying alive. " +
|
|
99
|
+
"wait_for_ready is for long-lived servers/watchers; for a run-to-completion command use async_bash (or bg_shell 'run').",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
90
102
|
const stderrLines = bg.output.filter(l => l.stream === "stderr").slice(-5).map(l => l.line);
|
|
91
103
|
const stderrContext = stderrLines.length > 0 ? `\nstderr:\n${stderrLines.join("\n").slice(0, 500)}` : "";
|
|
92
104
|
return {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, test } from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
import type { BgProcess } from "../types.ts";
|
|
4
|
-
import { detectProcessType } from "../process-manager.ts";
|
|
4
|
+
import { detectProcessType, startProcess, terminateProcess, processes } from "../process-manager.ts";
|
|
5
5
|
import { waitForReady } from "../readiness-detector.ts";
|
|
6
6
|
import {
|
|
7
7
|
formatTimeAgo,
|
|
@@ -108,6 +108,18 @@ describe("bg-shell waitForReady", () => {
|
|
|
108
108
|
assert.match(result.detail, /137/);
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
+
test("a clean exit-0 reads as completion, not a crash, and points at the right tool", async () => {
|
|
112
|
+
// Regression: a run-to-completion batch command (e.g. terraform apply) put
|
|
113
|
+
// under wait_for_ready exits 0 on success. It must NOT be framed as a crash;
|
|
114
|
+
// it should say it completed and steer toward async_bash.
|
|
115
|
+
const bg = makeBg({ alive: false, exitCode: 0 });
|
|
116
|
+
const result = await waitForReady(bg, 1000);
|
|
117
|
+
assert.equal(result.ready, false);
|
|
118
|
+
assert.match(result.detail, /completed \(exit 0\)/);
|
|
119
|
+
assert.match(result.detail, /async_bash/);
|
|
120
|
+
assert.doesNotMatch(result.detail, /exited before becoming ready/);
|
|
121
|
+
});
|
|
122
|
+
|
|
111
123
|
test("reports failure when the process entered an error state", async () => {
|
|
112
124
|
const bg = makeBg({ status: "error", readyPort: 5173 });
|
|
113
125
|
const result = await waitForReady(bg, 1000);
|
|
@@ -204,3 +216,38 @@ describe("bg-shell resolveBgShellPersistenceCwd", () => {
|
|
|
204
216
|
assert.equal(result, "/proj/.gsd/worktrees/feature-b");
|
|
205
217
|
});
|
|
206
218
|
});
|
|
219
|
+
|
|
220
|
+
describe("bg-shell terminateProcess (graceful kill ladder)", () => {
|
|
221
|
+
test(
|
|
222
|
+
"force-kills a SIGTERM-immune process via the shared killProcessTree ladder",
|
|
223
|
+
{ skip: process.platform === "win32" ? "Unix-primary graceful semantics" : false, timeout: 15_000 },
|
|
224
|
+
async (t) => {
|
|
225
|
+
// A process that ignores SIGTERM must still die — terminateProcess routes
|
|
226
|
+
// through killProcessTree, which escalates SIGTERM → grace → SIGKILL. A bare
|
|
227
|
+
// single-signal kill (the old behavior) would have left this running.
|
|
228
|
+
const bg = startProcess({
|
|
229
|
+
command: "trap '' TERM; while true; do sleep 1; done",
|
|
230
|
+
cwd: "/tmp",
|
|
231
|
+
label: "sigterm-immune",
|
|
232
|
+
type: "generic",
|
|
233
|
+
});
|
|
234
|
+
t.after(() => {
|
|
235
|
+
try { if (bg.proc.pid) process.kill(-bg.proc.pid, "SIGKILL"); } catch { /* gone */ }
|
|
236
|
+
processes.delete(bg.id);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Let the trap install.
|
|
240
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
241
|
+
assert.equal(bg.alive, true, "process should be alive before terminate");
|
|
242
|
+
|
|
243
|
+
terminateProcess(bg.id);
|
|
244
|
+
|
|
245
|
+
// SIGKILL fires after the 5s grace; poll up to grace + slack.
|
|
246
|
+
const deadline = Date.now() + 9_000;
|
|
247
|
+
while (bg.alive && Date.now() < deadline) {
|
|
248
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
249
|
+
}
|
|
250
|
+
assert.equal(bg.alive, false, "SIGTERM-immune process must be SIGKILLed via the ladder, not left running");
|
|
251
|
+
},
|
|
252
|
+
);
|
|
253
|
+
});
|