@opengsd/gsd-pi 1.2.0-dev.955e4da0 → 1.2.0-dev.9ad8ae33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-style.d.ts +17 -0
- package/dist/cli-style.js +28 -0
- package/dist/cli.js +1 -1
- package/dist/headless-events.d.ts +4 -2
- package/dist/headless-events.js +14 -34
- package/dist/mcp-server.js +2 -1
- package/dist/models-resolver.d.ts +3 -13
- package/dist/models-resolver.js +3 -22
- package/dist/resource-loader.d.ts +10 -5
- package/dist/resource-loader.js +123 -20
- 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/bg-shell/utilities.js +3 -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 +30 -4
- 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 +35 -8
- package/dist/resources/extensions/gsd/auto-prompts.js +81 -19
- package/dist/resources/extensions/gsd/auto-start.js +41 -18
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +18 -0
- package/dist/resources/extensions/gsd/auto-unit-closeout.js +45 -21
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +12 -20
- package/dist/resources/extensions/gsd/auto-verification.js +23 -30
- package/dist/resources/extensions/gsd/auto-worktree.js +44 -91
- package/dist/resources/extensions/gsd/auto.js +41 -14
- package/dist/resources/extensions/gsd/blocked-models.js +28 -0
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +29 -8
- 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 +212 -48
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +303 -77
- package/dist/resources/extensions/gsd/branch-patterns.js +2 -0
- 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/captures.js +4 -6
- 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 +12 -15
- package/dist/resources/extensions/gsd/db/queries.js +26 -0
- package/dist/resources/extensions/gsd/db-writer.js +8 -17
- 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-environment.js +2 -6
- package/dist/resources/extensions/gsd/doctor-format.js +9 -6
- package/dist/resources/extensions/gsd/doctor-git-checks.js +2 -18
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +13 -15
- package/dist/resources/extensions/gsd/engine-hook-contract.js +70 -0
- package/dist/resources/extensions/gsd/error-classifier.js +9 -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 +158 -0
- package/dist/resources/extensions/gsd/guided-flow.js +23 -5
- 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/mcp-tool-name.js +5 -13
- package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +1 -1
- package/dist/resources/extensions/gsd/migrate/safety.js +4 -1
- 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/notification-store.js +11 -4
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +6 -4
- package/dist/resources/extensions/gsd/parsers-legacy.js +16 -4
- package/dist/resources/extensions/gsd/paths.js +27 -0
- package/dist/resources/extensions/gsd/pre-execution-checks.js +91 -3
- package/dist/resources/extensions/gsd/preferences-models.js +14 -48
- 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 +2 -2
- 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 +2 -2
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +2 -2
- 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/provider-error-guidance.js +1 -5
- package/dist/resources/extensions/gsd/provider-switch-observer.js +1 -1
- package/dist/resources/extensions/gsd/publication.js +87 -0
- package/dist/resources/extensions/gsd/reactive-graph.js +8 -1
- package/dist/resources/extensions/gsd/recovery-classification.js +37 -94
- 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 +6 -20
- package/dist/resources/extensions/gsd/stop-notice.js +57 -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/tool-surface-readiness.js +56 -0
- 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 +9 -7
- package/dist/resources/extensions/gsd/tools/plan-slice.js +14 -8
- 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-closeout.js +138 -0
- package/dist/resources/extensions/gsd/unit-context-composer.js +74 -1
- package/dist/resources/extensions/gsd/unit-context-manifest.js +4 -27
- package/dist/resources/extensions/gsd/unit-registry.js +337 -0
- package/dist/resources/extensions/gsd/unit-tool-contracts.js +9 -182
- 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/workflow-tool-surface.js +1 -1
- package/dist/resources/extensions/gsd/worktree-git-recovery.js +15 -9
- 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-root.js +11 -0
- package/dist/resources/extensions/gsd/worktree-session-state.js +4 -5
- 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 +13 -13
- 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 +13 -13
- package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
- 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/dist/worktree-cli.js +3 -6
- package/dist/worktree-status-banner.js +7 -15
- package/package.json +2 -2
- 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/dist/workflow.d.ts +4 -0
- package/packages/contracts/dist/workflow.d.ts.map +1 -1
- package/packages/contracts/dist/workflow.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 +9 -1
- 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 +26 -18
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +116 -39
- 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 +35 -125
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +46 -120
- 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/bg-shell/utilities.ts +3 -0
- 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 +34 -4
- 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 +40 -8
- package/src/resources/extensions/gsd/auto-prompts.ts +118 -35
- package/src/resources/extensions/gsd/auto-start.ts +42 -21
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
- package/src/resources/extensions/gsd/auto-unit-closeout.ts +83 -28
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +14 -21
- package/src/resources/extensions/gsd/auto-verification.ts +26 -28
- package/src/resources/extensions/gsd/auto-worktree.ts +44 -94
- package/src/resources/extensions/gsd/auto.ts +52 -16
- package/src/resources/extensions/gsd/blocked-models.ts +49 -0
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +37 -10
- 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 +251 -47
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +352 -84
- package/src/resources/extensions/gsd/branch-patterns.ts +3 -0
- 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/captures.ts +4 -6
- 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 +13 -11
- package/src/resources/extensions/gsd/db/queries.ts +37 -0
- package/src/resources/extensions/gsd/db-writer.ts +11 -19
- 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-environment.ts +2 -7
- package/src/resources/extensions/gsd/doctor-format.ts +12 -7
- package/src/resources/extensions/gsd/doctor-git-checks.ts +2 -19
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +13 -15
- package/src/resources/extensions/gsd/engine-hook-contract.ts +79 -0
- package/src/resources/extensions/gsd/error-classifier.ts +11 -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 +217 -0
- package/src/resources/extensions/gsd/guided-flow.ts +37 -28
- 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/mcp-tool-name.ts +6 -11
- package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +1 -1
- package/src/resources/extensions/gsd/migrate/safety.ts +4 -1
- 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/notification-store.ts +26 -3
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +6 -4
- package/src/resources/extensions/gsd/parsers-legacy.ts +16 -4
- package/src/resources/extensions/gsd/paths.ts +33 -0
- package/src/resources/extensions/gsd/pre-execution-checks.ts +109 -3
- package/src/resources/extensions/gsd/preferences-models.ts +12 -47
- 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 +2 -2
- 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 +2 -2
- package/src/resources/extensions/gsd/prompts/replan-slice.md +2 -2
- 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/provider-error-guidance.ts +4 -9
- package/src/resources/extensions/gsd/provider-switch-observer.ts +1 -1
- package/src/resources/extensions/gsd/publication.ts +122 -0
- package/src/resources/extensions/gsd/reactive-graph.ts +11 -1
- package/src/resources/extensions/gsd/recovery-classification.ts +42 -96
- 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 +9 -21
- package/src/resources/extensions/gsd/stop-notice.ts +75 -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/auto-start-orphan-bootstrap.test.ts +236 -0
- 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/checkout-branch-stash-guard.test.ts +66 -1
- package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +8 -7
- 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/db-writer.test.ts +15 -4
- 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 +148 -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 +53 -11
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +73 -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/notification-store.test.ts +32 -0
- 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/pre-execution-checks.test.ts +193 -1
- package/src/resources/extensions/gsd/tests/prompt-db.test.ts +124 -6
- package/src/resources/extensions/gsd/tests/provider-error-guidance.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/publication.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +157 -0
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +20 -1
- package/src/resources/extensions/gsd/tests/stop-notice.test.ts +70 -0
- 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-invocation-error-loop-break.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/tool-surface-readiness.test.ts +155 -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-closeout.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +67 -2
- package/src/resources/extensions/gsd/tests/unit-registry.test.ts +163 -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-mcp.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/workflow-reconcile.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +275 -40
- 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/tool-surface-readiness.ts +76 -0
- 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 +8 -7
- package/src/resources/extensions/gsd/tools/plan-slice.ts +14 -8
- 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-closeout.ts +201 -0
- package/src/resources/extensions/gsd/unit-context-composer.ts +111 -1
- package/src/resources/extensions/gsd/unit-context-manifest.ts +4 -28
- package/src/resources/extensions/gsd/unit-registry.ts +412 -0
- package/src/resources/extensions/gsd/unit-tool-contracts.ts +27 -192
- 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/workflow-tool-surface.ts +4 -1
- package/src/resources/extensions/gsd/worktree-git-recovery.ts +15 -9
- 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-root.ts +12 -0
- package/src/resources/extensions/gsd/worktree-session-state.ts +3 -5
- 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/{C24pqUd-aru-l0Dp0gLZP → FBNo5cT_chy7YNoAQsU3o}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{C24pqUd-aru-l0Dp0gLZP → FBNo5cT_chy7YNoAQsU3o}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* async-bash-cancel.test.ts — Tests for graceful async_bash cancellation.
|
|
3
|
+
*
|
|
4
|
+
* Proves that:
|
|
5
|
+
* 1. The killProcessTree re-export from @gsd/pi-coding-agent resolves correctly
|
|
6
|
+
* (loading async-bash-tool.ts will fail at import-time if the export is missing).
|
|
7
|
+
* 2. manager.cancel() sets status to 'cancelled' and returns 'cancelled'.
|
|
8
|
+
* 3. The job promise settles promptly (SIGTERM kills a well-behaved child).
|
|
9
|
+
* 4. The timeout path force-kills a SIGTERM-immune child via SIGKILL (regression:
|
|
10
|
+
* it must not be left running in the background after the timeout fires).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import test from "node:test";
|
|
14
|
+
import assert from "node:assert/strict";
|
|
15
|
+
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
16
|
+
import { tmpdir } from "node:os";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { createAsyncBashTool } from "./async-bash-tool.ts";
|
|
19
|
+
import { createAwaitTool } from "./await-tool.ts";
|
|
20
|
+
import { AsyncJobManager } from "./job-manager.ts";
|
|
21
|
+
|
|
22
|
+
function isAlive(pid: number): boolean {
|
|
23
|
+
try {
|
|
24
|
+
process.kill(pid, 0); // signal 0 probes existence without killing
|
|
25
|
+
return true;
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// If killProcessTree is missing from the re-export the import above will throw
|
|
32
|
+
// at load time (async-bash-tool.ts destructures it from @gsd/pi-coding-agent).
|
|
33
|
+
// A load-time error surfaces as a test runner parse failure — no explicit
|
|
34
|
+
// assertion needed; the test file simply won't run.
|
|
35
|
+
|
|
36
|
+
const noopSignal = new AbortController().signal;
|
|
37
|
+
|
|
38
|
+
test("graceful cancel: manager.cancel returns 'cancelled' and job settles promptly", async () => {
|
|
39
|
+
const manager = new AsyncJobManager();
|
|
40
|
+
const tool = createAsyncBashTool(() => manager, () => process.cwd());
|
|
41
|
+
|
|
42
|
+
// Launch a long-running well-behaved process (responds to SIGTERM)
|
|
43
|
+
const result = await tool.execute(
|
|
44
|
+
"tc-cancel-01",
|
|
45
|
+
{
|
|
46
|
+
command: "sleep 30",
|
|
47
|
+
label: "cancel-test-sleep",
|
|
48
|
+
},
|
|
49
|
+
noopSignal,
|
|
50
|
+
() => {},
|
|
51
|
+
undefined as never,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const text = result.content.map((c: { type: string; text?: string }) => c.text ?? "").join("\n");
|
|
55
|
+
const jobId = text.match(/\*\*(bg_[a-f0-9]+)\*\*/)?.[1];
|
|
56
|
+
assert.ok(jobId, `Expected a job ID in result text, got: ${text}`);
|
|
57
|
+
|
|
58
|
+
const job = manager.getJob(jobId)!;
|
|
59
|
+
assert.ok(job, "Job should be registered in manager");
|
|
60
|
+
assert.equal(job.status, "running", "Job should be running before cancel");
|
|
61
|
+
|
|
62
|
+
// Cancel — should return 'cancelled' immediately
|
|
63
|
+
const cancelResult = manager.cancel(jobId);
|
|
64
|
+
assert.equal(cancelResult, "cancelled", `cancel() should return 'cancelled', got: ${cancelResult}`);
|
|
65
|
+
assert.equal(job.status, "cancelled", "Job status should flip to 'cancelled'");
|
|
66
|
+
|
|
67
|
+
// Job promise should settle promptly — SIGTERM kills sleep quickly
|
|
68
|
+
const start = Date.now();
|
|
69
|
+
await Promise.race([
|
|
70
|
+
job.promise,
|
|
71
|
+
new Promise<never>((_, reject) => {
|
|
72
|
+
const t = setTimeout(() => {
|
|
73
|
+
reject(new Error(
|
|
74
|
+
`Job promise did not settle within 5s after cancel ` +
|
|
75
|
+
`(${Date.now() - start}ms elapsed) — graceful cancel may be broken`,
|
|
76
|
+
));
|
|
77
|
+
}, 5_000);
|
|
78
|
+
if (typeof t === "object" && "unref" in t) t.unref();
|
|
79
|
+
}),
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
const elapsed = Date.now() - start;
|
|
83
|
+
assert.ok(elapsed < 5_000, `Job promise should settle under 5s, took ${elapsed}ms`);
|
|
84
|
+
|
|
85
|
+
manager.shutdown();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test(
|
|
89
|
+
"graceful cancel: status STAYS 'cancelled' after the job promise settles (not clobbered to 'completed')",
|
|
90
|
+
async (t) => {
|
|
91
|
+
// Regression: async_bash resolves (not rejects) its run promise even when aborted —
|
|
92
|
+
// safeResolve returns "Command aborted" rather than throwing — so the job-manager
|
|
93
|
+
// .then branch used to overwrite status back to 'completed', mislabeling a job the
|
|
94
|
+
// user explicitly cancelled. A cancelled job must also deliver NO follow-up.
|
|
95
|
+
const delivered: string[] = [];
|
|
96
|
+
const manager = new AsyncJobManager({ onJobComplete: (j) => delivered.push(j.id) });
|
|
97
|
+
// Tear down via t.after() so a thrown assertion still cleans up the manager.
|
|
98
|
+
t.after(() => manager.shutdown());
|
|
99
|
+
const tool = createAsyncBashTool(() => manager, () => process.cwd());
|
|
100
|
+
|
|
101
|
+
const result = await tool.execute(
|
|
102
|
+
"tc-cancel-status",
|
|
103
|
+
{ command: "sleep 30", label: "cancel-status-test" },
|
|
104
|
+
noopSignal,
|
|
105
|
+
() => {},
|
|
106
|
+
undefined as never,
|
|
107
|
+
);
|
|
108
|
+
const text = result.content.map((c: { type: string; text?: string }) => c.text ?? "").join("\n");
|
|
109
|
+
const jobId = text.match(/\*\*(bg_[a-f0-9]+)\*\*/)?.[1];
|
|
110
|
+
assert.ok(jobId, `Expected a job ID, got: ${text}`);
|
|
111
|
+
|
|
112
|
+
const job = manager.getJob(jobId)!;
|
|
113
|
+
assert.equal(manager.cancel(jobId), "cancelled");
|
|
114
|
+
assert.equal(job.status, "cancelled", "status should be 'cancelled' synchronously");
|
|
115
|
+
|
|
116
|
+
// Await the run promise settling (SIGTERM kills sleep quickly), THEN re-check status.
|
|
117
|
+
await job.promise;
|
|
118
|
+
// Let the deliverResult setTimeout(0) (if any) and microtasks flush.
|
|
119
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
120
|
+
|
|
121
|
+
assert.equal(
|
|
122
|
+
job.status,
|
|
123
|
+
"cancelled",
|
|
124
|
+
`status must REMAIN 'cancelled' after the run promise settles, got '${job.status}' ` +
|
|
125
|
+
`(the .then branch clobbered the user-cancelled status)`,
|
|
126
|
+
);
|
|
127
|
+
assert.equal(
|
|
128
|
+
delivered.includes(jobId),
|
|
129
|
+
false,
|
|
130
|
+
"a cancelled job must not fire an onJobComplete follow-up",
|
|
131
|
+
);
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
test("graceful cancel: killProcessTree re-export resolves (load-time check)", async () => {
|
|
136
|
+
// This test is a belt-and-suspenders static check. If killProcessTree were
|
|
137
|
+
// not re-exported, the import at the top of this file would have already
|
|
138
|
+
// caused the entire test file to fail to load. We do an explicit runtime
|
|
139
|
+
// check here to make the intent clear and produce a readable assertion failure
|
|
140
|
+
// rather than a cryptic module-not-found error in the test runner output.
|
|
141
|
+
const mod = await import("@gsd/pi-coding-agent");
|
|
142
|
+
assert.ok(
|
|
143
|
+
typeof (mod as Record<string, unknown>).killProcessTree === "function",
|
|
144
|
+
"killProcessTree must be exported from @gsd/pi-coding-agent",
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test(
|
|
149
|
+
"timeout path: SIGTERM-immune job is force-killed (SIGKILL), not left running in the background",
|
|
150
|
+
// Worst case ~12s (1s timeout + 5s grace + 3s hard-deadline + 3s poll); the
|
|
151
|
+
// explicit timeout prevents an infinite hang if force-resolve ever regresses.
|
|
152
|
+
{ skip: process.platform === "win32" ? "Unix-primary graceful semantics" : false, timeout: 20_000 },
|
|
153
|
+
async (t) => {
|
|
154
|
+
// Regression: the timeout path previously called a local killTree that only
|
|
155
|
+
// ever sent SIGTERM (twice), so a `trap '' TERM` child survived its timeout
|
|
156
|
+
// and ran forever in the background. It now routes through killProcessTree,
|
|
157
|
+
// which escalates SIGTERM -> grace -> SIGKILL. This proves the child is
|
|
158
|
+
// actually dead shortly after the 5s grace window, not orphaned.
|
|
159
|
+
const dir = mkdtempSync(join(tmpdir(), "async-timeout-sigkill-"));
|
|
160
|
+
const pidFile = join(dir, "pgid.pid");
|
|
161
|
+
t.after(() => {
|
|
162
|
+
// Best-effort: kill the process group if the test failed and left it alive.
|
|
163
|
+
try {
|
|
164
|
+
const pgid = Number.parseInt(readFileSync(pidFile, "utf-8").trim(), 10);
|
|
165
|
+
// Guard pgid > 0: process.kill(-0, ...) would signal the test runner's OWN
|
|
166
|
+
// process group (-0 === 0 === "caller's group" under POSIX).
|
|
167
|
+
if (Number.isFinite(pgid) && pgid > 0) process.kill(-pgid, "SIGKILL");
|
|
168
|
+
} catch {
|
|
169
|
+
/* already gone */
|
|
170
|
+
}
|
|
171
|
+
rmSync(dir, { recursive: true, force: true });
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const manager = new AsyncJobManager();
|
|
175
|
+
const tool = createAsyncBashTool(() => manager, () => process.cwd());
|
|
176
|
+
|
|
177
|
+
// echo $$ records the detached shell's PID (process-group leader). The shell
|
|
178
|
+
// ignores SIGTERM and loops forever, so only SIGKILL can end it.
|
|
179
|
+
const command = `echo $$ > '${pidFile}'; trap '' TERM; while true; do sleep 1; done`;
|
|
180
|
+
const result = await tool.execute(
|
|
181
|
+
"tc-timeout-sigkill",
|
|
182
|
+
{ command, label: "timeout-sigkill", timeout: 1 },
|
|
183
|
+
noopSignal,
|
|
184
|
+
() => {},
|
|
185
|
+
undefined as never,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const text = result.content.map((c: { type: string; text?: string }) => c.text ?? "").join("\n");
|
|
189
|
+
const jobId = text.match(/\*\*(bg_[a-f0-9]+)\*\*/)?.[1];
|
|
190
|
+
assert.ok(jobId, `Expected a job ID, got: ${text}`);
|
|
191
|
+
|
|
192
|
+
// The promise force-resolves at ~timeout + grace + hard-deadline (~1+5+3s).
|
|
193
|
+
await manager.getJob(jobId)!.promise;
|
|
194
|
+
|
|
195
|
+
const pgid = Number.parseInt(readFileSync(pidFile, "utf-8").trim(), 10);
|
|
196
|
+
assert.ok(Number.isFinite(pgid) && pgid > 0, "child must have recorded a valid positive process-group PID");
|
|
197
|
+
|
|
198
|
+
// Poll briefly: SIGKILL was sent at timeout+grace (~6s); give it a moment to reap.
|
|
199
|
+
const deadlineMs = Date.now() + 3_000;
|
|
200
|
+
while (isAlive(pgid) && Date.now() < deadlineMs) {
|
|
201
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
202
|
+
}
|
|
203
|
+
assert.equal(
|
|
204
|
+
isAlive(pgid),
|
|
205
|
+
false,
|
|
206
|
+
`SIGTERM-immune timed-out job (pgid ${pgid}) must be SIGKILLed, not left running`,
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
manager.shutdown();
|
|
210
|
+
},
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
test("graceful cancel: job promise settles even for node process on cancel", async () => {
|
|
214
|
+
const manager = new AsyncJobManager();
|
|
215
|
+
const tool = createAsyncBashTool(() => manager, () => process.cwd());
|
|
216
|
+
|
|
217
|
+
// node process that blocks for 30s — responds to SIGTERM
|
|
218
|
+
const result = await tool.execute(
|
|
219
|
+
"tc-cancel-02",
|
|
220
|
+
{
|
|
221
|
+
command: `node -e "setTimeout(()=>{}, 30000)"`,
|
|
222
|
+
label: "cancel-test-node",
|
|
223
|
+
},
|
|
224
|
+
noopSignal,
|
|
225
|
+
() => {},
|
|
226
|
+
undefined as never,
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
const text = result.content.map((c: { type: string; text?: string }) => c.text ?? "").join("\n");
|
|
230
|
+
const jobId = text.match(/\*\*(bg_[a-f0-9]+)\*\*/)?.[1];
|
|
231
|
+
assert.ok(jobId, "Expected a job ID");
|
|
232
|
+
|
|
233
|
+
const job = manager.getJob(jobId)!;
|
|
234
|
+
assert.equal(job.status, "running");
|
|
235
|
+
|
|
236
|
+
const cancelResult = manager.cancel(jobId);
|
|
237
|
+
assert.equal(cancelResult, "cancelled");
|
|
238
|
+
|
|
239
|
+
const start = Date.now();
|
|
240
|
+
await Promise.race([
|
|
241
|
+
job.promise,
|
|
242
|
+
new Promise<never>((_, reject) => {
|
|
243
|
+
const t = setTimeout(() => {
|
|
244
|
+
reject(new Error(`Job promise hung after cancel (${Date.now() - start}ms)`));
|
|
245
|
+
}, 5_000);
|
|
246
|
+
if (typeof t === "object" && "unref" in t) t.unref();
|
|
247
|
+
}),
|
|
248
|
+
]);
|
|
249
|
+
|
|
250
|
+
assert.ok(Date.now() - start < 5_000, "Promise should settle quickly after cancel");
|
|
251
|
+
manager.shutdown();
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test(
|
|
255
|
+
"cancel path: SIGTERM-immune job force-resolves via hard deadline, does not hang",
|
|
256
|
+
// Worst case ~8s (5s grace + 3s hard-deadline after cancel); explicit timeout
|
|
257
|
+
// guards against an infinite hang if the abort-path force-resolve regresses.
|
|
258
|
+
{ skip: process.platform === "win32" ? "Unix-primary graceful semantics" : false, timeout: 20_000 },
|
|
259
|
+
async () => {
|
|
260
|
+
// Regression: onAbort (the /jobs cancel path) used to kill the child but arm no
|
|
261
|
+
// hard deadline, so cancelling a `trap '' TERM` child that never closes left the
|
|
262
|
+
// job promise dangling forever. It now arms the same hard-deadline force-resolve
|
|
263
|
+
// the timeout path uses.
|
|
264
|
+
const manager = new AsyncJobManager();
|
|
265
|
+
const tool = createAsyncBashTool(() => manager, () => process.cwd());
|
|
266
|
+
|
|
267
|
+
const result = await tool.execute(
|
|
268
|
+
"tc-cancel-dstate",
|
|
269
|
+
{ command: `trap '' TERM; while true; do sleep 1; done`, label: "cancel-dstate" },
|
|
270
|
+
noopSignal,
|
|
271
|
+
() => {},
|
|
272
|
+
undefined as never,
|
|
273
|
+
);
|
|
274
|
+
const text = result.content.map((c: { type: string; text?: string }) => c.text ?? "").join("\n");
|
|
275
|
+
const jobId = text.match(/\*\*(bg_[a-f0-9]+)\*\*/)?.[1];
|
|
276
|
+
assert.ok(jobId, "Expected a job ID");
|
|
277
|
+
|
|
278
|
+
const job = manager.getJob(jobId)!;
|
|
279
|
+
assert.equal(job.status, "running");
|
|
280
|
+
assert.equal(manager.cancel(jobId), "cancelled");
|
|
281
|
+
|
|
282
|
+
// The promise must settle via the hard deadline (~8s) rather than hang.
|
|
283
|
+
const start = Date.now();
|
|
284
|
+
await Promise.race([
|
|
285
|
+
job.promise,
|
|
286
|
+
new Promise<never>((_, reject) => {
|
|
287
|
+
const t = setTimeout(() => {
|
|
288
|
+
reject(new Error(`Cancelled D-state job hung (${Date.now() - start}ms) — abort-path force-resolve missing`));
|
|
289
|
+
}, 15_000);
|
|
290
|
+
if (typeof t === "object" && "unref" in t) t.unref();
|
|
291
|
+
}),
|
|
292
|
+
]);
|
|
293
|
+
assert.ok(Date.now() - start < 15_000, "cancelled D-state job must force-resolve, not hang");
|
|
294
|
+
|
|
295
|
+
manager.shutdown();
|
|
296
|
+
},
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
test(
|
|
300
|
+
"ESC during await_job ends the wait but leaves the real background process alive",
|
|
301
|
+
// End-to-end proof of the headline claim: a real OS subprocess (not a synthetic
|
|
302
|
+
// in-process job) must survive an aborted await_job. The await tool resolves on
|
|
303
|
+
// abort; we then confirm the child PID is still alive before cleaning it up.
|
|
304
|
+
{ skip: process.platform === "win32" ? "Unix-primary graceful semantics" : false, timeout: 20_000 },
|
|
305
|
+
async (t) => {
|
|
306
|
+
const dir = mkdtempSync(join(tmpdir(), "await-esc-alive-"));
|
|
307
|
+
const pidFile = join(dir, "pgid.pid");
|
|
308
|
+
const manager = new AsyncJobManager();
|
|
309
|
+
t.after(() => {
|
|
310
|
+
try {
|
|
311
|
+
const pgid = Number.parseInt(readFileSync(pidFile, "utf-8").trim(), 10);
|
|
312
|
+
if (Number.isFinite(pgid) && pgid > 0) process.kill(-pgid, "SIGKILL");
|
|
313
|
+
} catch {
|
|
314
|
+
/* already gone */
|
|
315
|
+
}
|
|
316
|
+
manager.shutdown();
|
|
317
|
+
rmSync(dir, { recursive: true, force: true });
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
const asyncBash = createAsyncBashTool(() => manager, () => process.cwd());
|
|
321
|
+
const awaitJob = createAwaitTool(() => manager);
|
|
322
|
+
|
|
323
|
+
// Start a real 60s background process that records its process-group PID.
|
|
324
|
+
const started = await asyncBash.execute(
|
|
325
|
+
"tc-await-esc",
|
|
326
|
+
{ command: `echo $$ > '${pidFile}'; sleep 60`, label: "await-esc-sleeper" },
|
|
327
|
+
noopSignal,
|
|
328
|
+
() => {},
|
|
329
|
+
undefined as never,
|
|
330
|
+
);
|
|
331
|
+
const jobId = started.content.map((c: { type: string; text?: string }) => c.text ?? "").join("\n")
|
|
332
|
+
.match(/\*\*(bg_[a-f0-9]+)\*\*/)?.[1];
|
|
333
|
+
assert.ok(jobId, "expected a job id from async_bash");
|
|
334
|
+
|
|
335
|
+
// Give the child a moment to write its pidfile.
|
|
336
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
337
|
+
const pgid = Number.parseInt(readFileSync(pidFile, "utf-8").trim(), 10);
|
|
338
|
+
assert.ok(Number.isFinite(pgid) && pgid > 0, "child must record a valid pgid");
|
|
339
|
+
assert.equal(isAlive(pgid), true, "child should be alive before the await");
|
|
340
|
+
|
|
341
|
+
// await_job, then fire ESC (abort) ~100ms in. The wait must end promptly.
|
|
342
|
+
const ac = new AbortController();
|
|
343
|
+
const abortTimer = setTimeout(() => ac.abort(), 100);
|
|
344
|
+
if (typeof abortTimer === "object" && "unref" in abortTimer) (abortTimer as NodeJS.Timeout).unref();
|
|
345
|
+
|
|
346
|
+
const waitStart = Date.now();
|
|
347
|
+
const awaitResult = await awaitJob.execute("tc-await-esc-wait", { jobs: [jobId!] }, ac.signal, () => {}, undefined as never);
|
|
348
|
+
const elapsed = Date.now() - waitStart;
|
|
349
|
+
const awaitText = awaitResult.content.map((c: { type: string; text?: string }) => c.text ?? "").join("\n");
|
|
350
|
+
|
|
351
|
+
// (a) The wait ended promptly on ESC, not after the 120s default timeout.
|
|
352
|
+
assert.ok(elapsed < 5_000, `await should end promptly on ESC, took ${elapsed}ms`);
|
|
353
|
+
assert.match(awaitText, /interrupted/i, "aborted await should report the interruption");
|
|
354
|
+
|
|
355
|
+
// (b) The job and its real OS process are both STILL ALIVE — ESC ends the wait,
|
|
356
|
+
// it does not kill the job.
|
|
357
|
+
assert.equal(manager.getJob(jobId!)!.status, "running", "job should still be running after ESC");
|
|
358
|
+
assert.equal(isAlive(pgid), true, "the real background process must survive an aborted await_job");
|
|
359
|
+
},
|
|
360
|
+
);
|
|
@@ -10,11 +10,14 @@ import type { ToolDefinition } from "@gsd/pi-coding-agent";
|
|
|
10
10
|
import {
|
|
11
11
|
getShellConfig,
|
|
12
12
|
sanitizeCommand,
|
|
13
|
+
killProcessTree,
|
|
14
|
+
SIGKILL_GRACE_MS,
|
|
15
|
+
HARD_DEADLINE_MS,
|
|
13
16
|
DEFAULT_MAX_BYTES,
|
|
14
17
|
DEFAULT_MAX_LINES,
|
|
15
18
|
} from "@gsd/pi-coding-agent";
|
|
16
19
|
import { Type } from "@sinclair/typebox";
|
|
17
|
-
import { spawn
|
|
20
|
+
import { spawn } from "node:child_process";
|
|
18
21
|
import { createWriteStream } from "node:fs";
|
|
19
22
|
import { tmpdir } from "node:os";
|
|
20
23
|
import { join } from "node:path";
|
|
@@ -37,29 +40,6 @@ function getTempFilePath(): string {
|
|
|
37
40
|
return join(tmpdir(), `pi-async-bash-${id}.log`);
|
|
38
41
|
}
|
|
39
42
|
|
|
40
|
-
/**
|
|
41
|
-
* Kill a process and its children (cross-platform).
|
|
42
|
-
* Uses process group kill on Unix; taskkill /F /T on Windows.
|
|
43
|
-
*/
|
|
44
|
-
function killTree(pid: number): void {
|
|
45
|
-
if (process.platform === "win32") {
|
|
46
|
-
try {
|
|
47
|
-
spawnSync("taskkill", ["/F", "/T", "/PID", String(pid)], {
|
|
48
|
-
timeout: 5_000,
|
|
49
|
-
stdio: "ignore",
|
|
50
|
-
});
|
|
51
|
-
} catch {
|
|
52
|
-
try { process.kill(pid, "SIGTERM"); } catch { /* already exited */ }
|
|
53
|
-
}
|
|
54
|
-
} else {
|
|
55
|
-
try {
|
|
56
|
-
process.kill(-pid, "SIGTERM");
|
|
57
|
-
} catch {
|
|
58
|
-
try { process.kill(pid, "SIGTERM"); } catch { /* already exited */ }
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
43
|
export function createAsyncBashTool(
|
|
64
44
|
getManager: () => AsyncJobManager,
|
|
65
45
|
getCwd: () => string,
|
|
@@ -73,7 +53,7 @@ export function createAsyncBashTool(
|
|
|
73
53
|
`Output is truncated to the last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB.`,
|
|
74
54
|
promptSnippet: "Run a bash command in the background, returning a job ID immediately.",
|
|
75
55
|
promptGuidelines: [
|
|
76
|
-
"Use async_bash for
|
|
56
|
+
"Use async_bash for long-running builds, tests, installs, or operations that should run in the background so you can continue other work (the job ID lets you await_job later). Sync bash is uncapped — use async_bash when you want non-blocking behavior, not because of a timeout concern.",
|
|
77
57
|
"After starting async jobs, continue with other work and use await_job when you need the results.",
|
|
78
58
|
"await_job has a configurable timeout (default 120s) to prevent indefinite blocking — if it times out, jobs keep running and you can check again later.",
|
|
79
59
|
"For long-running processes (SSH, deploys, training) that may take minutes+, prefer async_bash with periodic await_job polling over a single long await.",
|
|
@@ -138,39 +118,27 @@ function executeBashInBackground(
|
|
|
138
118
|
|
|
139
119
|
let timedOut = false;
|
|
140
120
|
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
141
|
-
let sigkillHandle: ReturnType<typeof setTimeout> | undefined;
|
|
142
121
|
let hardDeadlineHandle: ReturnType<typeof setTimeout> | undefined;
|
|
143
122
|
|
|
144
|
-
/** Grace period (ms) between SIGTERM and SIGKILL. */
|
|
145
|
-
const SIGKILL_GRACE_MS = 5_000;
|
|
146
|
-
/** Hard deadline (ms) after SIGKILL to force-resolve the promise. */
|
|
147
|
-
const HARD_DEADLINE_MS = 3_000;
|
|
148
|
-
|
|
149
123
|
if (timeout !== undefined && timeout > 0) {
|
|
150
124
|
timeoutHandle = setTimeout(() => {
|
|
151
125
|
timedOut = true;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
: `Command timed out after ${timeout} seconds (force-killed)`,
|
|
169
|
-
);
|
|
170
|
-
}, HARD_DEADLINE_MS);
|
|
171
|
-
if (typeof hardDeadlineHandle === "object" && "unref" in hardDeadlineHandle) hardDeadlineHandle.unref();
|
|
172
|
-
}, SIGKILL_GRACE_MS);
|
|
173
|
-
if (typeof sigkillHandle === "object" && "unref" in sigkillHandle) sigkillHandle.unref();
|
|
126
|
+
// killProcessTree owns the SIGTERM -> grace -> SIGKILL escalation, so a
|
|
127
|
+
// SIGTERM-immune child is actually force-killed rather than left running.
|
|
128
|
+
if (child.pid) killProcessTree(child.pid);
|
|
129
|
+
|
|
130
|
+
// Hard deadline: a D-state (uninterruptible-I/O) child never emits 'close'
|
|
131
|
+
// even after SIGKILL, so force-resolve the promise rather than hang (#2186).
|
|
132
|
+
// Fires after the full grace window so SIGKILL has had its chance first.
|
|
133
|
+
hardDeadlineHandle = setTimeout(() => {
|
|
134
|
+
const output = Buffer.concat(chunks).toString("utf-8");
|
|
135
|
+
safeResolve(
|
|
136
|
+
output
|
|
137
|
+
? `${output}\n\nCommand timed out after ${timeout} seconds (force-killed)`
|
|
138
|
+
: `Command timed out after ${timeout} seconds (force-killed)`,
|
|
139
|
+
);
|
|
140
|
+
}, SIGKILL_GRACE_MS + HARD_DEADLINE_MS);
|
|
141
|
+
if (typeof hardDeadlineHandle === "object" && "unref" in hardDeadlineHandle) hardDeadlineHandle.unref();
|
|
174
142
|
}, timeout * 1000);
|
|
175
143
|
}
|
|
176
144
|
|
|
@@ -202,7 +170,18 @@ function executeBashInBackground(
|
|
|
202
170
|
if (child.stderr) child.stderr.on("data", onData);
|
|
203
171
|
|
|
204
172
|
const onAbort = () => {
|
|
205
|
-
if (child.pid)
|
|
173
|
+
if (child.pid) killProcessTree(child.pid);
|
|
174
|
+
// Arm the same hard-deadline force-resolve the timeout path uses, so a
|
|
175
|
+
// cancelled D-state child (never emits 'close' even after SIGKILL) can't
|
|
176
|
+
// hang the job promise forever. safeResolve is idempotent, so the real
|
|
177
|
+
// 'close' still wins if the child does exit.
|
|
178
|
+
if (!hardDeadlineHandle) {
|
|
179
|
+
hardDeadlineHandle = setTimeout(() => {
|
|
180
|
+
const output = Buffer.concat(chunks).toString("utf-8");
|
|
181
|
+
safeResolve(output ? `${output}\n\nCommand aborted (force-killed)` : "Command aborted (force-killed)");
|
|
182
|
+
}, SIGKILL_GRACE_MS + HARD_DEADLINE_MS);
|
|
183
|
+
if (typeof hardDeadlineHandle === "object" && "unref" in hardDeadlineHandle) hardDeadlineHandle.unref();
|
|
184
|
+
}
|
|
206
185
|
};
|
|
207
186
|
|
|
208
187
|
if (signal.aborted) {
|
|
@@ -213,7 +192,6 @@ function executeBashInBackground(
|
|
|
213
192
|
|
|
214
193
|
child.on("error", (err) => {
|
|
215
194
|
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
216
|
-
if (sigkillHandle) clearTimeout(sigkillHandle);
|
|
217
195
|
if (hardDeadlineHandle) clearTimeout(hardDeadlineHandle);
|
|
218
196
|
signal.removeEventListener("abort", onAbort);
|
|
219
197
|
safeReject(err);
|
|
@@ -221,7 +199,6 @@ function executeBashInBackground(
|
|
|
221
199
|
|
|
222
200
|
child.on("close", (code) => {
|
|
223
201
|
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
224
|
-
if (sigkillHandle) clearTimeout(sigkillHandle);
|
|
225
202
|
if (hardDeadlineHandle) clearTimeout(hardDeadlineHandle);
|
|
226
203
|
signal.removeEventListener("abort", onAbort);
|
|
227
204
|
if (spillStream) spillStream.end();
|
|
@@ -177,6 +177,145 @@ test("await_job suppresses follow-up for already-completed jobs (cross-turn case
|
|
|
177
177
|
manager.shutdown();
|
|
178
178
|
});
|
|
179
179
|
|
|
180
|
+
test("await_job aborts promptly when signal fires; job keeps running and is not suppressed", async () => {
|
|
181
|
+
const manager = new AsyncJobManager();
|
|
182
|
+
const tool = createAwaitTool(() => manager);
|
|
183
|
+
|
|
184
|
+
// Register a job that would run for 60s
|
|
185
|
+
const jobId = manager.register("bash", "long-job", async (_signal) => {
|
|
186
|
+
return new Promise<string>((resolve) => {
|
|
187
|
+
const timer = setTimeout(() => resolve("finally done"), 60_000);
|
|
188
|
+
if (typeof timer === "object" && "unref" in timer) timer.unref();
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const ac = new AbortController();
|
|
193
|
+
// Abort after ~100ms
|
|
194
|
+
const abortTimer = setTimeout(() => ac.abort(), 100);
|
|
195
|
+
if (typeof abortTimer === "object" && "unref" in abortTimer) (abortTimer as NodeJS.Timeout).unref();
|
|
196
|
+
|
|
197
|
+
const start = Date.now();
|
|
198
|
+
const result = await tool.execute("tc_abort1", { jobs: [jobId], timeout: 60 }, ac.signal, () => {}, undefined as never);
|
|
199
|
+
const elapsed = Date.now() - start;
|
|
200
|
+
const text = getTextFromResult(result);
|
|
201
|
+
|
|
202
|
+
// Should abort quickly (within ~100ms + overhead), not wait for full timeout
|
|
203
|
+
assert.ok(elapsed < 5_000, `Expected abort in ~100ms but took ${elapsed}ms`);
|
|
204
|
+
assert.match(text, /interrupted/i);
|
|
205
|
+
|
|
206
|
+
// Job should still be running (not killed)
|
|
207
|
+
const job = manager.getJob(jobId)!;
|
|
208
|
+
assert.equal(job.status, "running", "Job should still be running after abort");
|
|
209
|
+
// Job should NOT be marked awaited — results must resurface later
|
|
210
|
+
assert.ok(!job.awaited, "Job should not be suppressed after abort");
|
|
211
|
+
|
|
212
|
+
// Cleanup
|
|
213
|
+
manager.cancel(jobId);
|
|
214
|
+
manager.shutdown();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("after abort, a still-running job resurfaces via onJobComplete", async () => {
|
|
218
|
+
const followUps: string[] = [];
|
|
219
|
+
const manager = new AsyncJobManager({
|
|
220
|
+
onJobComplete: (job) => {
|
|
221
|
+
if (!job.awaited) followUps.push(job.id);
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
const tool = createAwaitTool(() => manager);
|
|
225
|
+
|
|
226
|
+
// Register a job that resolves after ~150ms
|
|
227
|
+
const jobId = manager.register("bash", "resurface-job", async () => {
|
|
228
|
+
return new Promise<string>((resolve) => {
|
|
229
|
+
const timer = setTimeout(() => resolve("done"), 150);
|
|
230
|
+
if (typeof timer === "object" && "unref" in timer) (timer as NodeJS.Timeout).unref();
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const ac = new AbortController();
|
|
235
|
+
// Abort the await at ~50ms (before the job completes at ~150ms)
|
|
236
|
+
const abortTimer = setTimeout(() => ac.abort(), 50);
|
|
237
|
+
if (typeof abortTimer === "object" && "unref" in abortTimer) (abortTimer as NodeJS.Timeout).unref();
|
|
238
|
+
|
|
239
|
+
const result = await tool.execute("tc_abort2", { jobs: [jobId], timeout: 10 }, ac.signal, () => {}, undefined as never);
|
|
240
|
+
const text = getTextFromResult(result);
|
|
241
|
+
assert.match(text, /interrupted/i);
|
|
242
|
+
|
|
243
|
+
// Wait longer than the job's natural completion time (150ms) + delivery timer (0ms) + buffer
|
|
244
|
+
await new Promise((r) => setTimeout(r, 350));
|
|
245
|
+
|
|
246
|
+
// The job should have completed and the follow-up should have fired
|
|
247
|
+
// (because we did NOT suppress it on abort)
|
|
248
|
+
assert.ok(followUps.includes(jobId), `Expected job ${jobId} to resurface via onJobComplete, but got: ${followUps.join(", ")}`);
|
|
249
|
+
|
|
250
|
+
manager.shutdown();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test("await_job does not reprint a job whose follow-up was already delivered (no duplicate-in-context)", async () => {
|
|
254
|
+
// Real cross-turn timing: a job completes in a prior turn, its setTimeout(0)
|
|
255
|
+
// follow-up FIRES (delivered to context), and only THEN does await_job run in a
|
|
256
|
+
// later turn. The earlier #3787 test masked this by advancing with setImmediate,
|
|
257
|
+
// which races ahead of setTimeout(0); a real turn boundary does not. await_job
|
|
258
|
+
// must acknowledge the already-delivered job tersely instead of reprinting its
|
|
259
|
+
// full output (which would duplicate it in context).
|
|
260
|
+
const followUps: string[] = [];
|
|
261
|
+
const manager = new AsyncJobManager({
|
|
262
|
+
onJobComplete: (job) => {
|
|
263
|
+
if (!job.awaited) followUps.push(job.id);
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
const tool = createAwaitTool(() => manager);
|
|
267
|
+
|
|
268
|
+
const jobId = manager.register("bash", "already-delivered-job", async () => "THE_RESULT_TEXT");
|
|
269
|
+
await manager.getJob(jobId)!.promise;
|
|
270
|
+
|
|
271
|
+
// Real macrotask gap — generously long so the setTimeout(0) delivery fires even
|
|
272
|
+
// if the CI event loop is briefly starved (a tight 25ms could race the precondition).
|
|
273
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
274
|
+
assert.equal(followUps.length, 1, "follow-up should have been delivered before the later-turn await_job");
|
|
275
|
+
|
|
276
|
+
const result = await tool.execute("tc_dup", { jobs: [jobId] }, noopSignal, () => {}, undefined as never);
|
|
277
|
+
const text = getTextFromResult(result);
|
|
278
|
+
|
|
279
|
+
// The full output must NOT be reprinted (it is already in context via the follow-up).
|
|
280
|
+
assert.ok(
|
|
281
|
+
!text.includes("THE_RESULT_TEXT"),
|
|
282
|
+
`await_job must not reprint already-delivered output, got:\n${text}`,
|
|
283
|
+
);
|
|
284
|
+
// It should still acknowledge the job so the agent knows the wait resolved.
|
|
285
|
+
assert.match(text, /already-delivered-job/);
|
|
286
|
+
// Single already-delivered job must use grammatical singular wording, not
|
|
287
|
+
// the plural form (regression: "These job ... their results ... they completed").
|
|
288
|
+
assert.match(text, /This job already finished and its result was shown above when it completed/);
|
|
289
|
+
assert.doesNotMatch(text, /These job\b/);
|
|
290
|
+
|
|
291
|
+
manager.shutdown();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("await_job still renders full output for a job consumed within the same turn (not yet delivered)", async () => {
|
|
295
|
+
// Within-turn case: await_job wins the race against the delivery timer, so the
|
|
296
|
+
// follow-up is suppressed and never delivered. Here await_job IS the only place
|
|
297
|
+
// the result surfaces, so it must render the full output.
|
|
298
|
+
const followUps: string[] = [];
|
|
299
|
+
const manager = new AsyncJobManager({
|
|
300
|
+
onJobComplete: (job) => {
|
|
301
|
+
if (!job.awaited) followUps.push(job.id);
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
const tool = createAwaitTool(() => manager);
|
|
305
|
+
|
|
306
|
+
const jobId = manager.register("bash", "within-turn-job", async () => {
|
|
307
|
+
return new Promise<string>((resolve) => setTimeout(() => resolve("WITHIN_TURN_OUTPUT"), 40));
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
const result = await tool.execute("tc_within", { jobs: [jobId] }, noopSignal, () => {}, undefined as never);
|
|
311
|
+
const text = getTextFromResult(result);
|
|
312
|
+
|
|
313
|
+
assert.equal(followUps.length, 0, "within-turn await must suppress the follow-up");
|
|
314
|
+
assert.match(text, /WITHIN_TURN_OUTPUT/, "within-turn await must render the full output inline");
|
|
315
|
+
|
|
316
|
+
manager.shutdown();
|
|
317
|
+
});
|
|
318
|
+
|
|
180
319
|
test("unawaited jobs still get follow-up delivery (#2248)", async () => {
|
|
181
320
|
const followUps: string[] = [];
|
|
182
321
|
const manager = new AsyncJobManager({
|