gsd-pi 2.76.0-dev.b072ebb73 → 2.76.0-dev.fe143342a
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/mcp-server.d.ts +7 -0
- package/dist/mcp-server.js +35 -1
- package/dist/resource-loader.d.ts +1 -1
- package/dist/resource-loader.js +2 -8
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +66 -4
- package/dist/resources/extensions/gsd/auto/phases.js +4 -1
- package/dist/resources/extensions/gsd/auto/session.js +4 -0
- package/dist/resources/extensions/gsd/auto-model-selection.js +39 -13
- package/dist/resources/extensions/gsd/auto-start.js +39 -21
- package/dist/resources/extensions/gsd/auto.js +15 -12
- package/dist/resources/extensions/gsd/blocked-models.js +68 -0
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +76 -0
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +39 -9
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +93 -0
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +35 -0
- package/dist/resources/extensions/gsd/compaction-snapshot.js +121 -0
- package/dist/resources/extensions/gsd/complexity-classifier.js +5 -3
- package/dist/resources/extensions/gsd/error-classifier.js +31 -3
- package/dist/resources/extensions/gsd/exec-history.js +120 -0
- package/dist/resources/extensions/gsd/exec-sandbox.js +258 -0
- package/dist/resources/extensions/gsd/gsd-db.js +62 -4
- package/dist/resources/extensions/gsd/init-wizard.js +15 -1
- package/dist/resources/extensions/gsd/key-manager.js +6 -0
- package/dist/resources/extensions/gsd/pre-execution-checks.js +13 -3
- package/dist/resources/extensions/gsd/preferences-types.js +9 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +83 -0
- package/dist/resources/extensions/gsd/preferences.js +17 -17
- package/dist/resources/extensions/gsd/prompt-loader.js +22 -7
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +1 -1
- package/dist/resources/extensions/gsd/tools/exec-search-tool.js +59 -0
- package/dist/resources/extensions/gsd/tools/exec-tool.js +126 -0
- package/dist/resources/extensions/gsd/tools/resume-tool.js +23 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +3 -0
- package/dist/resources/extensions/search-the-web/command-search-provider.js +5 -4
- package/dist/resources/extensions/search-the-web/native-search.js +45 -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 +8 -8
- 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/required-server-files.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/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 +8 -8
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- 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/server.js +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +64 -25
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/workflow-tools.test.ts +146 -1
- package/packages/mcp-server/src/workflow-tools.ts +84 -43
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +60 -15
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/providers/think-tag-parser.d.ts +17 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.js +75 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.js.map +1 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.js +41 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.js.map +1 -0
- package/packages/pi-ai/src/providers/openai-completions.ts +57 -16
- package/packages/pi-ai/src/providers/think-tag-parser.test.ts +44 -0
- package/packages/pi-ai/src/providers/think-tag-parser.ts +94 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts +3 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.js +92 -12
- package/packages/pi-coding-agent/dist/core/model-discovery.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js +16 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js +61 -1
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +5 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +76 -10
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.js +49 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.test.js +67 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js +9 -5
- package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.test.js +25 -1
- package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +5 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +13 -7
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts +7 -6
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +29 -21
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/packages/pi-coding-agent/src/core/model-discovery.test.ts +19 -0
- package/packages/pi-coding-agent/src/core/model-discovery.ts +99 -12
- package/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts +75 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +86 -10
- package/packages/pi-coding-agent/src/core/redact-secrets.test.ts +86 -0
- package/packages/pi-coding-agent/src/core/redact-secrets.ts +58 -0
- package/packages/pi-coding-agent/src/core/session-manager.test.ts +36 -1
- package/packages/pi-coding-agent/src/core/session-manager.ts +9 -5
- package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +6 -6
- package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +16 -7
- package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +36 -22
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/scripts/link-workspace-packages.cjs +1 -0
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +67 -4
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +137 -2
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/phases.ts +4 -0
- package/src/resources/extensions/gsd/auto/session.ts +7 -1
- package/src/resources/extensions/gsd/auto-model-selection.ts +50 -12
- package/src/resources/extensions/gsd/auto-start.ts +40 -22
- package/src/resources/extensions/gsd/auto.ts +15 -12
- package/src/resources/extensions/gsd/blocked-models.ts +98 -0
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +97 -0
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +40 -9
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +109 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +36 -0
- package/src/resources/extensions/gsd/compaction-snapshot.ts +165 -0
- package/src/resources/extensions/gsd/complexity-classifier.ts +5 -3
- package/src/resources/extensions/gsd/error-classifier.ts +36 -3
- package/src/resources/extensions/gsd/exec-history.ts +153 -0
- package/src/resources/extensions/gsd/exec-sandbox.ts +326 -0
- package/src/resources/extensions/gsd/gsd-db.ts +68 -4
- package/src/resources/extensions/gsd/init-wizard.ts +15 -1
- package/src/resources/extensions/gsd/key-manager.ts +6 -0
- package/src/resources/extensions/gsd/pre-execution-checks.ts +13 -3
- package/src/resources/extensions/gsd/preferences-types.ts +38 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +79 -0
- package/src/resources/extensions/gsd/preferences.ts +17 -17
- package/src/resources/extensions/gsd/prompt-loader.ts +30 -7
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +33 -3
- package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/blocked-models.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +123 -0
- package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/exec-history.test.ts +124 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +210 -0
- package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +151 -0
- package/src/resources/extensions/gsd/tests/init-wizard.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/key-manager.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/prompt-loader-extension-dir.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +91 -0
- package/src/resources/extensions/gsd/tests/save-gate-result-render.test.ts +95 -0
- package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +3 -1
- package/src/resources/extensions/gsd/tools/exec-search-tool.ts +81 -0
- package/src/resources/extensions/gsd/tools/exec-tool.ts +183 -0
- package/src/resources/extensions/gsd/tools/resume-tool.ts +40 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +2 -1
- package/src/resources/extensions/gsd/workflow-mcp.ts +3 -0
- package/src/resources/extensions/search-the-web/command-search-provider.ts +5 -4
- package/src/resources/extensions/search-the-web/native-search.ts +48 -12
- /package/dist/web/standalone/.next/static/{pBwmOoye64ZrRp-_rf0v1 → n21VtX2hZlkpdEUO_nU4z}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{pBwmOoye64ZrRp-_rf0v1 → n21VtX2hZlkpdEUO_nU4z}/_ssgManifest.js +0 -0
|
@@ -330,8 +330,8 @@ export function startAutoDetached(
|
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
/** Returns true if the project is configured for `isolation:worktree` mode. */
|
|
333
|
-
export function shouldUseWorktreeIsolation(): boolean {
|
|
334
|
-
const prefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
333
|
+
export function shouldUseWorktreeIsolation(basePath?: string): boolean {
|
|
334
|
+
const prefs = loadEffectiveGSDPreferences(basePath)?.preferences?.git;
|
|
335
335
|
if (prefs?.isolation === "worktree") return true;
|
|
336
336
|
// Default is false — worktree isolation requires explicit opt-in
|
|
337
337
|
return false;
|
|
@@ -424,7 +424,7 @@ export function getAutoDashboardData(): AutoDashboardData {
|
|
|
424
424
|
const rtkSavings = sessionId && s.basePath
|
|
425
425
|
? getRtkSessionSavings(s.basePath, sessionId)
|
|
426
426
|
: null;
|
|
427
|
-
const rtkEnabled = loadEffectiveGSDPreferences()?.preferences.experimental?.rtk === true;
|
|
427
|
+
const rtkEnabled = loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences.experimental?.rtk === true;
|
|
428
428
|
// Pending capture count — lazy check, non-fatal
|
|
429
429
|
let pendingCaptureCount = 0;
|
|
430
430
|
try {
|
|
@@ -648,7 +648,7 @@ function buildSnapshotOpts(
|
|
|
648
648
|
gitStatus?: "ok" | "failed";
|
|
649
649
|
gitError?: string;
|
|
650
650
|
} & Record<string, unknown> {
|
|
651
|
-
const prefs = loadEffectiveGSDPreferences()?.preferences;
|
|
651
|
+
const prefs = loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences;
|
|
652
652
|
const uokFlags = resolveUokFlags(prefs);
|
|
653
653
|
return {
|
|
654
654
|
...(s.autoStartTime > 0 ? { autoSessionKey: String(s.autoStartTime) } : {}),
|
|
@@ -686,7 +686,7 @@ function handleLostSessionLock(
|
|
|
686
686
|
restoreProjectRootEnv();
|
|
687
687
|
restoreMilestoneLockEnv();
|
|
688
688
|
deregisterSigtermHandler();
|
|
689
|
-
clearCmuxSidebar(loadEffectiveGSDPreferences()?.preferences);
|
|
689
|
+
clearCmuxSidebar(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences);
|
|
690
690
|
const base = lockBase();
|
|
691
691
|
const lockFilePath = base ? join(gsdRoot(base), "auto.lock") : "unknown";
|
|
692
692
|
const recoverySuggestion = "\nTo recover, run: gsd doctor --fix";
|
|
@@ -764,7 +764,7 @@ export async function stopAuto(
|
|
|
764
764
|
reason?: string,
|
|
765
765
|
): Promise<void> {
|
|
766
766
|
if (!s.active && !s.paused) return;
|
|
767
|
-
const loadedPreferences = loadEffectiveGSDPreferences()?.preferences;
|
|
767
|
+
const loadedPreferences = loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences;
|
|
768
768
|
const reasonSuffix = reason ? ` — ${reason}` : "";
|
|
769
769
|
|
|
770
770
|
try {
|
|
@@ -969,7 +969,7 @@ export async function stopAuto(
|
|
|
969
969
|
logWarning("engine", `file unlink failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
970
970
|
}
|
|
971
971
|
|
|
972
|
-
// ── Step 13: Restore original model (before reset clears IDs) ──
|
|
972
|
+
// ── Step 13: Restore original model + thinking (before reset clears IDs) ──
|
|
973
973
|
try {
|
|
974
974
|
if (pi && ctx && s.originalModelId && s.originalModelProvider) {
|
|
975
975
|
const original = ctx.modelRegistry.find(
|
|
@@ -978,6 +978,9 @@ export async function stopAuto(
|
|
|
978
978
|
);
|
|
979
979
|
if (original) await pi.setModel(original);
|
|
980
980
|
}
|
|
981
|
+
if (pi && s.originalThinkingLevel) {
|
|
982
|
+
pi.setThinkingLevel(s.originalThinkingLevel);
|
|
983
|
+
}
|
|
981
984
|
} catch (e) {
|
|
982
985
|
debugLog("stop-cleanup-model", { error: e instanceof Error ? e.message : String(e) });
|
|
983
986
|
}
|
|
@@ -1492,7 +1495,7 @@ export async function startAuto(
|
|
|
1492
1495
|
// ── Auto-worktree / branch-mode: re-enter on resume ──
|
|
1493
1496
|
if (
|
|
1494
1497
|
s.currentMilestoneId &&
|
|
1495
|
-
getIsolationMode() !== "none" &&
|
|
1498
|
+
getIsolationMode(s.originalBasePath || s.basePath) !== "none" &&
|
|
1496
1499
|
s.originalBasePath &&
|
|
1497
1500
|
!isInAutoWorktree(s.basePath) &&
|
|
1498
1501
|
!detectWorktreeName(s.basePath) &&
|
|
@@ -1531,7 +1534,7 @@ export async function startAuto(
|
|
|
1531
1534
|
await openProjectDbIfPresent(s.basePath);
|
|
1532
1535
|
try {
|
|
1533
1536
|
await rebuildState(s.basePath);
|
|
1534
|
-
syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
|
|
1537
|
+
syncCmuxSidebar(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, await deriveState(s.basePath));
|
|
1535
1538
|
} catch (e) {
|
|
1536
1539
|
debugLog("resume-rebuild-state-failed", {
|
|
1537
1540
|
error: e instanceof Error ? e.message : String(e),
|
|
@@ -1581,7 +1584,7 @@ export async function startAuto(
|
|
|
1581
1584
|
"resuming",
|
|
1582
1585
|
s.currentMilestoneId ?? "unknown",
|
|
1583
1586
|
);
|
|
1584
|
-
logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "progress");
|
|
1587
|
+
logCmuxEvent(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "progress");
|
|
1585
1588
|
|
|
1586
1589
|
captureProjectRootEnv(s.originalBasePath || s.basePath);
|
|
1587
1590
|
startAutoCommandPolling(s.basePath);
|
|
@@ -1619,12 +1622,12 @@ export async function startAuto(
|
|
|
1619
1622
|
|
|
1620
1623
|
captureProjectRootEnv(s.originalBasePath || s.basePath);
|
|
1621
1624
|
try {
|
|
1622
|
-
syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
|
|
1625
|
+
syncCmuxSidebar(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, await deriveState(s.basePath));
|
|
1623
1626
|
} catch (err) {
|
|
1624
1627
|
// Best-effort only — sidebar sync must never block auto-mode startup
|
|
1625
1628
|
logWarning("engine", `cmux sync failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
1626
1629
|
}
|
|
1627
|
-
logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, requestedStepMode ? "Step-mode started." : "Auto-mode started.", "progress");
|
|
1630
|
+
logCmuxEvent(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, requestedStepMode ? "Step-mode started." : "Auto-mode started.", "progress");
|
|
1628
1631
|
|
|
1629
1632
|
startAutoCommandPolling(s.basePath);
|
|
1630
1633
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// GSD — Persistent per-project blocklist of provider/model pairs that the
|
|
2
|
+
// provider has rejected at request time for account entitlement reasons.
|
|
3
|
+
//
|
|
4
|
+
// Lives at `.gsd/runtime/blocked-models.json` so the block survives /gsd auto
|
|
5
|
+
// restarts. Auto-mode model selection skips blocked entries; agent-end
|
|
6
|
+
// recovery adds entries when a runtime rejection is classified as
|
|
7
|
+
// `unsupported-model`. See issue #4513.
|
|
8
|
+
|
|
9
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
11
|
+
import { gsdRoot } from "./paths.js";
|
|
12
|
+
import { withFileLockSync } from "./file-lock.js";
|
|
13
|
+
|
|
14
|
+
export interface BlockedModelEntry {
|
|
15
|
+
provider: string;
|
|
16
|
+
id: string;
|
|
17
|
+
reason: string;
|
|
18
|
+
blockedAt: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface BlockedModelsFile {
|
|
22
|
+
version: 1;
|
|
23
|
+
blocked: BlockedModelEntry[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function blockedModelsPath(basePath: string): string {
|
|
27
|
+
return join(gsdRoot(basePath), "runtime", "blocked-models.json");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function modelKey(provider: string, id: string): string {
|
|
31
|
+
return `${provider.toLowerCase()}/${id.toLowerCase()}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function readFileSafe(path: string): BlockedModelsFile {
|
|
35
|
+
if (!existsSync(path)) return { version: 1, blocked: [] };
|
|
36
|
+
try {
|
|
37
|
+
const raw = readFileSync(path, "utf-8");
|
|
38
|
+
const parsed = JSON.parse(raw) as Partial<BlockedModelsFile>;
|
|
39
|
+
if (!parsed || !Array.isArray(parsed.blocked)) {
|
|
40
|
+
return { version: 1, blocked: [] };
|
|
41
|
+
}
|
|
42
|
+
const blocked = parsed.blocked.filter(
|
|
43
|
+
(e): e is BlockedModelEntry =>
|
|
44
|
+
!!e && typeof e.provider === "string" && typeof e.id === "string",
|
|
45
|
+
);
|
|
46
|
+
return { version: 1, blocked };
|
|
47
|
+
} catch {
|
|
48
|
+
// Corrupted JSON: treat as empty so a bad file never blocks dispatch.
|
|
49
|
+
return { version: 1, blocked: [] };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function loadBlockedModels(basePath: string): BlockedModelEntry[] {
|
|
54
|
+
return readFileSafe(blockedModelsPath(basePath)).blocked;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function isModelBlocked(
|
|
58
|
+
basePath: string,
|
|
59
|
+
provider: string | undefined,
|
|
60
|
+
id: string | undefined,
|
|
61
|
+
): boolean {
|
|
62
|
+
if (!provider || !id) return false;
|
|
63
|
+
const target = modelKey(provider, id);
|
|
64
|
+
return loadBlockedModels(basePath).some(
|
|
65
|
+
(e) => modelKey(e.provider, e.id) === target,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function blockModel(
|
|
70
|
+
basePath: string,
|
|
71
|
+
provider: string,
|
|
72
|
+
id: string,
|
|
73
|
+
reason: string,
|
|
74
|
+
): void {
|
|
75
|
+
const path = blockedModelsPath(basePath);
|
|
76
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
77
|
+
// Ensure the file exists before we try to lock it — proper-lockfile requires
|
|
78
|
+
// the target path to exist (file-lock.ts falls through to an unlocked call
|
|
79
|
+
// otherwise).
|
|
80
|
+
if (!existsSync(path)) {
|
|
81
|
+
writeFileSync(path, JSON.stringify({ version: 1, blocked: [] }, null, 2) + "\n", "utf-8");
|
|
82
|
+
}
|
|
83
|
+
withFileLockSync(path, () => {
|
|
84
|
+
const current = readFileSafe(path);
|
|
85
|
+
const target = modelKey(provider, id);
|
|
86
|
+
if (current.blocked.some((e) => modelKey(e.provider, e.id) === target)) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const next: BlockedModelsFile = {
|
|
90
|
+
version: 1,
|
|
91
|
+
blocked: [
|
|
92
|
+
...current.blocked,
|
|
93
|
+
{ provider, id, reason, blockedAt: Date.now() },
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
writeFileSync(path, JSON.stringify(next, null, 2) + "\n", "utf-8");
|
|
97
|
+
});
|
|
98
|
+
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
isTransient,
|
|
17
17
|
type ErrorClass,
|
|
18
18
|
} from "../error-classifier.js";
|
|
19
|
+
import { blockModel, isModelBlocked } from "../blocked-models.js";
|
|
19
20
|
|
|
20
21
|
const retryState = createRetryState();
|
|
21
22
|
const MAX_NETWORK_RETRIES = 2;
|
|
@@ -124,6 +125,102 @@ export async function handleAgentEnd(
|
|
|
124
125
|
// ── 1. Classify using rawErrorMsg to avoid prose false-positives ────
|
|
125
126
|
const cls = classifyError(rawErrorMsg, explicitRetryAfterMs);
|
|
126
127
|
|
|
128
|
+
// ── 1a. Unsupported-model: provider rejected this model for the current
|
|
129
|
+
// account/plan at request time (#4513). Persist a block so the
|
|
130
|
+
// same dead model isn't reselected on the next /gsd auto restart,
|
|
131
|
+
// then try a fallback before pausing.
|
|
132
|
+
if (cls.kind === "unsupported-model") {
|
|
133
|
+
const dash = getAutoDashboardData();
|
|
134
|
+
const rejectedProvider = ctx.model?.provider;
|
|
135
|
+
const rejectedId = ctx.model?.id;
|
|
136
|
+
if (dash.basePath && rejectedProvider && rejectedId) {
|
|
137
|
+
try {
|
|
138
|
+
blockModel(dash.basePath, rejectedProvider, rejectedId, rawErrorMsg || "unsupported for account");
|
|
139
|
+
ctx.ui.notify(
|
|
140
|
+
`Blocked ${rejectedProvider}/${rejectedId} for this project — provider rejected it for the current account.`,
|
|
141
|
+
"warning",
|
|
142
|
+
);
|
|
143
|
+
} catch (err) {
|
|
144
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
145
|
+
logWarning("bootstrap", `Failed to persist blocked model: ${m}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Try configured fallback chain, skipping anything already blocked.
|
|
150
|
+
if (dash.currentUnit && dash.basePath) {
|
|
151
|
+
const modelConfig = resolveModelWithFallbacksForUnit(dash.currentUnit.type);
|
|
152
|
+
if (modelConfig && modelConfig.fallbacks.length > 0) {
|
|
153
|
+
const availableModels = ctx.modelRegistry.getAvailable();
|
|
154
|
+
let cursorModelId: string | undefined = ctx.model?.id;
|
|
155
|
+
while (true) {
|
|
156
|
+
const nextModelId = getNextFallbackModel(cursorModelId, modelConfig);
|
|
157
|
+
if (!nextModelId) break;
|
|
158
|
+
const candidate = resolveModelId(nextModelId, availableModels, ctx.model?.provider);
|
|
159
|
+
if (candidate && !isModelBlocked(dash.basePath, candidate.provider, candidate.id)) {
|
|
160
|
+
const ok = await pi.setModel(candidate, { persist: false });
|
|
161
|
+
if (ok) {
|
|
162
|
+
setCurrentDispatchedModelId({ provider: candidate.provider, id: candidate.id });
|
|
163
|
+
ctx.ui.notify(
|
|
164
|
+
`Switched to fallback ${candidate.provider}/${candidate.id} after account entitlement rejection.`,
|
|
165
|
+
"warning",
|
|
166
|
+
);
|
|
167
|
+
pi.sendMessage(
|
|
168
|
+
{ customType: "gsd-auto-timeout-recovery", content: "Continue execution.", display: false },
|
|
169
|
+
{ triggerTurn: true },
|
|
170
|
+
);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
cursorModelId = nextModelId;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Fallback chain exhausted — try the auto-mode start model if it isn't
|
|
179
|
+
// the same one we just blocked and isn't itself blocked.
|
|
180
|
+
const sessionModel = getAutoModeStartModel();
|
|
181
|
+
if (
|
|
182
|
+
sessionModel &&
|
|
183
|
+
!(sessionModel.provider === rejectedProvider && sessionModel.id === rejectedId) &&
|
|
184
|
+
!isModelBlocked(dash.basePath, sessionModel.provider, sessionModel.id)
|
|
185
|
+
) {
|
|
186
|
+
const startModel = ctx.modelRegistry
|
|
187
|
+
.getAvailable()
|
|
188
|
+
.find((m) => m.provider === sessionModel.provider && m.id === sessionModel.id);
|
|
189
|
+
if (startModel) {
|
|
190
|
+
const ok = await pi.setModel(startModel, { persist: false });
|
|
191
|
+
if (ok) {
|
|
192
|
+
setCurrentDispatchedModelId({ provider: startModel.provider, id: startModel.id });
|
|
193
|
+
ctx.ui.notify(
|
|
194
|
+
`Restored auto-mode start model ${startModel.provider}/${startModel.id} after entitlement rejection.`,
|
|
195
|
+
"warning",
|
|
196
|
+
);
|
|
197
|
+
pi.sendMessage(
|
|
198
|
+
{ customType: "gsd-auto-timeout-recovery", content: "Continue execution.", display: false },
|
|
199
|
+
{ triggerTurn: true },
|
|
200
|
+
);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// No usable fallback — pause with a clearly named message.
|
|
208
|
+
const blockedLabel = rejectedProvider && rejectedId ? `${rejectedProvider}/${rejectedId}` : "current model";
|
|
209
|
+
const pauseDetail = `Model ${blockedLabel} blocked for this account${errorDetail}. Configure a different model and restart /gsd auto.`;
|
|
210
|
+
await pauseAutoForProviderError(ctx.ui, pauseDetail, () =>
|
|
211
|
+
pauseAuto(ctx, pi, {
|
|
212
|
+
message: pauseDetail,
|
|
213
|
+
category: "provider",
|
|
214
|
+
isTransient: false,
|
|
215
|
+
}),
|
|
216
|
+
{
|
|
217
|
+
isRateLimit: false,
|
|
218
|
+
isTransient: false,
|
|
219
|
+
retryAfterMs: 0,
|
|
220
|
+
});
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
127
224
|
// ── 1b. Defer to Core RetryHandler for most transient errors ────────
|
|
128
225
|
// Core retries transient failures in-session after this handler.
|
|
129
226
|
// Keep that behavior for non-rate-limit classes to avoid pause/retry races,
|
|
@@ -35,6 +35,19 @@ function registerAlias(pi: ExtensionAPI, toolDef: any, aliasName: string, canoni
|
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Read a tool result's structured payload, accommodating MCP's `details` →
|
|
40
|
+
* `structuredContent` rename (#4472, #4477). In-process executions still
|
|
41
|
+
* deliver the payload on `result.details`; MCP-routed executions deliver it
|
|
42
|
+
* on `result.structuredContent` (post `adaptExecutorResult` transform). All
|
|
43
|
+
* `renderResult` callbacks in this file route through this helper so a future
|
|
44
|
+
* field rename only needs to be applied in one place.
|
|
45
|
+
*/
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- result shape varies by tool
|
|
47
|
+
function readDetails(result: any): any {
|
|
48
|
+
return result?.details ?? result?.structuredContent;
|
|
49
|
+
}
|
|
50
|
+
|
|
38
51
|
export function registerDbTools(pi: ExtensionAPI): void {
|
|
39
52
|
// ─── gsd_decision_save (formerly gsd_save_decision) ─────────────────────
|
|
40
53
|
|
|
@@ -110,7 +123,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
110
123
|
return new Text(text, 0, 0);
|
|
111
124
|
},
|
|
112
125
|
renderResult(result: any, _options: any, theme: any) {
|
|
113
|
-
const d = result
|
|
126
|
+
const d = readDetails(result);
|
|
114
127
|
if (result.isError || d?.error) {
|
|
115
128
|
return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
|
|
116
129
|
}
|
|
@@ -188,7 +201,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
188
201
|
return new Text(text, 0, 0);
|
|
189
202
|
},
|
|
190
203
|
renderResult(result: any, _options: any, theme: any) {
|
|
191
|
-
const d = result
|
|
204
|
+
const d = readDetails(result);
|
|
192
205
|
if (result.isError || d?.error) {
|
|
193
206
|
return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
|
|
194
207
|
}
|
|
@@ -273,7 +286,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
273
286
|
return new Text(text, 0, 0);
|
|
274
287
|
},
|
|
275
288
|
renderResult(result: any, _options: any, theme: any) {
|
|
276
|
-
const d = result
|
|
289
|
+
const d = readDetails(result);
|
|
277
290
|
if (result.isError || d?.error) {
|
|
278
291
|
return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
|
|
279
292
|
}
|
|
@@ -322,7 +335,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
322
335
|
return new Text(text, 0, 0);
|
|
323
336
|
},
|
|
324
337
|
renderResult(result: any, _options: any, theme: any) {
|
|
325
|
-
const d = result
|
|
338
|
+
const d = readDetails(result);
|
|
326
339
|
if (result.isError || d?.error) {
|
|
327
340
|
return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
|
|
328
341
|
}
|
|
@@ -406,7 +419,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
406
419
|
return new Text(theme.fg("toolTitle", theme.bold("milestone_generate_id")), 0, 0);
|
|
407
420
|
},
|
|
408
421
|
renderResult(result: any, _options: any, theme: any) {
|
|
409
|
-
const d = result
|
|
422
|
+
const d = readDetails(result);
|
|
410
423
|
if (result.isError || d?.error) {
|
|
411
424
|
return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
|
|
412
425
|
}
|
|
@@ -1074,13 +1087,31 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
1074
1087
|
text += theme.fg("dim", ` → ${args.verdict ?? ""}`);
|
|
1075
1088
|
return new Text(text, 0, 0);
|
|
1076
1089
|
},
|
|
1090
|
+
/**
|
|
1091
|
+
* Render the save_gate_result tool output for the TUI.
|
|
1092
|
+
*
|
|
1093
|
+
* Prefers structured fields, but falls back to `content[0].text` when the
|
|
1094
|
+
* structured payload is empty. Defensive: the structural fix on this
|
|
1095
|
+
* branch plumbs `details` through MCP via `structuredContent`, but older
|
|
1096
|
+
* hosts, a future handler that forgets `structuredContent`, or any drop
|
|
1097
|
+
* of non-standard return fields would otherwise render as
|
|
1098
|
+
* "undefined: undefined". Same fallback applies to error rendering, and
|
|
1099
|
+
* we strip a leading `Error:` from the fallback text to avoid producing
|
|
1100
|
+
* `Error: Error: ...`.
|
|
1101
|
+
*/
|
|
1077
1102
|
renderResult(result: any, _options: any, theme: any) {
|
|
1078
|
-
const d = result
|
|
1103
|
+
const d = readDetails(result);
|
|
1079
1104
|
if (result.isError || d?.error) {
|
|
1080
|
-
|
|
1105
|
+
const rawMsg = d?.error ?? result.content?.[0]?.text ?? "unknown";
|
|
1106
|
+
const msg = rawMsg.replace(/^\s*Error:\s*/i, "");
|
|
1107
|
+
return new Text(theme.fg("error", `Error: ${msg}`), 0, 0);
|
|
1108
|
+
}
|
|
1109
|
+
if (!d?.gateId || !d?.verdict) {
|
|
1110
|
+
const text = result.content?.[0]?.text ?? "Gate result saved";
|
|
1111
|
+
return new Text(theme.fg("success", text), 0, 0);
|
|
1081
1112
|
}
|
|
1082
|
-
const color = d
|
|
1083
|
-
return new Text(theme.fg(color, `${d
|
|
1113
|
+
const color = d.verdict === "flag" ? "warning" : "success";
|
|
1114
|
+
return new Text(theme.fg(color, `${d.gateId}: ${d.verdict}`), 0, 0);
|
|
1084
1115
|
},
|
|
1085
1116
|
};
|
|
1086
1117
|
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// GSD2 — Exec (context-mode) tool registration.
|
|
2
|
+
//
|
|
3
|
+
// Exposes the `gsd_exec` tool over MCP. Opt-in: disabled unless
|
|
4
|
+
// `context_mode.enabled: true` is set in preferences.
|
|
5
|
+
|
|
6
|
+
import { Type } from "@sinclair/typebox";
|
|
7
|
+
import type { ExtensionAPI } from "@gsd/pi-coding-agent";
|
|
8
|
+
|
|
9
|
+
import { executeGsdExec } from "../tools/exec-tool.js";
|
|
10
|
+
import { executeExecSearch } from "../tools/exec-search-tool.js";
|
|
11
|
+
import { executeResume } from "../tools/resume-tool.js";
|
|
12
|
+
import { loadEffectiveGSDPreferences } from "../preferences.js";
|
|
13
|
+
import { logWarning } from "../workflow-logger.js";
|
|
14
|
+
|
|
15
|
+
export function registerExecTools(pi: ExtensionAPI): void {
|
|
16
|
+
pi.registerTool({
|
|
17
|
+
name: "gsd_exec",
|
|
18
|
+
label: "Exec (Sandboxed)",
|
|
19
|
+
description:
|
|
20
|
+
"Run a short script (bash/node/python) in a subprocess. Full stdout/stderr persist to " +
|
|
21
|
+
".gsd/exec/<id>.{stdout,stderr,meta.json}; only a short digest returns in context. Use " +
|
|
22
|
+
"this instead of reading many files or emitting large tool outputs — e.g. have the script " +
|
|
23
|
+
"count/grep/summarize and log the finding. Enabled by default; opt out via " +
|
|
24
|
+
"preferences.context_mode.enabled=false.",
|
|
25
|
+
promptSnippet:
|
|
26
|
+
"Run a bash/node/python script in a sandbox; full output is saved to disk and only a digest returns",
|
|
27
|
+
promptGuidelines: [
|
|
28
|
+
"Prefer gsd_exec for analyses that would otherwise read >3 files or produce large tool output.",
|
|
29
|
+
"Write scripts that log the finding (counts, matches, summaries) rather than raw dumps.",
|
|
30
|
+
"The digest is the last ~300 chars of stdout — size your log output accordingly.",
|
|
31
|
+
"Need the full output? Read the stdout_path returned in details (file on local disk).",
|
|
32
|
+
],
|
|
33
|
+
parameters: Type.Object({
|
|
34
|
+
runtime: Type.Union(
|
|
35
|
+
[Type.Literal("bash"), Type.Literal("node"), Type.Literal("python")],
|
|
36
|
+
{ description: "Interpreter: bash (-c), node (-e), or python3 (-c)." },
|
|
37
|
+
),
|
|
38
|
+
script: Type.String({ description: "Script body. Keep output small (log the finding, not the data)." }),
|
|
39
|
+
purpose: Type.Optional(Type.String({ description: "Short label recorded in meta.json for later review." })),
|
|
40
|
+
timeout_ms: Type.Optional(
|
|
41
|
+
Type.Number({
|
|
42
|
+
description: "Per-invocation timeout (ms). Capped at 600000. Default from preferences.",
|
|
43
|
+
minimum: 1_000,
|
|
44
|
+
maximum: 600_000,
|
|
45
|
+
}),
|
|
46
|
+
),
|
|
47
|
+
}),
|
|
48
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
49
|
+
let prefs: Awaited<ReturnType<typeof loadEffectiveGSDPreferences>> | null = null;
|
|
50
|
+
try {
|
|
51
|
+
prefs = loadEffectiveGSDPreferences();
|
|
52
|
+
} catch (err) {
|
|
53
|
+
logWarning("tool", `gsd_exec could not load preferences: ${err instanceof Error ? err.message : String(err)}`);
|
|
54
|
+
}
|
|
55
|
+
return executeGsdExec(params as Parameters<typeof executeGsdExec>[0], {
|
|
56
|
+
baseDir: process.cwd(),
|
|
57
|
+
preferences: prefs?.preferences ?? null,
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
pi.registerTool({
|
|
63
|
+
name: "gsd_exec_search",
|
|
64
|
+
label: "Search gsd_exec History",
|
|
65
|
+
description:
|
|
66
|
+
"List prior gsd_exec runs (most recent first) from .gsd/exec/*.meta.json. Useful for " +
|
|
67
|
+
"rediscovering the stdout_path of an earlier run without re-executing it. Read-only.",
|
|
68
|
+
promptSnippet: "Search prior gsd_exec runs by substring, runtime, or failing-only filter",
|
|
69
|
+
promptGuidelines: [
|
|
70
|
+
"Use this before re-running an expensive analysis — the prior run's stdout file may still answer.",
|
|
71
|
+
"The preview shows the trailing ~300 chars of stdout; read stdout_path for the full transcript.",
|
|
72
|
+
],
|
|
73
|
+
parameters: Type.Object({
|
|
74
|
+
query: Type.Optional(Type.String({ description: "Substring matched against id and purpose (case-insensitive)." })),
|
|
75
|
+
runtime: Type.Optional(
|
|
76
|
+
Type.Union([Type.Literal("bash"), Type.Literal("node"), Type.Literal("python")], {
|
|
77
|
+
description: "Restrict to one runtime.",
|
|
78
|
+
}),
|
|
79
|
+
),
|
|
80
|
+
failing_only: Type.Optional(Type.Boolean({ description: "Only non-zero exit codes and timeouts." })),
|
|
81
|
+
limit: Type.Optional(Type.Number({ description: "Max results (default 20, cap 200)", minimum: 1, maximum: 200 })),
|
|
82
|
+
}),
|
|
83
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
84
|
+
return executeExecSearch(params as Parameters<typeof executeExecSearch>[0], {
|
|
85
|
+
baseDir: process.cwd(),
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
pi.registerTool({
|
|
91
|
+
name: "gsd_resume",
|
|
92
|
+
label: "Resume (Read Snapshot)",
|
|
93
|
+
description:
|
|
94
|
+
"Return the contents of .gsd/last-snapshot.md — a ≤2 KB digest of top memories, recent " +
|
|
95
|
+
"gsd_exec runs, and active context, written automatically on session_before_compact. Use " +
|
|
96
|
+
"this after compaction or session resume to re-orient quickly.",
|
|
97
|
+
promptSnippet: "Read the pre-compaction snapshot to re-orient after context loss",
|
|
98
|
+
promptGuidelines: [
|
|
99
|
+
"Call this right after a session resumes if you feel you've lost durable context.",
|
|
100
|
+
"The snapshot is a summary — use memory_query or gsd_exec_search for detail.",
|
|
101
|
+
],
|
|
102
|
+
parameters: Type.Object({}),
|
|
103
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
104
|
+
return executeResume(params as Parameters<typeof executeResume>[0], {
|
|
105
|
+
baseDir: process.cwd(),
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
@@ -8,6 +8,7 @@ import type { GSDEcosystemBeforeAgentStartHandler } from "../ecosystem/gsd-exten
|
|
|
8
8
|
import { loadEcosystemExtensions } from "../ecosystem/loader.js";
|
|
9
9
|
import { registerDbTools } from "./db-tools.js";
|
|
10
10
|
import { registerDynamicTools } from "./dynamic-tools.js";
|
|
11
|
+
import { registerExecTools } from "./exec-tools.js";
|
|
11
12
|
import { registerJournalTools } from "./journal-tools.js";
|
|
12
13
|
import { registerMemoryTools } from "./memory-tools.js";
|
|
13
14
|
import { registerQueryTools } from "./query-tools.js";
|
|
@@ -100,6 +101,7 @@ export function registerGsdExtension(pi: ExtensionAPI): void {
|
|
|
100
101
|
["journal-tools", () => registerJournalTools(pi)],
|
|
101
102
|
["query-tools", () => registerQueryTools(pi)],
|
|
102
103
|
["memory-tools", () => registerMemoryTools(pi)],
|
|
104
|
+
["exec-tools", () => registerExecTools(pi)],
|
|
103
105
|
["shortcuts", () => registerShortcuts(pi)],
|
|
104
106
|
["hooks", () => registerHooks(pi, ecosystemHandlers)],
|
|
105
107
|
["ecosystem", () => {
|
|
@@ -225,6 +225,42 @@ export function registerHooks(
|
|
|
225
225
|
}));
|
|
226
226
|
});
|
|
227
227
|
|
|
228
|
+
// Context-mode snapshot: write .gsd/last-snapshot.md before compaction so
|
|
229
|
+
// agents can call gsd_resume (or Read the file) to re-orient. Opt-in via
|
|
230
|
+
// preferences.context_mode.enabled. Runs after the auto-cancel handler
|
|
231
|
+
// above — if that one returned cancel:true, pi still fires us but the
|
|
232
|
+
// compaction won't actually happen; the snapshot is still useful then,
|
|
233
|
+
// since auto may pause and resume later.
|
|
234
|
+
pi.on("session_before_compact", async () => {
|
|
235
|
+
try {
|
|
236
|
+
const { loadEffectiveGSDPreferences } = await import("../preferences.js");
|
|
237
|
+
const { isContextModeEnabled } = await import("../preferences-types.js");
|
|
238
|
+
const prefs = loadEffectiveGSDPreferences();
|
|
239
|
+
if (!isContextModeEnabled(prefs?.preferences)) return;
|
|
240
|
+
const { writeCompactionSnapshot } = await import("../compaction-snapshot.js");
|
|
241
|
+
const { ensureDbOpen } = await import("./dynamic-tools.js");
|
|
242
|
+
await ensureDbOpen();
|
|
243
|
+
const basePath = process.cwd();
|
|
244
|
+
let activeContext: string | null = null;
|
|
245
|
+
try {
|
|
246
|
+
const state = await deriveState(basePath);
|
|
247
|
+
if (state.activeMilestone && state.activeSlice && state.activeTask) {
|
|
248
|
+
activeContext =
|
|
249
|
+
`Active: ${state.activeMilestone.id} / ${state.activeSlice.id} / ${state.activeTask.id}` +
|
|
250
|
+
(state.activeTask.title ? ` — ${state.activeTask.title}` : "");
|
|
251
|
+
}
|
|
252
|
+
} catch {
|
|
253
|
+
/* non-fatal */
|
|
254
|
+
}
|
|
255
|
+
writeCompactionSnapshot(basePath, { activeContext });
|
|
256
|
+
} catch (err) {
|
|
257
|
+
safetyLogWarning(
|
|
258
|
+
"context-mode",
|
|
259
|
+
`failed to write compaction snapshot: ${err instanceof Error ? err.message : String(err)}`,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
228
264
|
pi.on("session_shutdown", async (_event, ctx: ExtensionContext) => {
|
|
229
265
|
if (isParallelActive()) {
|
|
230
266
|
try {
|