@opengsd/gsd-pi 1.3.0-dev.72e3af2a → 1.3.0-dev.738160b9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.js +17 -4
- package/dist/headless-answers.js +1 -1
- package/dist/headless-ui.d.ts +1 -1
- package/dist/headless-ui.js +1 -1
- package/dist/headless.js +1 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/claude-code-cli/models.js +9 -0
- package/dist/resources/extensions/gsd/auto/session.js +8 -0
- package/dist/resources/extensions/gsd/auto/verification-retry-policy.js +1 -1
- package/dist/resources/extensions/gsd/auto-budget.js +5 -1
- package/dist/resources/extensions/gsd/auto-dispatch.js +14 -1
- package/dist/resources/extensions/gsd/auto-post-unit.js +1 -1
- package/dist/resources/extensions/gsd/auto-prompts.js +3 -3
- package/dist/resources/extensions/gsd/auto-recovery.js +3 -3
- package/dist/resources/extensions/gsd/auto-verification.js +5 -2
- package/dist/resources/extensions/gsd/auto.js +19 -10
- package/dist/resources/extensions/gsd/bootstrap/core-session-tools.js +1 -0
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +5 -4
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +31 -2
- package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +32 -4
- package/dist/resources/extensions/gsd/closeout-consistency-gate.js +3 -1
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +1 -1
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands-usage.js +1 -1
- package/dist/resources/extensions/gsd/db/engine.js +4 -1
- package/dist/resources/extensions/gsd/db-memory-fts-schema.js +34 -0
- package/dist/resources/extensions/gsd/delegation-policy.js +1 -1
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +2 -2
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +192 -4
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +3 -0
- package/dist/resources/extensions/gsd/exec-sandbox.js +8 -2
- package/dist/resources/extensions/gsd/flat-phase-migration.js +45 -0
- package/dist/resources/extensions/gsd/git-service.js +57 -0
- package/dist/resources/extensions/gsd/gitignore.js +4 -1
- package/dist/resources/extensions/gsd/json-persistence.js +20 -0
- package/dist/resources/extensions/gsd/knowledge-backfill.js +18 -11
- package/dist/resources/extensions/gsd/memory-backfill.js +28 -21
- package/dist/resources/extensions/gsd/metrics.js +4 -4
- package/dist/resources/extensions/gsd/migrate-external.js +11 -2
- package/dist/resources/extensions/gsd/orphan-stash-audit.js +92 -16
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +2 -2
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +3 -3
- package/dist/resources/extensions/gsd/root-write-leak-guard.js +7 -1
- package/dist/resources/extensions/gsd/safety/destructive-guard.js +8 -1
- package/dist/resources/extensions/gsd/safety/evidence-collector.js +16 -1
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +5 -2
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +4 -2
- package/dist/resources/extensions/gsd/tools/plan-slice.js +59 -44
- package/dist/resources/extensions/gsd/tools/plan-task.js +28 -5
- package/dist/resources/extensions/gsd/undo.js +43 -6
- package/dist/resources/extensions/gsd/unit-registry.js +4 -2
- package/dist/resources/extensions/gsd/verification-gate.js +7 -0
- package/dist/resources/extensions/gsd/workflow-manifest.js +126 -13
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- 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/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 +9 -9
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- 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/rpc/rpc-client.d.ts +2 -0
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js +23 -5
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/workflow-tools.js +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +3 -2
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +46 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +38 -0
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- 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/extensions/claude-code-cli/models.ts +9 -0
- package/src/resources/extensions/claude-code-cli/tests/index.test.ts +14 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/session.ts +9 -0
- package/src/resources/extensions/gsd/auto/verification-retry-policy.ts +3 -1
- package/src/resources/extensions/gsd/auto-budget.ts +12 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +14 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +1 -1
- package/src/resources/extensions/gsd/auto-prompts.ts +3 -3
- package/src/resources/extensions/gsd/auto-recovery.ts +3 -3
- package/src/resources/extensions/gsd/auto-verification.ts +5 -1
- package/src/resources/extensions/gsd/auto.ts +23 -8
- package/src/resources/extensions/gsd/bootstrap/core-session-tools.ts +1 -0
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +5 -4
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +41 -2
- package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +34 -4
- package/src/resources/extensions/gsd/closeout-consistency-gate.ts +2 -1
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +1 -1
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands-usage.ts +1 -1
- package/src/resources/extensions/gsd/db/engine.ts +8 -1
- package/src/resources/extensions/gsd/db-memory-fts-schema.ts +45 -0
- package/src/resources/extensions/gsd/delegation-policy.ts +1 -1
- package/src/resources/extensions/gsd/docs/preferences-reference.md +2 -2
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +233 -5
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +3 -0
- package/src/resources/extensions/gsd/doctor-types.ts +3 -0
- package/src/resources/extensions/gsd/exec-sandbox.ts +7 -2
- package/src/resources/extensions/gsd/flat-phase-migration.ts +44 -0
- package/src/resources/extensions/gsd/git-service.ts +64 -0
- package/src/resources/extensions/gsd/gitignore.ts +4 -1
- package/src/resources/extensions/gsd/json-persistence.ts +20 -0
- package/src/resources/extensions/gsd/knowledge-backfill.ts +16 -10
- package/src/resources/extensions/gsd/memory-backfill.ts +26 -20
- package/src/resources/extensions/gsd/metrics.ts +4 -4
- package/src/resources/extensions/gsd/migrate-external.ts +8 -2
- package/src/resources/extensions/gsd/orphan-stash-audit.ts +108 -20
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +2 -2
- package/src/resources/extensions/gsd/prompts/plan-slice.md +3 -3
- package/src/resources/extensions/gsd/root-write-leak-guard.ts +13 -1
- package/src/resources/extensions/gsd/safety/destructive-guard.ts +9 -1
- package/src/resources/extensions/gsd/safety/evidence-collector.ts +16 -1
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +8 -2
- package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +14 -5
- package/src/resources/extensions/gsd/tests/auto-pause-double-entry-guard.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +182 -4
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +28 -0
- package/src/resources/extensions/gsd/tests/complete-slice-reopen-handoff.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/db-memory-fts-schema.test.ts +58 -3
- package/src/resources/extensions/gsd/tests/decisions-projection-from-memories.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/destructive-guard.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +143 -0
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +239 -2
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +37 -2
- package/src/resources/extensions/gsd/tests/gitignore-bg-shell-runtime.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +11 -2
- package/src/resources/extensions/gsd/tests/json-persistence-atomic.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/knowledge-backfill-projection.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +31 -1
- package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/metrics.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/migrate-external-worktree.test.ts +57 -0
- package/src/resources/extensions/gsd/tests/orphan-stash-audit.test.ts +91 -7
- package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +103 -4
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +44 -1
- package/src/resources/extensions/gsd/tests/post-mutation-hook.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +8 -6
- package/src/resources/extensions/gsd/tests/register-hooks-loop-guard-auto.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/root-write-leak-guard.test.ts +20 -1
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +8 -3
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +62 -5
- package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +6 -3
- package/src/resources/extensions/gsd/tests/undo.test.ts +74 -0
- package/src/resources/extensions/gsd/tests/uok-gitops-turn-action.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/verification-retry-policy.test.ts +69 -4
- package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +96 -15
- package/src/resources/extensions/gsd/tests/workflow-mcp-preflight.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +8 -4
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +6 -5
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -2
- package/src/resources/extensions/gsd/tools/plan-slice.ts +60 -47
- package/src/resources/extensions/gsd/tools/plan-task.ts +30 -5
- package/src/resources/extensions/gsd/undo.ts +47 -6
- package/src/resources/extensions/gsd/unit-registry.ts +4 -2
- package/src/resources/extensions/gsd/verification-gate.ts +8 -0
- package/src/resources/extensions/gsd/workflow-manifest.ts +141 -7
- /package/dist/web/standalone/.next/static/{O7xDYXO0r4zFhIzY1hrWV → 2A_WqqCS-EoYZxN9_v3d2}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{O7xDYXO0r4zFhIzY1hrWV → 2A_WqqCS-EoYZxN9_v3d2}/_ssgManifest.js +0 -0
package/README.md
CHANGED
|
@@ -158,7 +158,7 @@ Need help choosing settings? Use the [GSD Pi web configurator](https://pi.opengs
|
|
|
158
158
|
gsd
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
-
Run the setup flow, choose your preferred model provider, and open a project directory. GSD stores project planning and runtime state in `.gsd
|
|
161
|
+
Run the setup flow, choose your preferred model provider, and open a project directory. GSD stores project planning and runtime state in `.gsd/`, with gitignored sibling runtime directories such as `.gsd-backups/` for migration snapshots. Stale `.gsd-backups/migrate-*` snapshots are pruned after 30 days once the flat-phase `.gsd/phases/` migration is complete.
|
|
162
162
|
|
|
163
163
|
For a full first-run walkthrough, see [Getting Started With gsd-pi](./docs/user-docs/getting-started.md).
|
|
164
164
|
|
package/dist/cli.js
CHANGED
|
@@ -22,15 +22,23 @@ import { markStartup, printStartupTimings } from './startup-timings.js';
|
|
|
22
22
|
import { applyRtkProcessEnv, GSD_RTK_DISABLED_ENV, isTruthy } from './rtk-shared.js';
|
|
23
23
|
let piCodingAgentModulePromise;
|
|
24
24
|
let agentCoreModulePromise;
|
|
25
|
-
let
|
|
25
|
+
let interactiveModeModulePromise;
|
|
26
|
+
let printModeModulePromise;
|
|
27
|
+
let rpcModeModulePromise;
|
|
26
28
|
function loadPiCodingAgentModule() {
|
|
27
29
|
return (piCodingAgentModulePromise ??= import('@gsd/pi-coding-agent'));
|
|
28
30
|
}
|
|
29
31
|
function loadAgentCoreModule() {
|
|
30
32
|
return (agentCoreModulePromise ??= import('@gsd/agent-core'));
|
|
31
33
|
}
|
|
32
|
-
function
|
|
33
|
-
return (
|
|
34
|
+
function loadInteractiveModeModule() {
|
|
35
|
+
return (interactiveModeModulePromise ??= import('@gsd/agent-modes/modes/interactive/interactive-mode.js'));
|
|
36
|
+
}
|
|
37
|
+
function loadPrintModeModule() {
|
|
38
|
+
return (printModeModulePromise ??= import('@gsd/agent-modes/modes/print-mode.js'));
|
|
39
|
+
}
|
|
40
|
+
function loadRpcModeModule() {
|
|
41
|
+
return (rpcModeModulePromise ??= import('@gsd/agent-modes/modes/rpc/rpc-mode.js'));
|
|
34
42
|
}
|
|
35
43
|
// ---------------------------------------------------------------------------
|
|
36
44
|
// V8 compile cache — Node 22+ can cache compiled bytecode across runs,
|
|
@@ -511,7 +519,6 @@ if (!isPrintMode &&
|
|
|
511
519
|
}
|
|
512
520
|
const { AuthStorage, DefaultResourceLoader, ModelRegistry, SettingsManager, SessionManager, } = await loadPiCodingAgentModule();
|
|
513
521
|
const { createAgentSession } = await loadAgentCoreModule();
|
|
514
|
-
const { InteractiveMode, runPrintMode, runRpcMode } = await loadAgentModesModule();
|
|
515
522
|
markStartup('loadPiCodingAgent');
|
|
516
523
|
// Pi's tool bootstrap can mis-detect already-installed fd/rg on some systems
|
|
517
524
|
// because spawnSync(..., ["--version"]) returns EPERM despite a zero exit code.
|
|
@@ -530,6 +537,9 @@ markStartup('ModelRegistry');
|
|
|
530
537
|
const settingsManager = SettingsManager.create(process.cwd(), agentDir);
|
|
531
538
|
applySecurityOverrides(settingsManager);
|
|
532
539
|
markStartup('SettingsManager.create');
|
|
540
|
+
const { configureHttpDispatcher } = await import('@gsd/pi-coding-agent/core/http-dispatcher.js');
|
|
541
|
+
configureHttpDispatcher(settingsManager.getHttpIdleTimeoutMs());
|
|
542
|
+
markStartup('configureHttpDispatcher');
|
|
533
543
|
// Run onboarding wizard on first launch (no LLM provider configured)
|
|
534
544
|
if (!isPrintMode && shouldRunOnboarding(authStorage, settingsManager.getDefaultProvider())) {
|
|
535
545
|
await runOnboarding(authStorage);
|
|
@@ -651,6 +661,7 @@ if (isPrintMode) {
|
|
|
651
661
|
applyModelOverride(session, modelRegistry, cliFlags.model);
|
|
652
662
|
const mode = cliFlags.mode || 'text';
|
|
653
663
|
if (mode === 'rpc') {
|
|
664
|
+
const { runRpcMode } = await loadRpcModeModule();
|
|
654
665
|
printStartupTimings();
|
|
655
666
|
await runRpcMode(session);
|
|
656
667
|
process.exit(0);
|
|
@@ -674,6 +685,7 @@ if (isPrintMode) {
|
|
|
674
685
|
// MCP server runs until the transport closes; keep alive
|
|
675
686
|
await new Promise(() => { });
|
|
676
687
|
}
|
|
688
|
+
const { runPrintMode } = await loadPrintModeModule();
|
|
677
689
|
printStartupTimings();
|
|
678
690
|
await runPrintMode(session, {
|
|
679
691
|
mode: mode,
|
|
@@ -809,6 +821,7 @@ if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
|
809
821
|
: 'stdout is';
|
|
810
822
|
printNonTtyErrorAndExit(missing, true);
|
|
811
823
|
}
|
|
824
|
+
const { InteractiveMode } = await loadInteractiveModeModule();
|
|
812
825
|
const interactiveMode = new InteractiveMode(session);
|
|
813
826
|
markStartup('InteractiveMode');
|
|
814
827
|
printStartupTimings();
|
package/dist/headless-answers.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* default auto-responder or supervised mode.
|
|
7
7
|
*/
|
|
8
8
|
import { readFileSync } from 'node:fs';
|
|
9
|
-
import { serializeJsonLine } from '@gsd/agent-modes';
|
|
9
|
+
import { serializeJsonLine } from '@gsd/agent-modes/modes/rpc/jsonl.js';
|
|
10
10
|
import { canonicalHeadlessToolName } from './headless-events.js';
|
|
11
11
|
export function normalizeQuestionId(id) {
|
|
12
12
|
return id.trim().toLowerCase().replace(/[-\s]+/g, '_').replace(/_confirm$/, '');
|
package/dist/headless-ui.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* formats progress events for stderr output, and reads orchestrator
|
|
6
6
|
* commands from stdin in supervised mode.
|
|
7
7
|
*/
|
|
8
|
-
import { RpcClient } from '@gsd/agent-modes';
|
|
8
|
+
import type { RpcClient } from '@gsd/agent-modes/modes/rpc/rpc-client.js';
|
|
9
9
|
interface ExtensionUIRequest {
|
|
10
10
|
type: 'extension_ui_request';
|
|
11
11
|
id: string;
|
package/dist/headless-ui.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* formats progress events for stderr output, and reads orchestrator
|
|
6
6
|
* commands from stdin in supervised mode.
|
|
7
7
|
*/
|
|
8
|
-
import { attachJsonlLineReader } from '@gsd/agent-modes';
|
|
8
|
+
import { attachJsonlLineReader } from '@gsd/agent-modes/modes/rpc/jsonl.js';
|
|
9
9
|
// ---------------------------------------------------------------------------
|
|
10
10
|
// ANSI Color Helpers
|
|
11
11
|
// ---------------------------------------------------------------------------
|
package/dist/headless.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import { existsSync, mkdirSync, writeFileSync, writeSync } from 'node:fs';
|
|
15
15
|
import { join } from 'node:path';
|
|
16
16
|
import { resolve } from 'node:path';
|
|
17
|
-
import { RpcClient } from '@gsd/agent-modes';
|
|
17
|
+
import { RpcClient } from '@gsd/agent-modes/modes/rpc/rpc-client.js';
|
|
18
18
|
import { SessionManager } from '@gsd/pi-coding-agent';
|
|
19
19
|
import { getProjectSessionsDir } from './project-sessions.js';
|
|
20
20
|
import { loadAndValidateAnswerFile, AnswerInjector } from './headless-answers.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
bd26f10d1f8c134c
|
|
@@ -46,6 +46,15 @@ export const CLAUDE_CODE_MODELS = [
|
|
|
46
46
|
contextWindow: 1_000_000,
|
|
47
47
|
maxTokens: 128_000,
|
|
48
48
|
},
|
|
49
|
+
{
|
|
50
|
+
id: "claude-sonnet-5",
|
|
51
|
+
name: "Claude Sonnet 5 (via Claude Code)",
|
|
52
|
+
reasoning: true,
|
|
53
|
+
input: ["text", "image"],
|
|
54
|
+
cost: ZERO_COST,
|
|
55
|
+
contextWindow: 1_000_000,
|
|
56
|
+
maxTokens: 128_000,
|
|
57
|
+
},
|
|
49
58
|
{
|
|
50
59
|
id: "claude-sonnet-4-6",
|
|
51
60
|
name: "Claude Sonnet 4.6 (via Claude Code)",
|
|
@@ -124,6 +124,13 @@ export class AutoSession {
|
|
|
124
124
|
* the issues after MAX_PRE_EXEC_RETRIES re-attempts.
|
|
125
125
|
*/
|
|
126
126
|
preExecRetryCount = new Map();
|
|
127
|
+
/**
|
|
128
|
+
* Tracks how many times each slice unit has been re-dispatched to plan-slice
|
|
129
|
+
* because task plan files were missing. Keyed by unitId (e.g. "M001/S01").
|
|
130
|
+
* Separate from preExecRetryCount so pre-exec gate failures do not block or
|
|
131
|
+
* conflate with missing-task-plan recovery.
|
|
132
|
+
*/
|
|
133
|
+
missingTaskPlanRetryCount = new Map();
|
|
127
134
|
// ── Tool invocation errors (#2883) ──────────────────────────────────
|
|
128
135
|
/** Set when a GSD tool execution ends with isError due to malformed/truncated
|
|
129
136
|
* JSON arguments. Checked by postUnitPreVerification to break retry loops. */
|
|
@@ -310,6 +317,7 @@ export class AutoSession {
|
|
|
310
317
|
this.consecutiveCompleteBootstraps = 0;
|
|
311
318
|
this.lastPreExecFailure = null;
|
|
312
319
|
this.preExecRetryCount.clear();
|
|
320
|
+
this.missingTaskPlanRetryCount.clear();
|
|
313
321
|
this.lastToolInvocationError = null;
|
|
314
322
|
this.toolUnavailableRetries = 0;
|
|
315
323
|
this.lastUnitAgentEndMessages = null;
|
|
@@ -25,7 +25,7 @@ export function decideVerificationRetry(input) {
|
|
|
25
25
|
return { action: "pause", reason: "missing-retry-context" };
|
|
26
26
|
}
|
|
27
27
|
const key = verificationRetryKey(unitType, retryInfo.unitId);
|
|
28
|
-
const failureHash = hashVerificationFailureContext(retryInfo.failureContext);
|
|
28
|
+
const failureHash = hashVerificationFailureContext(retryInfo.signature ?? retryInfo.failureContext);
|
|
29
29
|
if (input.previousFailureHash === failureHash) {
|
|
30
30
|
return {
|
|
31
31
|
action: "pause",
|
|
@@ -68,6 +68,10 @@ export function resolveCompactionThresholdPercent(raw) {
|
|
|
68
68
|
const value = typeof raw === "number" && Number.isFinite(raw) && raw >= 0.5 && raw <= 0.95 ? raw : 0.6;
|
|
69
69
|
return value <= 1 ? value * 100 : value;
|
|
70
70
|
}
|
|
71
|
-
export
|
|
71
|
+
export const HARD_CONTEXT_REROOT_THRESHOLD_PERCENT = 90;
|
|
72
|
+
export function shouldWarnStepSessionForContext(contextPercent, compactionThresholdPercent) {
|
|
72
73
|
return getContextPauseAction(contextPercent, resolveCompactionThresholdPercent(compactionThresholdPercent)) === "pause";
|
|
73
74
|
}
|
|
75
|
+
export function shouldRerootStepSessionForContext(contextPercent) {
|
|
76
|
+
return getContextPauseAction(contextPercent, HARD_CONTEXT_REROOT_THRESHOLD_PERCENT) === "pause";
|
|
77
|
+
}
|
|
@@ -1238,6 +1238,7 @@ export const DISPATCH_RULES = [
|
|
|
1238
1238
|
const sid = state.activeSlice.id;
|
|
1239
1239
|
const sTitle = state.activeSlice.title;
|
|
1240
1240
|
const tid = state.activeTask.id;
|
|
1241
|
+
const unitId = `${mid}/${sid}`;
|
|
1241
1242
|
const artifactBasePath = resolveArtifactBasePath(basePath, mid, session);
|
|
1242
1243
|
// Guard: if the slice plan exists but the individual task plan files are
|
|
1243
1244
|
// missing, the planner created S##-PLAN.md with task entries but never
|
|
@@ -1263,6 +1264,17 @@ export const DISPATCH_RULES = [
|
|
|
1263
1264
|
if ((!taskPlanPath || !existsSync(taskPlanPath)) &&
|
|
1264
1265
|
!existsSync(projectionTaskPlanPath) &&
|
|
1265
1266
|
!tasksEmbeddedInSlicePlan) {
|
|
1267
|
+
const MAX_MISSING_TASK_PLAN_RETRIES = 2;
|
|
1268
|
+
const retryCount = session?.missingTaskPlanRetryCount?.get(unitId) ?? 0;
|
|
1269
|
+
if (retryCount >= MAX_MISSING_TASK_PLAN_RETRIES) {
|
|
1270
|
+
session?.missingTaskPlanRetryCount?.delete(unitId);
|
|
1271
|
+
return {
|
|
1272
|
+
action: "stop",
|
|
1273
|
+
reason: `Missing task-plan recovery failed ${retryCount} times for ${unitId} - manual intervention required. Task plan ${tid} is still missing after regenerating the slice plan. Fix the task-plan files manually, then run /gsd auto to resume.`,
|
|
1274
|
+
level: "error",
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
session?.missingTaskPlanRetryCount?.set(unitId, retryCount + 1);
|
|
1266
1278
|
if (isDebugEnabled()) {
|
|
1267
1279
|
const expectedTaskPlanPath = join(artifactBasePath, relTaskFile(artifactBasePath, mid, sid, tid, "PLAN"));
|
|
1268
1280
|
const originalProjectRoot = session?.originalBasePath || basePath;
|
|
@@ -1286,10 +1298,11 @@ export const DISPATCH_RULES = [
|
|
|
1286
1298
|
return {
|
|
1287
1299
|
action: "dispatch",
|
|
1288
1300
|
unitType: "plan-slice",
|
|
1289
|
-
unitId
|
|
1301
|
+
unitId,
|
|
1290
1302
|
prompt: await buildPlanSlicePrompt(mid, midTitle, sid, sTitle, basePath, undefined, { sessionContextWindow, modelRegistry, sessionProvider }),
|
|
1291
1303
|
};
|
|
1292
1304
|
}
|
|
1305
|
+
session?.missingTaskPlanRetryCount?.delete(unitId);
|
|
1293
1306
|
return null;
|
|
1294
1307
|
},
|
|
1295
1308
|
},
|
|
@@ -1834,7 +1834,7 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
1834
1834
|
saveCustomVerifyRetryCounts(s, { logFailure: err => debugLog("postUnit", { phase: "save-verify-retries-failed", error: err instanceof Error ? err.message : String(err) }) });
|
|
1835
1835
|
s.pendingVerificationRetry = {
|
|
1836
1836
|
unitId: s.currentUnit.id,
|
|
1837
|
-
failureContext:
|
|
1837
|
+
failureContext: failureDetails,
|
|
1838
1838
|
attempt,
|
|
1839
1839
|
};
|
|
1840
1840
|
debugLog("postUnit", { phase: "artifact-verify-retry", unitType: s.currentUnit.type, unitId: s.currentUnit.id, attempt });
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { loadFile, parseContinue, parseSummary, loadActiveOverrides, formatOverridesSection } from "./files.js";
|
|
11
11
|
import { hasVerdict, extractVerdict } from "./verdict-parser.js";
|
|
12
12
|
import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
|
|
13
|
-
import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksDir, resolveTaskFiles, resolveTaskFile, relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath, resolveGsdRootFile, relGsdRootFile, resolveRuntimeFile, } from "./paths.js";
|
|
13
|
+
import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksDir, resolveTaskFiles, resolveTaskFile, relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath, relTaskFile, resolveGsdRootFile, relGsdRootFile, resolveRuntimeFile, } from "./paths.js";
|
|
14
14
|
import { resolveInlineLevel, loadEffectiveGSDPreferences } from "./preferences.js";
|
|
15
15
|
import { isContextModeEnabled } from "./preferences-types.js";
|
|
16
16
|
import { parseRoadmap } from "./parsers-legacy.js";
|
|
@@ -2282,7 +2282,7 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
|
|
|
2282
2282
|
: "- (no prior tasks)";
|
|
2283
2283
|
const taskPlanPath = resolveTaskFile(base, mid, sid, tid, "PLAN");
|
|
2284
2284
|
const taskPlanContent = taskPlanPath ? await loadFile(taskPlanPath) : null;
|
|
2285
|
-
const taskPlanRelPath =
|
|
2285
|
+
const taskPlanRelPath = relTaskFile(base, mid, sid, tid, "PLAN");
|
|
2286
2286
|
const taskPlanContext = taskPlanContent
|
|
2287
2287
|
? [
|
|
2288
2288
|
"## Inlined Task Plan (authoritative local execution contract)",
|
|
@@ -3376,7 +3376,7 @@ opts) {
|
|
|
3376
3376
|
const depPaths = await getDependencyTaskSummaryPaths(mid, sid, tid, node?.dependsOn ?? [], base);
|
|
3377
3377
|
const taskPlanPath = resolveTaskFile(base, mid, sid, tid, "PLAN");
|
|
3378
3378
|
const taskPlanContent = taskPlanPath ? await loadFile(taskPlanPath) : null;
|
|
3379
|
-
const taskPlanRelPath =
|
|
3379
|
+
const taskPlanRelPath = relTaskFile(base, mid, sid, tid, "PLAN");
|
|
3380
3380
|
const taskPlanInline = taskPlanContent
|
|
3381
3381
|
? [
|
|
3382
3382
|
"## Inlined Task Plan (authoritative local execution contract)",
|
|
@@ -16,7 +16,7 @@ import { isDbAvailable, getTask, updateTaskStatus, updateSliceStatus, insertSlic
|
|
|
16
16
|
import { refreshWorkflowDatabaseFromDisk } from "./db-workspace.js";
|
|
17
17
|
import { getErrorMessage } from "./error-utils.js";
|
|
18
18
|
import { logWarning } from "./workflow-logger.js";
|
|
19
|
-
import { isClosedStatus } from "./status-guards.js";
|
|
19
|
+
import { isClosedStatus, isInactiveStatus } from "./status-guards.js";
|
|
20
20
|
import { resolveSlicePath, resolveTasksDir, resolveTaskFiles, relMilestoneFile, relSliceFile, clearPathCache, } from "./paths.js";
|
|
21
21
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
|
|
22
22
|
import { dirname } from "node:path";
|
|
@@ -194,7 +194,7 @@ export function writeReactiveExecuteBlocker(unitId, base, reason) {
|
|
|
194
194
|
transaction(() => {
|
|
195
195
|
for (const tid of summaryPresent) {
|
|
196
196
|
const task = getTask(mid, sid, tid);
|
|
197
|
-
if (!task ||
|
|
197
|
+
if (!task || isInactiveStatus(task.status)) {
|
|
198
198
|
unchangedTaskIds.push(tid);
|
|
199
199
|
continue;
|
|
200
200
|
}
|
|
@@ -203,7 +203,7 @@ export function writeReactiveExecuteBlocker(unitId, base, reason) {
|
|
|
203
203
|
}
|
|
204
204
|
for (const tid of summaryMissing) {
|
|
205
205
|
const task = getTask(mid, sid, tid);
|
|
206
|
-
if (!task ||
|
|
206
|
+
if (!task || isInactiveStatus(task.status)) {
|
|
207
207
|
unchangedTaskIds.push(tid);
|
|
208
208
|
continue;
|
|
209
209
|
}
|
|
@@ -9,7 +9,7 @@ import { isDbAvailable, getTask, getSliceTasks, getMilestoneSlices } from "./gsd
|
|
|
9
9
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
10
10
|
import { isClosedStatus } from "./status-guards.js";
|
|
11
11
|
import { loadFile } from "./files.js";
|
|
12
|
-
import { runVerificationGate, runVerificationGateForTargets, formatFailureContext, captureRuntimeErrors, runDependencyAudit, } from "./verification-gate.js";
|
|
12
|
+
import { runVerificationGate, runVerificationGateForTargets, formatFailureContext, formatFailureSignature, captureRuntimeErrors, runDependencyAudit, } from "./verification-gate.js";
|
|
13
13
|
import { writeVerificationJSON } from "./verification-evidence.js";
|
|
14
14
|
import { logWarning } from "./workflow-logger.js";
|
|
15
15
|
import { runPostExecutionChecks } from "./post-execution-checks.js";
|
|
@@ -686,10 +686,13 @@ export async function runPostUnitVerification(vctx, pauseAuto) {
|
|
|
686
686
|
ctx.ui.notify(`Unit ${s.currentUnit.id} cost spike detected (${unitCostUsd.toFixed(2)} vs avg ${rollingAvgUsd.toFixed(2)}) during verification retry; keeping verification failure as the authoritative blocker.`, "warning");
|
|
687
687
|
}
|
|
688
688
|
const nextAttempt = attempt + 1;
|
|
689
|
+
const failureContext = verdict.failureContext || formatFailureContext(result);
|
|
690
|
+
const failureSignature = formatFailureSignature(result);
|
|
689
691
|
s.verificationRetryCount.set(retryKey, nextAttempt);
|
|
690
692
|
s.pendingVerificationRetry = {
|
|
691
693
|
unitId: s.currentUnit.id,
|
|
692
|
-
failureContext
|
|
694
|
+
failureContext,
|
|
695
|
+
...(failureSignature ? { signature: failureSignature } : {}),
|
|
693
696
|
attempt: nextAttempt,
|
|
694
697
|
};
|
|
695
698
|
const failedCmds = result.checks
|
|
@@ -27,7 +27,7 @@ import { writeLock, clearLock, clearStaleWorkerLock, readCrashLock, isLockProces
|
|
|
27
27
|
import { acquireSessionLock, getSessionLockStatus, releaseSessionLock, updateSessionLock, } from "./session-lock.js";
|
|
28
28
|
import { resolveAutoSupervisorConfig, loadEffectiveGSDPreferences, loadEffectiveGSDPreferencesWithRegistry, resolveProfileAnchorProvider, getIsolationMode, resolveEffectiveUnitIsolationMode, } from "./preferences.js";
|
|
29
29
|
import { playNotificationBell, sendDesktopNotification } from "./notifications.js";
|
|
30
|
-
import { getBudgetAlertLevel, getNewBudgetAlertLevel, getBudgetEnforcementAction, resolveCompactionThresholdPercent, shouldRerootStepSessionForContext, } from "./auto-budget.js";
|
|
30
|
+
import { getBudgetAlertLevel, getNewBudgetAlertLevel, getBudgetEnforcementAction, HARD_CONTEXT_REROOT_THRESHOLD_PERCENT, resolveCompactionThresholdPercent, shouldRerootStepSessionForContext, shouldWarnStepSessionForContext, } from "./auto-budget.js";
|
|
31
31
|
import { markToolStart as _markToolStart, markToolEnd as _markToolEnd, getOldestInFlightToolAgeMs as _getOldestInFlightToolAgeMs, clearInFlightTools, isToolInvocationError, isQueuedUserMessageSkip, isDeterministicPolicyError, } from "./auto-tool-tracking.js";
|
|
32
32
|
import { closeoutUnit } from "./auto-unit-closeout.js";
|
|
33
33
|
import { selectAndApplyModel, resolveModelId, clearToolBaseline } from "./auto-model-selection.js";
|
|
@@ -369,7 +369,7 @@ function getEffectiveUnitIsolationMode(basePath) {
|
|
|
369
369
|
* running suspiciously long (e.g., a Bash command hung because `&` kept stdout open).
|
|
370
370
|
*/
|
|
371
371
|
// Re-export budget utilities for external consumers
|
|
372
|
-
export { getBudgetAlertLevel, getNewBudgetAlertLevel, getBudgetEnforcementAction, getContextPauseAction, resolveCompactionThresholdPercent, shouldRerootStepSessionForContext, } from "./auto-budget.js";
|
|
372
|
+
export { getBudgetAlertLevel, getNewBudgetAlertLevel, getBudgetEnforcementAction, getContextPauseAction, resolveCompactionThresholdPercent, shouldRerootStepSessionForContext, shouldWarnStepSessionForContext, } from "./auto-budget.js";
|
|
373
373
|
function closeOutSignalInterruptedUnit(currentBasePath) {
|
|
374
374
|
const currentUnit = s.currentUnit;
|
|
375
375
|
if (!currentUnit)
|
|
@@ -904,9 +904,10 @@ export async function rerootCommandSession(cmdCtx, workspaceRoot) {
|
|
|
904
904
|
}
|
|
905
905
|
}
|
|
906
906
|
/**
|
|
907
|
-
* After a completed step-mode unit,
|
|
908
|
-
*
|
|
909
|
-
* re-
|
|
907
|
+
* After a completed step-mode unit, warn when usage reaches the configurable
|
|
908
|
+
* soft context threshold. At the fixed hard boundary, write a context-mode
|
|
909
|
+
* snapshot and re-root the visible command session while leaving the NEXT
|
|
910
|
+
* progress surface.
|
|
910
911
|
*/
|
|
911
912
|
export async function maybeRerootStepSessionForHighContext(ctx, workspaceRoot, cmdCtx, originalBasePath) {
|
|
912
913
|
if (!cmdCtx)
|
|
@@ -915,18 +916,24 @@ export async function maybeRerootStepSessionForHighContext(ctx, workspaceRoot, c
|
|
|
915
916
|
const { loadEffectiveGSDPreferences } = await import("./preferences.js");
|
|
916
917
|
const prefs = loadEffectiveGSDPreferences(workspaceRoot);
|
|
917
918
|
const compactionThreshold = prefs?.preferences.context_management?.compaction_threshold_percent;
|
|
918
|
-
|
|
919
|
+
const shouldWarn = shouldWarnStepSessionForContext(contextPercent, compactionThreshold);
|
|
920
|
+
const shouldReroot = shouldRerootStepSessionForContext(contextPercent);
|
|
921
|
+
if (!shouldWarn && !shouldReroot) {
|
|
922
|
+
return { rerooted: false };
|
|
923
|
+
}
|
|
924
|
+
const displayPercent = contextPercent.toFixed(1);
|
|
925
|
+
const thresholdDisplay = resolveCompactionThresholdPercent(compactionThreshold).toFixed(0);
|
|
926
|
+
if (!shouldReroot) {
|
|
927
|
+
ctx.ui.notify(`Step complete — context at ${displayPercent}% (soft threshold: ${thresholdDisplay}%). Use /compact when convenient; continuing in this session is still safe.`, "info");
|
|
919
928
|
return { rerooted: false };
|
|
920
929
|
}
|
|
921
930
|
const { resolveWorktreeProjectRoot } = await import("./worktree-root.js");
|
|
922
931
|
const snapshotBase = resolveWorktreeProjectRoot(workspaceRoot, originalBasePath);
|
|
923
932
|
const { writeContextModeCompactionSnapshot } = await import("./context-mode-snapshot.js");
|
|
924
933
|
await writeContextModeCompactionSnapshot(snapshotBase);
|
|
925
|
-
const displayPercent = contextPercent.toFixed(1);
|
|
926
|
-
const thresholdDisplay = resolveCompactionThresholdPercent(compactionThreshold).toFixed(0);
|
|
927
934
|
const result = await rerootCommandSession(cmdCtx, workspaceRoot);
|
|
928
935
|
if (result.status === "ok") {
|
|
929
|
-
ctx.ui.notify(`Step complete — context at ${displayPercent}% (threshold: ${
|
|
936
|
+
ctx.ui.notify(`Step complete — context at ${displayPercent}% (hard threshold: ${HARD_CONTEXT_REROOT_THRESHOLD_PERCENT}%). Fresh session ready for /gsd next.`, "info");
|
|
930
937
|
return { rerooted: true };
|
|
931
938
|
}
|
|
932
939
|
if (result.status === "cancelled") {
|
|
@@ -1545,7 +1552,7 @@ export function _selectStopAutoWorktreeExit(args) {
|
|
|
1545
1552
|
/**
|
|
1546
1553
|
* Pause auto-mode without destroying state. Context is preserved.
|
|
1547
1554
|
* The user can interact with the agent, then `/gsd auto` resumes
|
|
1548
|
-
* from disk state. Called when the user presses Escape
|
|
1555
|
+
* from disk state. Called when the user presses Escape or runs `/gsd pause`.
|
|
1549
1556
|
*/
|
|
1550
1557
|
export async function pauseAuto(ctx, _pi, _errorContext, options = {}) {
|
|
1551
1558
|
if (!s.active)
|
|
@@ -1564,6 +1571,8 @@ export async function pauseAuto(ctx, _pi, _errorContext, options = {}) {
|
|
|
1564
1571
|
].filter((path) => Boolean(path)));
|
|
1565
1572
|
s.active = false;
|
|
1566
1573
|
s.paused = true;
|
|
1574
|
+
if (options.abortActiveTurn && ctx && !ctx.isIdle())
|
|
1575
|
+
ctx.abort();
|
|
1567
1576
|
clearUnitTimeout();
|
|
1568
1577
|
stopAutoCommandPolling();
|
|
1569
1578
|
// Flush queued follow-up messages (#3512).
|
|
@@ -654,8 +654,9 @@ export function registerDbTools(pi) {
|
|
|
654
654
|
promptSnippet: "Plan a slice via DB write + PLAN render + cache invalidation",
|
|
655
655
|
promptGuidelines: [
|
|
656
656
|
"Use gsd_plan_slice for slice planning instead of writing S##-PLAN.md or task PLAN files directly.",
|
|
657
|
-
"
|
|
658
|
-
"
|
|
657
|
+
"For incremental planning, call gsd_plan_slice with slice metadata only, then call gsd_plan_task once per task.",
|
|
658
|
+
"When tasks is omitted or empty, the tool writes only slice planning metadata and preserves existing tasks.",
|
|
659
|
+
"When tasks is non-empty, the tool validates input, requires an existing parent slice, writes slice/task planning data, renders PLAN.md and task plan files from DB, and clears both state and parse caches after success.",
|
|
659
660
|
"Use the canonical name gsd_plan_slice; gsd_slice_plan is only an alias.",
|
|
660
661
|
],
|
|
661
662
|
parameters: Type.Object({
|
|
@@ -663,7 +664,7 @@ export function registerDbTools(pi) {
|
|
|
663
664
|
milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
|
|
664
665
|
sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
|
|
665
666
|
goal: Type.String({ description: "Slice goal" }),
|
|
666
|
-
tasks: Type.Array(Type.Object({
|
|
667
|
+
tasks: Type.Optional(Type.Array(Type.Object({
|
|
667
668
|
taskId: Type.String({ description: "Task ID (e.g. T01)" }),
|
|
668
669
|
title: Type.String({ description: "Task title" }),
|
|
669
670
|
description: Type.String({ description: "Task description / steps block" }),
|
|
@@ -673,7 +674,7 @@ export function registerDbTools(pi) {
|
|
|
673
674
|
inputs: Type.Array(Type.String(), { description: "Array<string> of input files or references; pass [\"path\"] or [], never a single string" }),
|
|
674
675
|
expectedOutput: Type.Array(Type.String(), { description: "Array<string> of files this task creates or overwrites; pass [\"path\"] or [], never prose or a single string" }),
|
|
675
676
|
observabilityImpact: Type.Optional(Type.String({ description: "Task observability impact" })),
|
|
676
|
-
}), { description: "
|
|
677
|
+
}), { description: "Optional full task replacement for the slice. Omit for incremental planning, then call gsd_plan_task once per task." })),
|
|
677
678
|
// ── Enrichment metadata (optional — defaults to empty) ────────────
|
|
678
679
|
successCriteria: Type.Optional(Type.String({ description: "Slice success criteria block" })),
|
|
679
680
|
proofLevel: Type.Optional(Type.String({ description: "Slice proof level" })),
|
|
@@ -14,6 +14,7 @@ import { registerShortcuts } from "./register-shortcuts.js";
|
|
|
14
14
|
import { writeCrashLog } from "./crash-log.js";
|
|
15
15
|
import { logWarning, isGsdExtensionStderrEnabled } from "../workflow-logger.js";
|
|
16
16
|
import { UNIT_TOOL_CONTRACTS } from "../unit-tool-contracts.js";
|
|
17
|
+
import { installManifestFlushOnProcessTeardown } from "../workflow-manifest.js";
|
|
17
18
|
// Static import so cmux event listeners are registered synchronously during
|
|
18
19
|
// extension bootstrap. Prior implementation used `void import().then()` which
|
|
19
20
|
// queued listener registration as a microtask — any CMUX_CHANNELS emit fired
|
|
@@ -177,6 +178,7 @@ export function registerGsdExtension(pi) {
|
|
|
177
178
|
process.stderr.write(`[gsd] Failed to bootstrap hook-emitter bridge: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
|
|
178
179
|
});
|
|
179
180
|
installEpipeGuard();
|
|
181
|
+
installManifestFlushOnProcessTeardown();
|
|
180
182
|
// Ecosystem handlers captured by the GSDExtensionAPI wrapper for the
|
|
181
183
|
// GSD-owned `before_agent_start` dispatch step (#3338).
|
|
182
184
|
const ecosystemHandlers = [];
|
|
@@ -14,7 +14,7 @@ import { isBlockedStateFile, isBashWriteToStateFile, BLOCKED_WRITE_ERROR } from
|
|
|
14
14
|
import { loadFile, saveFile, formatContinue } from "../files.js";
|
|
15
15
|
import { clearAutoCompletionStopInProgress, clearToolInvocationError, getAutoRuntimeSnapshot, getSourceObservationStore, isAutoActive, isAutoCompletionStopInProgress, isAutoPaused, isInteractiveElicitationInFlight, markToolEnd, markToolStart, recordAutoToolSurfaceSnapshot, recordToolInvocationError, } from "../auto-runtime-state.js";
|
|
16
16
|
import { applyProviderPayloadPolicy } from "../provider-payload-policy.js";
|
|
17
|
-
import { checkToolCallLoop, resetToolCallLoopGuard } from "./tool-call-loop-guard.js";
|
|
17
|
+
import { checkToolCallLoop, recordToolCallLoopMutation, resetToolCallLoopGuard } from "./tool-call-loop-guard.js";
|
|
18
18
|
import { MINIMAL_AUTO_BASE_TOOL_NAMES } from "./core-session-tools.js";
|
|
19
19
|
import { maybePauseAutoForApprovalGate, resetPendingGatePauseGuard } from "./pending-gate-pause.js";
|
|
20
20
|
import { saveActivityLog } from "../activity-log.js";
|
|
@@ -43,6 +43,7 @@ import { supportsSourceObservationsForUnit } from "../source-observations.js";
|
|
|
43
43
|
import { clearPendingAutoStart } from "../pending-auto-start.js";
|
|
44
44
|
import { resolveWorkflowToolBasePath } from "./dynamic-tools.js";
|
|
45
45
|
import { getRequiredWorkflowToolsForUnit } from "../unit-tool-contracts.js";
|
|
46
|
+
import { flushAllManifests } from "../workflow-manifest.js";
|
|
46
47
|
let approvalQuestionAbortInFlight = false;
|
|
47
48
|
async function loadWelcomeScreenModule() {
|
|
48
49
|
const candidates = [];
|
|
@@ -435,6 +436,16 @@ function deferApprovalGateFromSnapshot(gateId, basePath, snapshot) {
|
|
|
435
436
|
function contextBasePath(ctx) {
|
|
436
437
|
return typeof ctx?.cwd === "string" ? ctx.cwd : process.cwd();
|
|
437
438
|
}
|
|
439
|
+
const LOOP_GUARD_INTERACTIVE_INSTRUCTIONS = [
|
|
440
|
+
"Do not retry this tool or call other tools this turn — stop and respond to the user in text.",
|
|
441
|
+
"Do not retry this tool or pivot to other tools this turn — stop and respond to the user in text.",
|
|
442
|
+
];
|
|
443
|
+
const LOOP_GUARD_AUTO_INSTRUCTION = "Do not re-issue this blocked tool. In /gsd auto, stop tool calls for this turn and return control to the auto-mode recovery/replan path.";
|
|
444
|
+
function formatLoopGuardBlockReason(reason) {
|
|
445
|
+
if (!reason || !getAutoRuntimeSnapshot().active)
|
|
446
|
+
return reason;
|
|
447
|
+
return LOOP_GUARD_INTERACTIVE_INSTRUCTIONS.reduce((formatted, instruction) => formatted.replace(instruction, LOOP_GUARD_AUTO_INSTRUCTION), reason);
|
|
448
|
+
}
|
|
438
449
|
function beginSourceObservationStoreForCurrentUnit(ctx) {
|
|
439
450
|
if (!isAutoActive())
|
|
440
451
|
return null;
|
|
@@ -703,6 +714,12 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
703
714
|
}
|
|
704
715
|
}
|
|
705
716
|
}
|
|
717
|
+
const projectRoot = resolveWorktreeProjectRoot(basePath);
|
|
718
|
+
const { pruneStaleFlatPhaseBackups } = await import("../flat-phase-migration.js");
|
|
719
|
+
const pruned = pruneStaleFlatPhaseBackups(projectRoot);
|
|
720
|
+
if (pruned > 0) {
|
|
721
|
+
safetyLogWarning("bootstrap", `pruned ${pruned} stale flat-phase migration backup(s) from .gsd-backups/ (retention exceeded)`);
|
|
722
|
+
}
|
|
706
723
|
}
|
|
707
724
|
catch (err) {
|
|
708
725
|
safetyLogWarning("bootstrap", `flat-phase migration: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -860,6 +877,17 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
860
877
|
const { handleAgentEnd } = await import("./agent-end-recovery.js");
|
|
861
878
|
const agentEndBasePath = contextBasePath(ctx);
|
|
862
879
|
try {
|
|
880
|
+
// The manifest is a non-critical projection (the append-only event log is
|
|
881
|
+
// the authoritative recovery source), so a flush failure must not skip
|
|
882
|
+
// agent_end recovery. drainManifestWrites still propagates write failures
|
|
883
|
+
// to explicit flush callers; here we deliberately log and continue so
|
|
884
|
+
// handleAgentEnd (and the finally below) always run.
|
|
885
|
+
try {
|
|
886
|
+
await flushAllManifests();
|
|
887
|
+
}
|
|
888
|
+
catch (err) {
|
|
889
|
+
safetyLogWarning("manifest", `flushAllManifests on agent_end failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
890
|
+
}
|
|
863
891
|
await handleAgentEnd(pi, event, ctx);
|
|
864
892
|
}
|
|
865
893
|
finally {
|
|
@@ -1049,7 +1077,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
1049
1077
|
// ── Loop guard: block repeated identical tool calls ──
|
|
1050
1078
|
const loopCheck = checkToolCallLoop(toolName, event.input);
|
|
1051
1079
|
if (loopCheck.block) {
|
|
1052
|
-
return { block: true, reason: loopCheck.reason };
|
|
1080
|
+
return { block: true, reason: formatLoopGuardBlockReason(loopCheck.reason) };
|
|
1053
1081
|
}
|
|
1054
1082
|
const deferredGateGuard = shouldBlockDeferredApprovalTool(toolName, event.input, discussionBasePath);
|
|
1055
1083
|
if (deferredGateGuard.block) {
|
|
@@ -1255,6 +1283,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
1255
1283
|
}
|
|
1256
1284
|
}
|
|
1257
1285
|
if (!event.isError) {
|
|
1286
|
+
recordToolCallLoopMutation(toolName);
|
|
1258
1287
|
refreshSourceObservationAfterMutation(toolName, event.input, ctx);
|
|
1259
1288
|
clearSourceObservationsAfterShell(toolName);
|
|
1260
1289
|
}
|
|
@@ -14,10 +14,12 @@
|
|
|
14
14
|
* to stop tooling for the rest of that turn and answer in text.
|
|
15
15
|
*
|
|
16
16
|
* The per-tool-name check (#783 Brief C) tracks call counts within a
|
|
17
|
-
* turn regardless of args
|
|
17
|
+
* turn regardless of args, decaying after successful state-mutating tools.
|
|
18
|
+
* This catches improvisation
|
|
18
19
|
* loops where the model attempts the same missing workflow tool through
|
|
19
20
|
* varied surfaces (bash → `node -e` → CLI), each with a different
|
|
20
|
-
* signature, so the identical-args streak never trips
|
|
21
|
+
* signature, so the identical-args streak never trips, while allowing
|
|
22
|
+
* tool-heavy turns that are making file-mutation progress. Whichever guard
|
|
21
23
|
* trips first blocks.
|
|
22
24
|
*/
|
|
23
25
|
import { createHash } from "node:crypto";
|
|
@@ -38,12 +40,16 @@ const MAX_CONSECUTIVE_STRICT = 1;
|
|
|
38
40
|
*/
|
|
39
41
|
const PER_TOOL_DEFAULT_CAP = 6;
|
|
40
42
|
const PER_TOOL_REPEATABLE_CAP = 15;
|
|
43
|
+
const PER_TOOL_CAP_EXEMPT_TOOLS = new Set(["find", "glob", "grep", "ls", "read", "search_and_read"]);
|
|
44
|
+
const STATE_MUTATING_TOOL_SET = new Set(["edit", "write", "multi_edit", "notebook_edit"]);
|
|
41
45
|
let consecutiveCount = 0;
|
|
42
46
|
let lastSignature = "";
|
|
43
47
|
let lastToolName = "";
|
|
44
48
|
let enabled = true;
|
|
45
49
|
/** Per-tool-name call counts within the current turn (#783 Brief C). */
|
|
46
50
|
const perToolCounts = new Map();
|
|
51
|
+
let mutationEpoch = 0;
|
|
52
|
+
const perToolLastMutationEpoch = new Map();
|
|
47
53
|
/** Hash tool name + args into a compact signature for comparison. */
|
|
48
54
|
function hashToolCall(toolName, args) {
|
|
49
55
|
const h = createHash("sha256");
|
|
@@ -67,7 +73,8 @@ function hashToolCall(toolName, args) {
|
|
|
67
73
|
* 1. Identical-signature streak (MAX_CONSECUTIVE_IDENTICAL_CALLS, strict for
|
|
68
74
|
* ask_user_questions).
|
|
69
75
|
* 2. Per-tool-name cap (PER_TOOL_DEFAULT_CAP / PER_TOOL_REPEATABLE_CAP),
|
|
70
|
-
* independent of args
|
|
76
|
+
* independent of args, reset after file-mutation progress — catches
|
|
77
|
+
* improvisation loops (#783).
|
|
71
78
|
*/
|
|
72
79
|
export function checkToolCallLoop(toolName, args) {
|
|
73
80
|
if (!enabled)
|
|
@@ -97,8 +104,17 @@ export function checkToolCallLoop(toolName, args) {
|
|
|
97
104
|
// ── Guard 2: per-tool-name cap, independent of args (#783 Brief C) ──
|
|
98
105
|
// Catches improvisation loops where the same tool is invoked many times with
|
|
99
106
|
// varied args (e.g. retrying a missing workflow tool via bash/node -e/CLI).
|
|
100
|
-
const
|
|
107
|
+
const priorPerToolCount = (perToolLastMutationEpoch.get(toolName) ?? mutationEpoch) < mutationEpoch
|
|
108
|
+
? 0
|
|
109
|
+
: (perToolCounts.get(toolName) ?? 0);
|
|
110
|
+
const perToolCount = priorPerToolCount + 1;
|
|
101
111
|
perToolCounts.set(toolName, perToolCount);
|
|
112
|
+
perToolLastMutationEpoch.set(toolName, mutationEpoch);
|
|
113
|
+
// Read-only navigation tools are normal context gathering; Guard 1 still
|
|
114
|
+
// catches true reread loops with identical arguments.
|
|
115
|
+
if (PER_TOOL_CAP_EXEMPT_TOOLS.has(toolName)) {
|
|
116
|
+
return { block: false, count: consecutiveCount };
|
|
117
|
+
}
|
|
102
118
|
const perToolCap = INHERENTLY_REPEATABLE_TOOL_SET.has(toolName)
|
|
103
119
|
? PER_TOOL_REPEATABLE_CAP
|
|
104
120
|
: PER_TOOL_DEFAULT_CAP;
|
|
@@ -114,6 +130,14 @@ export function checkToolCallLoop(toolName, args) {
|
|
|
114
130
|
}
|
|
115
131
|
return { block: false, count: consecutiveCount };
|
|
116
132
|
}
|
|
133
|
+
/** Record successful mutation progress so Guard 2 can decay on the next count. */
|
|
134
|
+
export function recordToolCallLoopMutation(toolName) {
|
|
135
|
+
if (!enabled)
|
|
136
|
+
return;
|
|
137
|
+
if (!STATE_MUTATING_TOOL_SET.has(toolName))
|
|
138
|
+
return;
|
|
139
|
+
mutationEpoch++;
|
|
140
|
+
}
|
|
117
141
|
/** Reset the guard state. Call at agent turn boundaries. */
|
|
118
142
|
export function resetToolCallLoopGuard() {
|
|
119
143
|
consecutiveCount = 0;
|
|
@@ -121,6 +145,8 @@ export function resetToolCallLoopGuard() {
|
|
|
121
145
|
lastToolName = "";
|
|
122
146
|
enabled = true;
|
|
123
147
|
perToolCounts.clear();
|
|
148
|
+
mutationEpoch = 0;
|
|
149
|
+
perToolLastMutationEpoch.clear();
|
|
124
150
|
}
|
|
125
151
|
/** Disable the guard (e.g. during shutdown). */
|
|
126
152
|
export function disableToolCallLoopGuard() {
|
|
@@ -129,6 +155,8 @@ export function disableToolCallLoopGuard() {
|
|
|
129
155
|
lastSignature = "";
|
|
130
156
|
lastToolName = "";
|
|
131
157
|
perToolCounts.clear();
|
|
158
|
+
mutationEpoch = 0;
|
|
159
|
+
perToolLastMutationEpoch.clear();
|
|
132
160
|
}
|
|
133
161
|
/** Get current consecutive count for diagnostics. */
|
|
134
162
|
export function getToolCallLoopCount() {
|