gsd-pi 2.76.0-dev.82e249f7b → 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-start.js +27 -14
- package/dist/resources/extensions/gsd/auto.js +11 -11
- 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/error-classifier.js +10 -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/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/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/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 +9 -9
- 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-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/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/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/skill-invocation-message.ts +36 -22
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- 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-start.ts +28 -15
- package/src/resources/extensions/gsd/auto.ts +11 -11
- 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/error-classifier.ts +10 -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/safety/file-change-validator.ts +1 -1
- package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +123 -0
- 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/provider-errors.test.ts +48 -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/dist/web/standalone/.next/static/{ecSsu49rxxcpbNmVP4mLD → n21VtX2hZlkpdEUO_nU4z}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{ecSsu49rxxcpbNmVP4mLD → n21VtX2hZlkpdEUO_nU4z}/_ssgManifest.js +0 -0
package/dist/mcp-server.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Minimal tool interface matching GSD's AgentTool shape.
|
|
3
3
|
* Avoids a direct dependency on @gsd/pi-agent-core from this compiled module.
|
|
4
|
+
*
|
|
5
|
+
* `details` and `isError` are optional fields that runtime tool implementations
|
|
6
|
+
* may populate. The MCP transport drops non-standard fields, so the wrapper at
|
|
7
|
+
* the call site mirrors `details` into `structuredContent` and forwards
|
|
8
|
+
* `isError` directly. See #4472.
|
|
4
9
|
*/
|
|
5
10
|
export interface McpToolDef {
|
|
6
11
|
name: string;
|
|
@@ -13,6 +18,8 @@ export interface McpToolDef {
|
|
|
13
18
|
data?: string;
|
|
14
19
|
mimeType?: string;
|
|
15
20
|
}>;
|
|
21
|
+
details?: Record<string, unknown>;
|
|
22
|
+
isError?: boolean;
|
|
16
23
|
}>;
|
|
17
24
|
}
|
|
18
25
|
/**
|
package/dist/mcp-server.js
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict plain-object guard. True only for object literals and
|
|
3
|
+
* `Object.create(null)` — not for `Date`, `URL`, `Map`, `Set`, class instances,
|
|
4
|
+
* or arrays. Used to gate `structuredContent` forwarding so the MCP transport
|
|
5
|
+
* receives only true JSON objects (the protocol contract). See #4477 review.
|
|
6
|
+
*
|
|
7
|
+
* Mirrored in `packages/mcp-server/src/workflow-tools.ts` for the
|
|
8
|
+
* `adaptExecutorResult` adapter on the workflow path. Keep both copies in
|
|
9
|
+
* sync if the contract definition needs to evolve.
|
|
10
|
+
*/
|
|
11
|
+
function isPlainObject(value) {
|
|
12
|
+
if (value === null || typeof value !== 'object')
|
|
13
|
+
return false;
|
|
14
|
+
if (Array.isArray(value))
|
|
15
|
+
return false;
|
|
16
|
+
const proto = Object.getPrototypeOf(value);
|
|
17
|
+
return proto === null || proto === Object.prototype;
|
|
18
|
+
}
|
|
1
19
|
// MCP SDK subpath imports use wildcard exports (./*) in @modelcontextprotocol/sdk's
|
|
2
20
|
// package.json export map. The wildcard maps "./foo" → "./dist/cjs/foo" (no .js
|
|
3
21
|
// suffix), so bare subpath specifiers like `${MCP_PKG}/server/stdio` resolve to
|
|
@@ -83,7 +101,23 @@ export async function startMcpServer(options) {
|
|
|
83
101
|
// by stringifying into a text block so clients see the payload.
|
|
84
102
|
return { type: 'text', text: JSON.stringify(block) };
|
|
85
103
|
});
|
|
86
|
-
|
|
104
|
+
// Forward a tool's runtime `details` field to MCP's `structuredContent`
|
|
105
|
+
// channel. The protocol drops non-standard fields on the wire, so tools
|
|
106
|
+
// that populate `details` for client-side renderers (e.g. save_gate_result)
|
|
107
|
+
// would otherwise arrive empty on the other side. See #4472.
|
|
108
|
+
//
|
|
109
|
+
// Use a strict plain-object guard (prototype-chain check) rather than just
|
|
110
|
+
// `typeof === 'object' && !Array.isArray()` — Date, URL, Map, Set, and
|
|
111
|
+
// class instances would otherwise pass through and end up as
|
|
112
|
+
// `structuredContent`, violating the protocol's JSON-object contract.
|
|
113
|
+
// The mirror discipline applies in `workflow-tools.ts adaptExecutorResult`.
|
|
114
|
+
const base = { content };
|
|
115
|
+
if (isPlainObject(result.details)) {
|
|
116
|
+
base.structuredContent = result.details;
|
|
117
|
+
}
|
|
118
|
+
if (result.isError === true)
|
|
119
|
+
base.isError = true;
|
|
120
|
+
return base;
|
|
87
121
|
}
|
|
88
122
|
catch (err) {
|
|
89
123
|
// AbortError from a cancelled tool surfaces as a normal error — MCP
|
|
@@ -21,6 +21,6 @@ export declare function getNewerManagedResourceVersion(agentDir: string, current
|
|
|
21
21
|
*
|
|
22
22
|
* Inspectable: `ls ~/.gsd/agent/extensions/`
|
|
23
23
|
*/
|
|
24
|
-
export declare function initResources(agentDir: string): void;
|
|
24
|
+
export declare function initResources(agentDir: string, skillsDir?: string): void;
|
|
25
25
|
export declare function hasStaleCompiledExtensionSiblings(extensionsDir: string, sourceDir?: string): boolean;
|
|
26
26
|
export declare function buildResourceLoader(agentDir: string): DefaultResourceLoader;
|
package/dist/resource-loader.js
CHANGED
|
@@ -506,7 +506,7 @@ function pruneRemovedBundledExtensions(manifest, agentDir) {
|
|
|
506
506
|
*
|
|
507
507
|
* Inspectable: `ls ~/.gsd/agent/extensions/`
|
|
508
508
|
*/
|
|
509
|
-
export function initResources(agentDir) {
|
|
509
|
+
export function initResources(agentDir, skillsDir = join(homedir(), '.agents', 'skills')) {
|
|
510
510
|
mkdirSync(agentDir, { recursive: true });
|
|
511
511
|
const currentVersion = getBundledGsdVersion();
|
|
512
512
|
const manifest = readManagedResourceManifest(agentDir);
|
|
@@ -538,13 +538,7 @@ export function initResources(agentDir) {
|
|
|
538
538
|
// Sync bundled resources — overwrite so updates land on next launch.
|
|
539
539
|
syncResourceDir(bundledExtensionsDir, join(agentDir, 'extensions'));
|
|
540
540
|
syncResourceDir(join(resourcesDir, 'agents'), join(agentDir, 'agents'));
|
|
541
|
-
|
|
542
|
-
// skills.sh CLI (`npx skills add <repo>`) into ~/.agents/skills/ which
|
|
543
|
-
// is the industry-standard Agent Skills ecosystem directory.
|
|
544
|
-
//
|
|
545
|
-
// Migration from the legacy ~/.gsd/agent/skills/ directory is handled
|
|
546
|
-
// above the manifest check so it runs on every launch (including retries
|
|
547
|
-
// after partial copy failures).
|
|
541
|
+
syncResourceDir(join(resourcesDir, 'skills'), skillsDir);
|
|
548
542
|
// Sync GSD-WORKFLOW.md to agentDir as a fallback for when GSD_WORKFLOW_PATH
|
|
549
543
|
// env var is not set (e.g. fork/dev builds, alternative entry points).
|
|
550
544
|
const workflowSrc = join(resourcesDir, 'GSD-WORKFLOW.md');
|
|
@@ -637,6 +637,68 @@ function normalizeToolResultContent(content) {
|
|
|
637
637
|
}
|
|
638
638
|
return blocks.length > 0 ? blocks : [{ type: "text", text: "" }];
|
|
639
639
|
}
|
|
640
|
+
/**
|
|
641
|
+
* Extract a `details` payload from an MCP tool-result block.
|
|
642
|
+
*
|
|
643
|
+
* MCP's `CallToolResult` carries structured data in `structuredContent` — the
|
|
644
|
+
* protocol's supported channel for non-text payloads. Claude Code's synthetic
|
|
645
|
+
* user message may surface that field in one of two shapes depending on SDK
|
|
646
|
+
* version: as a sibling on the `mcp_tool_result` block itself, or as a
|
|
647
|
+
* dedicated content sub-block with `type: "structuredContent"`. Snake-case
|
|
648
|
+
* (`structured_content`) is accepted defensively in case a transport hop
|
|
649
|
+
* rewrites casing. All other shapes fall back to an empty object so callers
|
|
650
|
+
* can rely on `details` being present.
|
|
651
|
+
*/
|
|
652
|
+
function extractStructuredDetailsFromBlock(block) {
|
|
653
|
+
const sibling = block.structuredContent ?? block.structured_content;
|
|
654
|
+
if (sibling && typeof sibling === "object" && !Array.isArray(sibling)) {
|
|
655
|
+
return sibling;
|
|
656
|
+
}
|
|
657
|
+
if (Array.isArray(block.content)) {
|
|
658
|
+
for (const item of block.content) {
|
|
659
|
+
if (!item || typeof item !== "object")
|
|
660
|
+
continue;
|
|
661
|
+
const sub = item;
|
|
662
|
+
if (sub.type !== "structuredContent" && sub.type !== "structured_content")
|
|
663
|
+
continue;
|
|
664
|
+
const payload = sub.structuredContent ?? sub.structured_content ?? sub.data ?? sub.value;
|
|
665
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
666
|
+
return payload;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
// Return undefined (not {}) when no structured payload is present, matching
|
|
671
|
+
// the pre-#4477 contract where `details` was nullable. An empty-object
|
|
672
|
+
// sentinel is truthy and breaks downstream consumers that gate on
|
|
673
|
+
// `if (details)`. `undefined` matches the type of the field these results
|
|
674
|
+
// flow into (`Record<string, unknown> | undefined`).
|
|
675
|
+
return undefined;
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* True for items that are MCP `structuredContent` pseudo-blocks living inside
|
|
679
|
+
* a tool-result `content[]` array. These blocks carry the structured payload
|
|
680
|
+
* (extracted separately by `extractStructuredDetailsFromBlock`) and must NOT
|
|
681
|
+
* leak into the visible content rendered to the user — otherwise the renderer
|
|
682
|
+
* stringifies the JSON pseudo-block and shows it next to the actual tool
|
|
683
|
+
* output. See PR #4477 review (CodeRabbit, post-fix-round).
|
|
684
|
+
*/
|
|
685
|
+
function isStructuredContentPseudoBlock(item) {
|
|
686
|
+
if (!item || typeof item !== "object")
|
|
687
|
+
return false;
|
|
688
|
+
const type = item.type;
|
|
689
|
+
return type === "structuredContent" || type === "structured_content";
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Strip `structuredContent` pseudo-blocks from a tool-result content array
|
|
693
|
+
* before normalization. The structured payload is extracted via the sibling
|
|
694
|
+
* `structuredContent` field (or a dedicated extractor pass on the raw block);
|
|
695
|
+
* the visible content path must not include the pseudo-block itself.
|
|
696
|
+
*/
|
|
697
|
+
function stripStructuredContentPseudoBlocks(content) {
|
|
698
|
+
if (!Array.isArray(content))
|
|
699
|
+
return content;
|
|
700
|
+
return content.filter((item) => !isStructuredContentPseudoBlock(item));
|
|
701
|
+
}
|
|
640
702
|
/** Extract tool result payloads from an SDK synthetic user message, keyed by tool-use ID. */
|
|
641
703
|
export function extractToolResultsFromSdkUserMessage(message) {
|
|
642
704
|
const extracted = [];
|
|
@@ -657,8 +719,8 @@ export function extractToolResultsFromSdkUserMessage(message) {
|
|
|
657
719
|
extracted.push({
|
|
658
720
|
toolUseId,
|
|
659
721
|
result: {
|
|
660
|
-
content: normalizeToolResultContent(block.content),
|
|
661
|
-
details:
|
|
722
|
+
content: normalizeToolResultContent(stripStructuredContentPseudoBlocks(block.content)),
|
|
723
|
+
details: extractStructuredDetailsFromBlock(block),
|
|
662
724
|
isError: block.is_error === true,
|
|
663
725
|
},
|
|
664
726
|
});
|
|
@@ -672,8 +734,8 @@ export function extractToolResultsFromSdkUserMessage(message) {
|
|
|
672
734
|
extracted.push({
|
|
673
735
|
toolUseId,
|
|
674
736
|
result: {
|
|
675
|
-
content: normalizeToolResultContent(toolResult.content),
|
|
676
|
-
details:
|
|
737
|
+
content: normalizeToolResultContent(stripStructuredContentPseudoBlocks(toolResult.content)),
|
|
738
|
+
details: extractStructuredDetailsFromBlock(toolResult),
|
|
677
739
|
isError: toolResult.is_error === true,
|
|
678
740
|
},
|
|
679
741
|
});
|
|
@@ -30,7 +30,7 @@ import { initRoutingHistory } from "./routing-history.js";
|
|
|
30
30
|
import { restoreHookState, resetHookState } from "./post-unit-hooks.js";
|
|
31
31
|
import { resetProactiveHealing, setLevelChangeCallback } from "./doctor-proactive.js";
|
|
32
32
|
import { snapshotSkills } from "./skill-discovery.js";
|
|
33
|
-
import { isDbAvailable, getMilestone, openDatabase } from "./gsd-db.js";
|
|
33
|
+
import { isDbAvailable, getMilestone, openDatabase, getDbStatus } from "./gsd-db.js";
|
|
34
34
|
import { hideFooter } from "./auto-dashboard.js";
|
|
35
35
|
import { debugLog, enableDebug, isDebugEnabled, getDebugLogPath, } from "./debug-logger.js";
|
|
36
36
|
import { logWarning, logError } from "./workflow-logger.js";
|
|
@@ -247,7 +247,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
247
247
|
// the parent git root). See #2393 and related issue.
|
|
248
248
|
const hasLocalGit = existsSync(join(base, ".git"));
|
|
249
249
|
if (!hasLocalGit || isInheritedRepo(base)) {
|
|
250
|
-
const mainBranch = loadEffectiveGSDPreferences()?.preferences?.git?.main_branch || "main";
|
|
250
|
+
const mainBranch = loadEffectiveGSDPreferences(base)?.preferences?.git?.main_branch || "main";
|
|
251
251
|
nativeInit(base, mainBranch);
|
|
252
252
|
}
|
|
253
253
|
// Migrate legacy in-project .gsd/ to external state directory.
|
|
@@ -263,7 +263,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
263
263
|
// Ensure .gitignore has baseline patterns.
|
|
264
264
|
// ensureGitignore checks for git-tracked .gsd/ files and skips the
|
|
265
265
|
// ".gsd" pattern if the project intentionally tracks .gsd/ in git.
|
|
266
|
-
const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
266
|
+
const gitPrefs = loadEffectiveGSDPreferences(base)?.preferences?.git;
|
|
267
267
|
const manageGitignore = gitPrefs?.manage_gitignore;
|
|
268
268
|
ensureGitignore(base, { manageGitignore });
|
|
269
269
|
if (manageGitignore !== false)
|
|
@@ -289,7 +289,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
289
289
|
prepareWorkflowMcpForProject(ctx, base);
|
|
290
290
|
}
|
|
291
291
|
// Initialize GitServiceImpl
|
|
292
|
-
s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
|
|
292
|
+
s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences(base)?.preferences?.git ?? {});
|
|
293
293
|
// ── Debug mode ──
|
|
294
294
|
if (!isDebugEnabled() && process.env.GSD_DEBUG === "1") {
|
|
295
295
|
enableDebug(base);
|
|
@@ -322,7 +322,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
322
322
|
// was lost due to session ending between completion and teardown.
|
|
323
323
|
// Must run after DB open and before worktree entry.
|
|
324
324
|
try {
|
|
325
|
-
const auditResult = auditOrphanedMilestoneBranches(base, getIsolationMode());
|
|
325
|
+
const auditResult = auditOrphanedMilestoneBranches(base, getIsolationMode(base));
|
|
326
326
|
for (const msg of auditResult.recovered) {
|
|
327
327
|
ctx.ui.notify(`Orphan audit: ${msg}`, "info");
|
|
328
328
|
}
|
|
@@ -340,7 +340,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
340
340
|
let state = await deriveState(base);
|
|
341
341
|
// Stale worktree state recovery (#654)
|
|
342
342
|
if (state.activeMilestone &&
|
|
343
|
-
shouldUseWorktreeIsolation() &&
|
|
343
|
+
shouldUseWorktreeIsolation(base) &&
|
|
344
344
|
!detectWorktreeName(base)) {
|
|
345
345
|
const wtPath = getAutoWorktreePath(base, state.activeMilestone.id);
|
|
346
346
|
if (wtPath) {
|
|
@@ -355,7 +355,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
355
355
|
let hasSurvivorBranch = false;
|
|
356
356
|
if (state.activeMilestone &&
|
|
357
357
|
(state.phase === "pre-planning" || state.phase === "complete") &&
|
|
358
|
-
getIsolationMode() !== "none" &&
|
|
358
|
+
getIsolationMode(base) !== "none" &&
|
|
359
359
|
!detectWorktreeName(base) &&
|
|
360
360
|
!base.includes(`${pathSep}.gsd${pathSep}worktrees${pathSep}`)) {
|
|
361
361
|
const milestoneBranch = `milestone/${state.activeMilestone.id}`;
|
|
@@ -516,7 +516,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
516
516
|
registerSigtermHandler(base);
|
|
517
517
|
// Capture integration branch
|
|
518
518
|
if (s.currentMilestoneId) {
|
|
519
|
-
if (getIsolationMode() !== "none") {
|
|
519
|
+
if (getIsolationMode(base) !== "none") {
|
|
520
520
|
captureIntegrationBranch(base, s.currentMilestoneId);
|
|
521
521
|
}
|
|
522
522
|
setActiveMilestoneId(base, s.currentMilestoneId);
|
|
@@ -524,7 +524,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
524
524
|
// Guard against stale milestone branch when isolation:none (#3613).
|
|
525
525
|
// A prior session with isolation:branch/worktree may have left HEAD on
|
|
526
526
|
// milestone/<MID>. Auto-checkout back to the integration branch.
|
|
527
|
-
if (getIsolationMode() === "none" && nativeIsRepo(base)) {
|
|
527
|
+
if (getIsolationMode(base) === "none" && nativeIsRepo(base)) {
|
|
528
528
|
try {
|
|
529
529
|
const currentBranch = nativeGetCurrentBranch(base);
|
|
530
530
|
if (currentBranch.startsWith("milestone/")) {
|
|
@@ -552,7 +552,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
552
552
|
return symlinkRe.test(p);
|
|
553
553
|
};
|
|
554
554
|
if (s.currentMilestoneId &&
|
|
555
|
-
getIsolationMode() !== "none" &&
|
|
555
|
+
getIsolationMode(base) !== "none" &&
|
|
556
556
|
!detectWorktreeName(base) &&
|
|
557
557
|
!isUnderGsdWorktrees(base)) {
|
|
558
558
|
buildResolver().enterMilestone(s.currentMilestoneId, {
|
|
@@ -597,8 +597,21 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
597
597
|
// call returns "db_unavailable", triggering artifact-retry which
|
|
598
598
|
// re-dispatches the same task — producing an infinite loop (#2419).
|
|
599
599
|
if (existsSync(gsdDbPath) && !isDbAvailable()) {
|
|
600
|
-
|
|
601
|
-
|
|
600
|
+
const dbStatus = getDbStatus();
|
|
601
|
+
const phaseHint = dbStatus.lastPhase === "open"
|
|
602
|
+
? "The database file could not be opened"
|
|
603
|
+
: dbStatus.lastPhase === "initSchema"
|
|
604
|
+
? "The database schema could not be initialized"
|
|
605
|
+
: dbStatus.lastPhase === "vacuum-recovery"
|
|
606
|
+
? "Corruption recovery (VACUUM) failed"
|
|
607
|
+
: dbStatus.attempted
|
|
608
|
+
? "The database could not be opened (phase unknown)"
|
|
609
|
+
: "The database provider could not be loaded";
|
|
610
|
+
const errorDetail = dbStatus.lastError ? ` (${dbStatus.lastError.message})` : "";
|
|
611
|
+
const providerHint = dbStatus.provider
|
|
612
|
+
? ` Provider: ${dbStatus.provider}.`
|
|
613
|
+
: " No SQLite provider available — check Node >= 22 or install better-sqlite3.";
|
|
614
|
+
ctx.ui.notify(`SQLite database exists but failed to open: ${gsdDbPath}. ${phaseHint}${errorDetail}.${providerHint}`, "error");
|
|
602
615
|
return releaseLockAndReturn();
|
|
603
616
|
}
|
|
604
617
|
// Initialize metrics
|
|
@@ -633,7 +646,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
633
646
|
}
|
|
634
647
|
}
|
|
635
648
|
// Snapshot installed skills
|
|
636
|
-
if (resolveSkillDiscoveryMode() !== "off") {
|
|
649
|
+
if (resolveSkillDiscoveryMode(base) !== "off") {
|
|
637
650
|
snapshotSkills();
|
|
638
651
|
}
|
|
639
652
|
ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
|
|
@@ -661,7 +674,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
661
674
|
// FlatRateContext used by selectAndApplyModel so user-declared
|
|
662
675
|
// flat-rate providers and externalCli auto-detection are respected.
|
|
663
676
|
const { isFlatRateProvider, buildFlatRateContext } = await import("./auto-model-selection.js");
|
|
664
|
-
const bannerPrefs = loadEffectiveGSDPreferences()?.preferences;
|
|
677
|
+
const bannerPrefs = loadEffectiveGSDPreferences(base)?.preferences;
|
|
665
678
|
const effectiveProvider = s.autoModeStartModel?.provider ?? ctx.model?.provider;
|
|
666
679
|
const effectivelyEnabled = routingConfig.enabled
|
|
667
680
|
&& (routingConfig.allow_flat_rate_providers
|
|
@@ -145,8 +145,8 @@ export function startAutoDetached(ctx, pi, base, verboseMode, options) {
|
|
|
145
145
|
});
|
|
146
146
|
}
|
|
147
147
|
/** Returns true if the project is configured for `isolation:worktree` mode. */
|
|
148
|
-
export function shouldUseWorktreeIsolation() {
|
|
149
|
-
const prefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
148
|
+
export function shouldUseWorktreeIsolation(basePath) {
|
|
149
|
+
const prefs = loadEffectiveGSDPreferences(basePath)?.preferences?.git;
|
|
150
150
|
if (prefs?.isolation === "worktree")
|
|
151
151
|
return true;
|
|
152
152
|
// Default is false — worktree isolation requires explicit opt-in
|
|
@@ -215,7 +215,7 @@ export function getAutoDashboardData() {
|
|
|
215
215
|
const rtkSavings = sessionId && s.basePath
|
|
216
216
|
? getRtkSessionSavings(s.basePath, sessionId)
|
|
217
217
|
: null;
|
|
218
|
-
const rtkEnabled = loadEffectiveGSDPreferences()?.preferences.experimental?.rtk === true;
|
|
218
|
+
const rtkEnabled = loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences.experimental?.rtk === true;
|
|
219
219
|
// Pending capture count — lazy check, non-fatal
|
|
220
220
|
let pendingCaptureCount = 0;
|
|
221
221
|
try {
|
|
@@ -393,7 +393,7 @@ function clearUnitTimeout() {
|
|
|
393
393
|
}
|
|
394
394
|
/** Build snapshot metric opts. */
|
|
395
395
|
function buildSnapshotOpts(_unitType, _unitId) {
|
|
396
|
-
const prefs = loadEffectiveGSDPreferences()?.preferences;
|
|
396
|
+
const prefs = loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences;
|
|
397
397
|
const uokFlags = resolveUokFlags(prefs);
|
|
398
398
|
return {
|
|
399
399
|
...(s.autoStartTime > 0 ? { autoSessionKey: String(s.autoStartTime) } : {}),
|
|
@@ -427,7 +427,7 @@ function handleLostSessionLock(ctx, lockStatus) {
|
|
|
427
427
|
restoreProjectRootEnv();
|
|
428
428
|
restoreMilestoneLockEnv();
|
|
429
429
|
deregisterSigtermHandler();
|
|
430
|
-
clearCmuxSidebar(loadEffectiveGSDPreferences()?.preferences);
|
|
430
|
+
clearCmuxSidebar(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences);
|
|
431
431
|
const base = lockBase();
|
|
432
432
|
const lockFilePath = base ? join(gsdRoot(base), "auto.lock") : "unknown";
|
|
433
433
|
const recoverySuggestion = "\nTo recover, run: gsd doctor --fix";
|
|
@@ -498,7 +498,7 @@ function cleanupAfterLoopExit(ctx) {
|
|
|
498
498
|
export async function stopAuto(ctx, pi, reason) {
|
|
499
499
|
if (!s.active && !s.paused)
|
|
500
500
|
return;
|
|
501
|
-
const loadedPreferences = loadEffectiveGSDPreferences()?.preferences;
|
|
501
|
+
const loadedPreferences = loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences;
|
|
502
502
|
const reasonSuffix = reason ? ` — ${reason}` : "";
|
|
503
503
|
try {
|
|
504
504
|
// ── Step 1: Timers and locks ──
|
|
@@ -1164,7 +1164,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1164
1164
|
});
|
|
1165
1165
|
// ── Auto-worktree / branch-mode: re-enter on resume ──
|
|
1166
1166
|
if (s.currentMilestoneId &&
|
|
1167
|
-
getIsolationMode() !== "none" &&
|
|
1167
|
+
getIsolationMode(s.originalBasePath || s.basePath) !== "none" &&
|
|
1168
1168
|
s.originalBasePath &&
|
|
1169
1169
|
!isInAutoWorktree(s.basePath) &&
|
|
1170
1170
|
!detectWorktreeName(s.basePath) &&
|
|
@@ -1197,7 +1197,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1197
1197
|
await openProjectDbIfPresent(s.basePath);
|
|
1198
1198
|
try {
|
|
1199
1199
|
await rebuildState(s.basePath);
|
|
1200
|
-
syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
|
|
1200
|
+
syncCmuxSidebar(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, await deriveState(s.basePath));
|
|
1201
1201
|
}
|
|
1202
1202
|
catch (e) {
|
|
1203
1203
|
debugLog("resume-rebuild-state-failed", {
|
|
@@ -1227,7 +1227,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1227
1227
|
}
|
|
1228
1228
|
updateSessionLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown");
|
|
1229
1229
|
writeLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown");
|
|
1230
|
-
logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "progress");
|
|
1230
|
+
logCmuxEvent(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "progress");
|
|
1231
1231
|
captureProjectRootEnv(s.originalBasePath || s.basePath);
|
|
1232
1232
|
startAutoCommandPolling(s.basePath);
|
|
1233
1233
|
await runAutoLoopWithUok({
|
|
@@ -1253,13 +1253,13 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1253
1253
|
return;
|
|
1254
1254
|
captureProjectRootEnv(s.originalBasePath || s.basePath);
|
|
1255
1255
|
try {
|
|
1256
|
-
syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
|
|
1256
|
+
syncCmuxSidebar(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, await deriveState(s.basePath));
|
|
1257
1257
|
}
|
|
1258
1258
|
catch (err) {
|
|
1259
1259
|
// Best-effort only — sidebar sync must never block auto-mode startup
|
|
1260
1260
|
logWarning("engine", `cmux sync failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
1261
1261
|
}
|
|
1262
|
-
logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, requestedStepMode ? "Step-mode started." : "Auto-mode started.", "progress");
|
|
1262
|
+
logCmuxEvent(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, requestedStepMode ? "Step-mode started." : "Auto-mode started.", "progress");
|
|
1263
1263
|
startAutoCommandPolling(s.basePath);
|
|
1264
1264
|
// Dispatch the first unit
|
|
1265
1265
|
await runAutoLoopWithUok({
|
|
@@ -19,6 +19,18 @@ function registerAlias(pi, toolDef, aliasName, canonicalName) {
|
|
|
19
19
|
promptGuidelines: [`Alias for ${canonicalName} — prefer the canonical name.`],
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Read a tool result's structured payload, accommodating MCP's `details` →
|
|
24
|
+
* `structuredContent` rename (#4472, #4477). In-process executions still
|
|
25
|
+
* deliver the payload on `result.details`; MCP-routed executions deliver it
|
|
26
|
+
* on `result.structuredContent` (post `adaptExecutorResult` transform). All
|
|
27
|
+
* `renderResult` callbacks in this file route through this helper so a future
|
|
28
|
+
* field rename only needs to be applied in one place.
|
|
29
|
+
*/
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- result shape varies by tool
|
|
31
|
+
function readDetails(result) {
|
|
32
|
+
return result?.details ?? result?.structuredContent;
|
|
33
|
+
}
|
|
22
34
|
export function registerDbTools(pi) {
|
|
23
35
|
// ─── gsd_decision_save (formerly gsd_save_decision) ─────────────────────
|
|
24
36
|
const decisionSaveExecute = async (_toolCallId, params, _signal, _onUpdate, _ctx) => {
|
|
@@ -92,7 +104,7 @@ export function registerDbTools(pi) {
|
|
|
92
104
|
return new Text(text, 0, 0);
|
|
93
105
|
},
|
|
94
106
|
renderResult(result, _options, theme) {
|
|
95
|
-
const d = result
|
|
107
|
+
const d = readDetails(result);
|
|
96
108
|
if (result.isError || d?.error) {
|
|
97
109
|
return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
|
|
98
110
|
}
|
|
@@ -175,7 +187,7 @@ export function registerDbTools(pi) {
|
|
|
175
187
|
return new Text(text, 0, 0);
|
|
176
188
|
},
|
|
177
189
|
renderResult(result, _options, theme) {
|
|
178
|
-
const d = result
|
|
190
|
+
const d = readDetails(result);
|
|
179
191
|
if (result.isError || d?.error) {
|
|
180
192
|
return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
|
|
181
193
|
}
|
|
@@ -255,7 +267,7 @@ export function registerDbTools(pi) {
|
|
|
255
267
|
return new Text(text, 0, 0);
|
|
256
268
|
},
|
|
257
269
|
renderResult(result, _options, theme) {
|
|
258
|
-
const d = result
|
|
270
|
+
const d = readDetails(result);
|
|
259
271
|
if (result.isError || d?.error) {
|
|
260
272
|
return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
|
|
261
273
|
}
|
|
@@ -301,7 +313,7 @@ export function registerDbTools(pi) {
|
|
|
301
313
|
return new Text(text, 0, 0);
|
|
302
314
|
},
|
|
303
315
|
renderResult(result, _options, theme) {
|
|
304
|
-
const d = result
|
|
316
|
+
const d = readDetails(result);
|
|
305
317
|
if (result.isError || d?.error) {
|
|
306
318
|
return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
|
|
307
319
|
}
|
|
@@ -382,7 +394,7 @@ export function registerDbTools(pi) {
|
|
|
382
394
|
return new Text(theme.fg("toolTitle", theme.bold("milestone_generate_id")), 0, 0);
|
|
383
395
|
},
|
|
384
396
|
renderResult(result, _options, theme) {
|
|
385
|
-
const d = result
|
|
397
|
+
const d = readDetails(result);
|
|
386
398
|
if (result.isError || d?.error) {
|
|
387
399
|
return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
|
|
388
400
|
}
|
|
@@ -967,13 +979,31 @@ export function registerDbTools(pi) {
|
|
|
967
979
|
text += theme.fg("dim", ` → ${args.verdict ?? ""}`);
|
|
968
980
|
return new Text(text, 0, 0);
|
|
969
981
|
},
|
|
982
|
+
/**
|
|
983
|
+
* Render the save_gate_result tool output for the TUI.
|
|
984
|
+
*
|
|
985
|
+
* Prefers structured fields, but falls back to `content[0].text` when the
|
|
986
|
+
* structured payload is empty. Defensive: the structural fix on this
|
|
987
|
+
* branch plumbs `details` through MCP via `structuredContent`, but older
|
|
988
|
+
* hosts, a future handler that forgets `structuredContent`, or any drop
|
|
989
|
+
* of non-standard return fields would otherwise render as
|
|
990
|
+
* "undefined: undefined". Same fallback applies to error rendering, and
|
|
991
|
+
* we strip a leading `Error:` from the fallback text to avoid producing
|
|
992
|
+
* `Error: Error: ...`.
|
|
993
|
+
*/
|
|
970
994
|
renderResult(result, _options, theme) {
|
|
971
|
-
const d = result
|
|
995
|
+
const d = readDetails(result);
|
|
972
996
|
if (result.isError || d?.error) {
|
|
973
|
-
|
|
997
|
+
const rawMsg = d?.error ?? result.content?.[0]?.text ?? "unknown";
|
|
998
|
+
const msg = rawMsg.replace(/^\s*Error:\s*/i, "");
|
|
999
|
+
return new Text(theme.fg("error", `Error: ${msg}`), 0, 0);
|
|
1000
|
+
}
|
|
1001
|
+
if (!d?.gateId || !d?.verdict) {
|
|
1002
|
+
const text = result.content?.[0]?.text ?? "Gate result saved";
|
|
1003
|
+
return new Text(theme.fg("success", text), 0, 0);
|
|
974
1004
|
}
|
|
975
|
-
const color = d
|
|
976
|
-
return new Text(theme.fg(color, `${d
|
|
1005
|
+
const color = d.verdict === "flag" ? "warning" : "success";
|
|
1006
|
+
return new Text(theme.fg(color, `${d.gateId}: ${d.verdict}`), 0, 0);
|
|
977
1007
|
},
|
|
978
1008
|
};
|
|
979
1009
|
pi.registerTool(saveGateResultTool);
|
|
@@ -0,0 +1,93 @@
|
|
|
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
|
+
import { Type } from "@sinclair/typebox";
|
|
6
|
+
import { executeGsdExec } from "../tools/exec-tool.js";
|
|
7
|
+
import { executeExecSearch } from "../tools/exec-search-tool.js";
|
|
8
|
+
import { executeResume } from "../tools/resume-tool.js";
|
|
9
|
+
import { loadEffectiveGSDPreferences } from "../preferences.js";
|
|
10
|
+
import { logWarning } from "../workflow-logger.js";
|
|
11
|
+
export function registerExecTools(pi) {
|
|
12
|
+
pi.registerTool({
|
|
13
|
+
name: "gsd_exec",
|
|
14
|
+
label: "Exec (Sandboxed)",
|
|
15
|
+
description: "Run a short script (bash/node/python) in a subprocess. Full stdout/stderr persist to " +
|
|
16
|
+
".gsd/exec/<id>.{stdout,stderr,meta.json}; only a short digest returns in context. Use " +
|
|
17
|
+
"this instead of reading many files or emitting large tool outputs — e.g. have the script " +
|
|
18
|
+
"count/grep/summarize and log the finding. Enabled by default; opt out via " +
|
|
19
|
+
"preferences.context_mode.enabled=false.",
|
|
20
|
+
promptSnippet: "Run a bash/node/python script in a sandbox; full output is saved to disk and only a digest returns",
|
|
21
|
+
promptGuidelines: [
|
|
22
|
+
"Prefer gsd_exec for analyses that would otherwise read >3 files or produce large tool output.",
|
|
23
|
+
"Write scripts that log the finding (counts, matches, summaries) rather than raw dumps.",
|
|
24
|
+
"The digest is the last ~300 chars of stdout — size your log output accordingly.",
|
|
25
|
+
"Need the full output? Read the stdout_path returned in details (file on local disk).",
|
|
26
|
+
],
|
|
27
|
+
parameters: Type.Object({
|
|
28
|
+
runtime: Type.Union([Type.Literal("bash"), Type.Literal("node"), Type.Literal("python")], { description: "Interpreter: bash (-c), node (-e), or python3 (-c)." }),
|
|
29
|
+
script: Type.String({ description: "Script body. Keep output small (log the finding, not the data)." }),
|
|
30
|
+
purpose: Type.Optional(Type.String({ description: "Short label recorded in meta.json for later review." })),
|
|
31
|
+
timeout_ms: Type.Optional(Type.Number({
|
|
32
|
+
description: "Per-invocation timeout (ms). Capped at 600000. Default from preferences.",
|
|
33
|
+
minimum: 1_000,
|
|
34
|
+
maximum: 600_000,
|
|
35
|
+
})),
|
|
36
|
+
}),
|
|
37
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
38
|
+
let prefs = null;
|
|
39
|
+
try {
|
|
40
|
+
prefs = loadEffectiveGSDPreferences();
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
logWarning("tool", `gsd_exec could not load preferences: ${err instanceof Error ? err.message : String(err)}`);
|
|
44
|
+
}
|
|
45
|
+
return executeGsdExec(params, {
|
|
46
|
+
baseDir: process.cwd(),
|
|
47
|
+
preferences: prefs?.preferences ?? null,
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
pi.registerTool({
|
|
52
|
+
name: "gsd_exec_search",
|
|
53
|
+
label: "Search gsd_exec History",
|
|
54
|
+
description: "List prior gsd_exec runs (most recent first) from .gsd/exec/*.meta.json. Useful for " +
|
|
55
|
+
"rediscovering the stdout_path of an earlier run without re-executing it. Read-only.",
|
|
56
|
+
promptSnippet: "Search prior gsd_exec runs by substring, runtime, or failing-only filter",
|
|
57
|
+
promptGuidelines: [
|
|
58
|
+
"Use this before re-running an expensive analysis — the prior run's stdout file may still answer.",
|
|
59
|
+
"The preview shows the trailing ~300 chars of stdout; read stdout_path for the full transcript.",
|
|
60
|
+
],
|
|
61
|
+
parameters: Type.Object({
|
|
62
|
+
query: Type.Optional(Type.String({ description: "Substring matched against id and purpose (case-insensitive)." })),
|
|
63
|
+
runtime: Type.Optional(Type.Union([Type.Literal("bash"), Type.Literal("node"), Type.Literal("python")], {
|
|
64
|
+
description: "Restrict to one runtime.",
|
|
65
|
+
})),
|
|
66
|
+
failing_only: Type.Optional(Type.Boolean({ description: "Only non-zero exit codes and timeouts." })),
|
|
67
|
+
limit: Type.Optional(Type.Number({ description: "Max results (default 20, cap 200)", minimum: 1, maximum: 200 })),
|
|
68
|
+
}),
|
|
69
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
70
|
+
return executeExecSearch(params, {
|
|
71
|
+
baseDir: process.cwd(),
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
pi.registerTool({
|
|
76
|
+
name: "gsd_resume",
|
|
77
|
+
label: "Resume (Read Snapshot)",
|
|
78
|
+
description: "Return the contents of .gsd/last-snapshot.md — a ≤2 KB digest of top memories, recent " +
|
|
79
|
+
"gsd_exec runs, and active context, written automatically on session_before_compact. Use " +
|
|
80
|
+
"this after compaction or session resume to re-orient quickly.",
|
|
81
|
+
promptSnippet: "Read the pre-compaction snapshot to re-orient after context loss",
|
|
82
|
+
promptGuidelines: [
|
|
83
|
+
"Call this right after a session resumes if you feel you've lost durable context.",
|
|
84
|
+
"The snapshot is a summary — use memory_query or gsd_exec_search for detail.",
|
|
85
|
+
],
|
|
86
|
+
parameters: Type.Object({}),
|
|
87
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
88
|
+
return executeResume(params, {
|
|
89
|
+
baseDir: process.cwd(),
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
}
|
|
@@ -4,6 +4,7 @@ import { registerWorktreeCommand } from "../worktree-command.js";
|
|
|
4
4
|
import { loadEcosystemExtensions } from "../ecosystem/loader.js";
|
|
5
5
|
import { registerDbTools } from "./db-tools.js";
|
|
6
6
|
import { registerDynamicTools } from "./dynamic-tools.js";
|
|
7
|
+
import { registerExecTools } from "./exec-tools.js";
|
|
7
8
|
import { registerJournalTools } from "./journal-tools.js";
|
|
8
9
|
import { registerMemoryTools } from "./memory-tools.js";
|
|
9
10
|
import { registerQueryTools } from "./query-tools.js";
|
|
@@ -86,6 +87,7 @@ export function registerGsdExtension(pi) {
|
|
|
86
87
|
["journal-tools", () => registerJournalTools(pi)],
|
|
87
88
|
["query-tools", () => registerQueryTools(pi)],
|
|
88
89
|
["memory-tools", () => registerMemoryTools(pi)],
|
|
90
|
+
["exec-tools", () => registerExecTools(pi)],
|
|
89
91
|
["shortcuts", () => registerShortcuts(pi)],
|
|
90
92
|
["hooks", () => registerHooks(pi, ecosystemHandlers)],
|
|
91
93
|
["ecosystem", () => {
|