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
|
@@ -867,6 +867,69 @@ function normalizeToolResultContent(content: unknown): ExternalToolResultContent
|
|
|
867
867
|
return blocks.length > 0 ? blocks : [{ type: "text", text: "" }];
|
|
868
868
|
}
|
|
869
869
|
|
|
870
|
+
/**
|
|
871
|
+
* Extract a `details` payload from an MCP tool-result block.
|
|
872
|
+
*
|
|
873
|
+
* MCP's `CallToolResult` carries structured data in `structuredContent` — the
|
|
874
|
+
* protocol's supported channel for non-text payloads. Claude Code's synthetic
|
|
875
|
+
* user message may surface that field in one of two shapes depending on SDK
|
|
876
|
+
* version: as a sibling on the `mcp_tool_result` block itself, or as a
|
|
877
|
+
* dedicated content sub-block with `type: "structuredContent"`. Snake-case
|
|
878
|
+
* (`structured_content`) is accepted defensively in case a transport hop
|
|
879
|
+
* rewrites casing. All other shapes fall back to an empty object so callers
|
|
880
|
+
* can rely on `details` being present.
|
|
881
|
+
*/
|
|
882
|
+
function extractStructuredDetailsFromBlock(block: Record<string, unknown>): Record<string, unknown> | undefined {
|
|
883
|
+
const sibling = block.structuredContent ?? (block as Record<string, unknown>).structured_content;
|
|
884
|
+
if (sibling && typeof sibling === "object" && !Array.isArray(sibling)) {
|
|
885
|
+
return sibling as Record<string, unknown>;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
if (Array.isArray(block.content)) {
|
|
889
|
+
for (const item of block.content) {
|
|
890
|
+
if (!item || typeof item !== "object") continue;
|
|
891
|
+
const sub = item as Record<string, unknown>;
|
|
892
|
+
if (sub.type !== "structuredContent" && sub.type !== "structured_content") continue;
|
|
893
|
+
const payload = sub.structuredContent ?? sub.structured_content ?? sub.data ?? sub.value;
|
|
894
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
895
|
+
return payload as Record<string, unknown>;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// Return undefined (not {}) when no structured payload is present, matching
|
|
901
|
+
// the pre-#4477 contract where `details` was nullable. An empty-object
|
|
902
|
+
// sentinel is truthy and breaks downstream consumers that gate on
|
|
903
|
+
// `if (details)`. `undefined` matches the type of the field these results
|
|
904
|
+
// flow into (`Record<string, unknown> | undefined`).
|
|
905
|
+
return undefined;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* True for items that are MCP `structuredContent` pseudo-blocks living inside
|
|
910
|
+
* a tool-result `content[]` array. These blocks carry the structured payload
|
|
911
|
+
* (extracted separately by `extractStructuredDetailsFromBlock`) and must NOT
|
|
912
|
+
* leak into the visible content rendered to the user — otherwise the renderer
|
|
913
|
+
* stringifies the JSON pseudo-block and shows it next to the actual tool
|
|
914
|
+
* output. See PR #4477 review (CodeRabbit, post-fix-round).
|
|
915
|
+
*/
|
|
916
|
+
function isStructuredContentPseudoBlock(item: unknown): boolean {
|
|
917
|
+
if (!item || typeof item !== "object") return false;
|
|
918
|
+
const type = (item as Record<string, unknown>).type;
|
|
919
|
+
return type === "structuredContent" || type === "structured_content";
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* Strip `structuredContent` pseudo-blocks from a tool-result content array
|
|
924
|
+
* before normalization. The structured payload is extracted via the sibling
|
|
925
|
+
* `structuredContent` field (or a dedicated extractor pass on the raw block);
|
|
926
|
+
* the visible content path must not include the pseudo-block itself.
|
|
927
|
+
*/
|
|
928
|
+
function stripStructuredContentPseudoBlocks(content: unknown): unknown {
|
|
929
|
+
if (!Array.isArray(content)) return content;
|
|
930
|
+
return content.filter((item) => !isStructuredContentPseudoBlock(item));
|
|
931
|
+
}
|
|
932
|
+
|
|
870
933
|
/** Extract tool result payloads from an SDK synthetic user message, keyed by tool-use ID. */
|
|
871
934
|
export function extractToolResultsFromSdkUserMessage(message: SDKUserMessage): Array<{
|
|
872
935
|
toolUseId: string;
|
|
@@ -890,8 +953,8 @@ export function extractToolResultsFromSdkUserMessage(message: SDKUserMessage): A
|
|
|
890
953
|
extracted.push({
|
|
891
954
|
toolUseId,
|
|
892
955
|
result: {
|
|
893
|
-
content: normalizeToolResultContent(block.content),
|
|
894
|
-
details:
|
|
956
|
+
content: normalizeToolResultContent(stripStructuredContentPseudoBlocks(block.content)),
|
|
957
|
+
details: extractStructuredDetailsFromBlock(block),
|
|
895
958
|
isError: block.is_error === true,
|
|
896
959
|
},
|
|
897
960
|
});
|
|
@@ -906,8 +969,8 @@ export function extractToolResultsFromSdkUserMessage(message: SDKUserMessage): A
|
|
|
906
969
|
extracted.push({
|
|
907
970
|
toolUseId,
|
|
908
971
|
result: {
|
|
909
|
-
content: normalizeToolResultContent(toolResult.content),
|
|
910
|
-
details:
|
|
972
|
+
content: normalizeToolResultContent(stripStructuredContentPseudoBlocks(toolResult.content)),
|
|
973
|
+
details: extractStructuredDetailsFromBlock(toolResult),
|
|
911
974
|
isError: toolResult.is_error === true,
|
|
912
975
|
},
|
|
913
976
|
});
|
|
@@ -372,13 +372,146 @@ describe("stream-adapter — Claude Code external tool results", () => {
|
|
|
372
372
|
toolUseId: "tool-bash-1",
|
|
373
373
|
result: {
|
|
374
374
|
content: [{ type: "text", text: "line 1\nline 2" }],
|
|
375
|
-
|
|
375
|
+
// extractStructuredDetailsFromBlock returns undefined when no
|
|
376
|
+
// structured payload exists, restoring the pre-#4477 nullable
|
|
377
|
+
// contract (#4477 review feedback).
|
|
378
|
+
details: undefined,
|
|
376
379
|
isError: false,
|
|
377
380
|
},
|
|
378
381
|
},
|
|
379
382
|
]);
|
|
380
383
|
});
|
|
381
384
|
|
|
385
|
+
test("extractToolResultsFromSdkUserMessage reads structuredContent as a sibling field (#4472)", () => {
|
|
386
|
+
const message: SDKUserMessage = {
|
|
387
|
+
type: "user",
|
|
388
|
+
session_id: "sess-1",
|
|
389
|
+
parent_tool_use_id: "tool-mcp-1",
|
|
390
|
+
message: {
|
|
391
|
+
role: "user",
|
|
392
|
+
content: [
|
|
393
|
+
{
|
|
394
|
+
type: "mcp_tool_result",
|
|
395
|
+
tool_use_id: "tool-mcp-1",
|
|
396
|
+
content: [{ type: "text", text: "Gate Q3 result saved: verdict=pass" }],
|
|
397
|
+
is_error: false,
|
|
398
|
+
structuredContent: { gateId: "Q3", verdict: "pass" },
|
|
399
|
+
} as unknown as Record<string, unknown>,
|
|
400
|
+
],
|
|
401
|
+
},
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
const results = extractToolResultsFromSdkUserMessage(message);
|
|
405
|
+
assert.deepEqual(results[0].result.details, { gateId: "Q3", verdict: "pass" });
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
test("extractToolResultsFromSdkUserMessage reads structuredContent from a content sub-block (#4472)", () => {
|
|
409
|
+
const message: SDKUserMessage = {
|
|
410
|
+
type: "user",
|
|
411
|
+
session_id: "sess-1",
|
|
412
|
+
parent_tool_use_id: "tool-mcp-2",
|
|
413
|
+
message: {
|
|
414
|
+
role: "user",
|
|
415
|
+
content: [
|
|
416
|
+
{
|
|
417
|
+
type: "mcp_tool_result",
|
|
418
|
+
tool_use_id: "tool-mcp-2",
|
|
419
|
+
content: [
|
|
420
|
+
{ type: "text", text: "Gate Q4 result saved: verdict=flag" },
|
|
421
|
+
{ type: "structuredContent", structuredContent: { gateId: "Q4", verdict: "flag" } },
|
|
422
|
+
],
|
|
423
|
+
is_error: false,
|
|
424
|
+
} as unknown as Record<string, unknown>,
|
|
425
|
+
],
|
|
426
|
+
},
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
const results = extractToolResultsFromSdkUserMessage(message);
|
|
430
|
+
assert.deepEqual(results[0].result.details, { gateId: "Q4", verdict: "flag" });
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test("#4477 extractToolResultsFromSdkUserMessage does NOT leak structuredContent pseudo-blocks into visible content", () => {
|
|
434
|
+
// Regression: when a content sub-block carries `type: "structuredContent"`,
|
|
435
|
+
// it carries the structured payload (extracted separately into `details`)
|
|
436
|
+
// and must NOT appear in the visible `content` array — otherwise the
|
|
437
|
+
// renderer stringifies the JSON pseudo-block and shows it next to the
|
|
438
|
+
// actual tool output. See PR #4477 review (CodeRabbit, post-fix-round).
|
|
439
|
+
const message: SDKUserMessage = {
|
|
440
|
+
type: "user",
|
|
441
|
+
session_id: "sess-1",
|
|
442
|
+
parent_tool_use_id: "tool-mcp-strip",
|
|
443
|
+
message: {
|
|
444
|
+
role: "user",
|
|
445
|
+
content: [
|
|
446
|
+
{
|
|
447
|
+
type: "mcp_tool_result",
|
|
448
|
+
tool_use_id: "tool-mcp-strip",
|
|
449
|
+
content: [
|
|
450
|
+
{ type: "text", text: "Gate Q5 result saved: verdict=pass" },
|
|
451
|
+
{ type: "structuredContent", structuredContent: { gateId: "Q5", verdict: "pass" } },
|
|
452
|
+
{ type: "text", text: "second visible line" },
|
|
453
|
+
// snake_case variant — also a pseudo-block; also must be stripped
|
|
454
|
+
{ type: "structured_content", structured_content: { extra: "data" } },
|
|
455
|
+
],
|
|
456
|
+
is_error: false,
|
|
457
|
+
} as unknown as Record<string, unknown>,
|
|
458
|
+
],
|
|
459
|
+
},
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
const results = extractToolResultsFromSdkUserMessage(message);
|
|
463
|
+
assert.equal(results.length, 1, "should extract one result");
|
|
464
|
+
const result = results[0].result;
|
|
465
|
+
|
|
466
|
+
// The structured payload IS extracted to `details`.
|
|
467
|
+
assert.deepEqual(result.details, { gateId: "Q5", verdict: "pass" });
|
|
468
|
+
|
|
469
|
+
// The visible content has the two text blocks but NEITHER pseudo-block.
|
|
470
|
+
const visibleTexts = result.content.map((c: any) => c.text);
|
|
471
|
+
assert.deepEqual(
|
|
472
|
+
visibleTexts,
|
|
473
|
+
["Gate Q5 result saved: verdict=pass", "second visible line"],
|
|
474
|
+
"visible content must include only the two text blocks; both structuredContent variants must be stripped",
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
// Belt-and-suspenders: assert no rendered text shows the JSON serialization
|
|
478
|
+
// of a pseudo-block. We don't check for bare keys like "gateId" or "verdict"
|
|
479
|
+
// because those are legitimate words in the gate-result message text. The
|
|
480
|
+
// regression signature would be a JSON-shaped substring that could only
|
|
481
|
+
// appear via stringification.
|
|
482
|
+
const allText = visibleTexts.join("\n");
|
|
483
|
+
assert.ok(
|
|
484
|
+
!allText.includes('"structuredContent"'),
|
|
485
|
+
"rendered content must not include the pseudo-block type marker as JSON text",
|
|
486
|
+
);
|
|
487
|
+
assert.ok(
|
|
488
|
+
!allText.includes('"structured_content"'),
|
|
489
|
+
"rendered content must not include the snake_case pseudo-block type marker as JSON text",
|
|
490
|
+
);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
test("extractToolResultsFromSdkUserMessage accepts snake_case structured_content defensively (#4472)", () => {
|
|
494
|
+
const message: SDKUserMessage = {
|
|
495
|
+
type: "user",
|
|
496
|
+
session_id: "sess-1",
|
|
497
|
+
parent_tool_use_id: "tool-mcp-3",
|
|
498
|
+
message: {
|
|
499
|
+
role: "user",
|
|
500
|
+
content: [
|
|
501
|
+
{
|
|
502
|
+
type: "mcp_tool_result",
|
|
503
|
+
tool_use_id: "tool-mcp-3",
|
|
504
|
+
content: [{ type: "text", text: "ok" }],
|
|
505
|
+
structured_content: { operation: "save_gate_result" },
|
|
506
|
+
} as unknown as Record<string, unknown>,
|
|
507
|
+
],
|
|
508
|
+
},
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
const results = extractToolResultsFromSdkUserMessage(message);
|
|
512
|
+
assert.deepEqual(results[0].result.details, { operation: "save_gate_result" });
|
|
513
|
+
});
|
|
514
|
+
|
|
382
515
|
test("extractToolResultsFromSdkUserMessage falls back to tool_use_result", () => {
|
|
383
516
|
const message: SDKUserMessage = {
|
|
384
517
|
type: "user",
|
|
@@ -398,7 +531,9 @@ describe("stream-adapter — Claude Code external tool results", () => {
|
|
|
398
531
|
toolUseId: "tool-read-1",
|
|
399
532
|
result: {
|
|
400
533
|
content: [{ type: "text", text: "file contents" }],
|
|
401
|
-
|
|
534
|
+
// undefined (not {}) per the restored nullable contract — see
|
|
535
|
+
// the analogous assertion in the tool_result test above.
|
|
536
|
+
details: undefined,
|
|
402
537
|
isError: true,
|
|
403
538
|
},
|
|
404
539
|
},
|
|
@@ -60,7 +60,7 @@ import { initRoutingHistory } from "./routing-history.js";
|
|
|
60
60
|
import { restoreHookState, resetHookState } from "./post-unit-hooks.js";
|
|
61
61
|
import { resetProactiveHealing, setLevelChangeCallback } from "./doctor-proactive.js";
|
|
62
62
|
import { snapshotSkills } from "./skill-discovery.js";
|
|
63
|
-
import { isDbAvailable, getMilestone, openDatabase } from "./gsd-db.js";
|
|
63
|
+
import { isDbAvailable, getMilestone, openDatabase, getDbStatus } from "./gsd-db.js";
|
|
64
64
|
import { hideFooter } from "./auto-dashboard.js";
|
|
65
65
|
import {
|
|
66
66
|
debugLog,
|
|
@@ -92,7 +92,7 @@ import type { WorktreeResolver } from "./worktree-resolver.js";
|
|
|
92
92
|
import { getSessionModelOverride } from "./session-model-override.js";
|
|
93
93
|
|
|
94
94
|
export interface BootstrapDeps {
|
|
95
|
-
shouldUseWorktreeIsolation: () => boolean;
|
|
95
|
+
shouldUseWorktreeIsolation: (basePath?: string) => boolean;
|
|
96
96
|
registerSigtermHandler: (basePath: string) => void;
|
|
97
97
|
lockBase: () => string;
|
|
98
98
|
buildResolver: () => WorktreeResolver;
|
|
@@ -343,7 +343,7 @@ export async function bootstrapAutoSession(
|
|
|
343
343
|
const hasLocalGit = existsSync(join(base, ".git"));
|
|
344
344
|
if (!hasLocalGit || isInheritedRepo(base)) {
|
|
345
345
|
const mainBranch =
|
|
346
|
-
loadEffectiveGSDPreferences()?.preferences?.git?.main_branch || "main";
|
|
346
|
+
loadEffectiveGSDPreferences(base)?.preferences?.git?.main_branch || "main";
|
|
347
347
|
nativeInit(base, mainBranch);
|
|
348
348
|
}
|
|
349
349
|
|
|
@@ -361,7 +361,7 @@ export async function bootstrapAutoSession(
|
|
|
361
361
|
// Ensure .gitignore has baseline patterns.
|
|
362
362
|
// ensureGitignore checks for git-tracked .gsd/ files and skips the
|
|
363
363
|
// ".gsd" pattern if the project intentionally tracks .gsd/ in git.
|
|
364
|
-
const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
364
|
+
const gitPrefs = loadEffectiveGSDPreferences(base)?.preferences?.git;
|
|
365
365
|
const manageGitignore = gitPrefs?.manage_gitignore;
|
|
366
366
|
ensureGitignore(base, { manageGitignore });
|
|
367
367
|
if (manageGitignore !== false) untrackRuntimeFiles(base);
|
|
@@ -390,7 +390,7 @@ export async function bootstrapAutoSession(
|
|
|
390
390
|
// Initialize GitServiceImpl
|
|
391
391
|
s.gitService = new GitServiceImpl(
|
|
392
392
|
s.basePath,
|
|
393
|
-
loadEffectiveGSDPreferences()?.preferences?.git ?? {},
|
|
393
|
+
loadEffectiveGSDPreferences(base)?.preferences?.git ?? {},
|
|
394
394
|
);
|
|
395
395
|
|
|
396
396
|
// ── Debug mode ──
|
|
@@ -434,7 +434,7 @@ export async function bootstrapAutoSession(
|
|
|
434
434
|
// was lost due to session ending between completion and teardown.
|
|
435
435
|
// Must run after DB open and before worktree entry.
|
|
436
436
|
try {
|
|
437
|
-
const auditResult = auditOrphanedMilestoneBranches(base, getIsolationMode());
|
|
437
|
+
const auditResult = auditOrphanedMilestoneBranches(base, getIsolationMode(base));
|
|
438
438
|
for (const msg of auditResult.recovered) {
|
|
439
439
|
ctx.ui.notify(`Orphan audit: ${msg}`, "info");
|
|
440
440
|
}
|
|
@@ -454,7 +454,7 @@ export async function bootstrapAutoSession(
|
|
|
454
454
|
// Stale worktree state recovery (#654)
|
|
455
455
|
if (
|
|
456
456
|
state.activeMilestone &&
|
|
457
|
-
shouldUseWorktreeIsolation() &&
|
|
457
|
+
shouldUseWorktreeIsolation(base) &&
|
|
458
458
|
!detectWorktreeName(base)
|
|
459
459
|
) {
|
|
460
460
|
const wtPath = getAutoWorktreePath(base, state.activeMilestone.id);
|
|
@@ -472,7 +472,7 @@ export async function bootstrapAutoSession(
|
|
|
472
472
|
if (
|
|
473
473
|
state.activeMilestone &&
|
|
474
474
|
(state.phase === "pre-planning" || state.phase === "complete") &&
|
|
475
|
-
getIsolationMode() !== "none" &&
|
|
475
|
+
getIsolationMode(base) !== "none" &&
|
|
476
476
|
!detectWorktreeName(base) &&
|
|
477
477
|
!base.includes(`${pathSep}.gsd${pathSep}worktrees${pathSep}`)
|
|
478
478
|
) {
|
|
@@ -676,7 +676,7 @@ export async function bootstrapAutoSession(
|
|
|
676
676
|
|
|
677
677
|
// Capture integration branch
|
|
678
678
|
if (s.currentMilestoneId) {
|
|
679
|
-
if (getIsolationMode() !== "none") {
|
|
679
|
+
if (getIsolationMode(base) !== "none") {
|
|
680
680
|
captureIntegrationBranch(base, s.currentMilestoneId);
|
|
681
681
|
}
|
|
682
682
|
setActiveMilestoneId(base, s.currentMilestoneId);
|
|
@@ -685,7 +685,7 @@ export async function bootstrapAutoSession(
|
|
|
685
685
|
// Guard against stale milestone branch when isolation:none (#3613).
|
|
686
686
|
// A prior session with isolation:branch/worktree may have left HEAD on
|
|
687
687
|
// milestone/<MID>. Auto-checkout back to the integration branch.
|
|
688
|
-
if (getIsolationMode() === "none" && nativeIsRepo(base)) {
|
|
688
|
+
if (getIsolationMode(base) === "none" && nativeIsRepo(base)) {
|
|
689
689
|
try {
|
|
690
690
|
const currentBranch = nativeGetCurrentBranch(base);
|
|
691
691
|
if (currentBranch.startsWith("milestone/")) {
|
|
@@ -716,7 +716,7 @@ export async function bootstrapAutoSession(
|
|
|
716
716
|
|
|
717
717
|
if (
|
|
718
718
|
s.currentMilestoneId &&
|
|
719
|
-
getIsolationMode() !== "none" &&
|
|
719
|
+
getIsolationMode(base) !== "none" &&
|
|
720
720
|
!detectWorktreeName(base) &&
|
|
721
721
|
!isUnderGsdWorktrees(base)
|
|
722
722
|
) {
|
|
@@ -762,9 +762,22 @@ export async function bootstrapAutoSession(
|
|
|
762
762
|
// call returns "db_unavailable", triggering artifact-retry which
|
|
763
763
|
// re-dispatches the same task — producing an infinite loop (#2419).
|
|
764
764
|
if (existsSync(gsdDbPath) && !isDbAvailable()) {
|
|
765
|
+
const dbStatus = getDbStatus();
|
|
766
|
+
const phaseHint = dbStatus.lastPhase === "open"
|
|
767
|
+
? "The database file could not be opened"
|
|
768
|
+
: dbStatus.lastPhase === "initSchema"
|
|
769
|
+
? "The database schema could not be initialized"
|
|
770
|
+
: dbStatus.lastPhase === "vacuum-recovery"
|
|
771
|
+
? "Corruption recovery (VACUUM) failed"
|
|
772
|
+
: dbStatus.attempted
|
|
773
|
+
? "The database could not be opened (phase unknown)"
|
|
774
|
+
: "The database provider could not be loaded";
|
|
775
|
+
const errorDetail = dbStatus.lastError ? ` (${dbStatus.lastError.message})` : "";
|
|
776
|
+
const providerHint = dbStatus.provider
|
|
777
|
+
? ` Provider: ${dbStatus.provider}.`
|
|
778
|
+
: " No SQLite provider available — check Node >= 22 or install better-sqlite3.";
|
|
765
779
|
ctx.ui.notify(
|
|
766
|
-
|
|
767
|
-
"Check for corrupt gsd.db or missing native SQLite bindings.",
|
|
780
|
+
`SQLite database exists but failed to open: ${gsdDbPath}. ${phaseHint}${errorDetail}.${providerHint}`,
|
|
768
781
|
"error",
|
|
769
782
|
);
|
|
770
783
|
return releaseLockAndReturn();
|
|
@@ -806,7 +819,7 @@ export async function bootstrapAutoSession(
|
|
|
806
819
|
}
|
|
807
820
|
|
|
808
821
|
// Snapshot installed skills
|
|
809
|
-
if (resolveSkillDiscoveryMode() !== "off") {
|
|
822
|
+
if (resolveSkillDiscoveryMode(base) !== "off") {
|
|
810
823
|
snapshotSkills();
|
|
811
824
|
}
|
|
812
825
|
|
|
@@ -840,7 +853,7 @@ export async function bootstrapAutoSession(
|
|
|
840
853
|
// FlatRateContext used by selectAndApplyModel so user-declared
|
|
841
854
|
// flat-rate providers and externalCli auto-detection are respected.
|
|
842
855
|
const { isFlatRateProvider, buildFlatRateContext } = await import("./auto-model-selection.js");
|
|
843
|
-
const bannerPrefs = loadEffectiveGSDPreferences()?.preferences;
|
|
856
|
+
const bannerPrefs = loadEffectiveGSDPreferences(base)?.preferences;
|
|
844
857
|
const effectiveProvider = s.autoModeStartModel?.provider ?? ctx.model?.provider;
|
|
845
858
|
const effectivelyEnabled = routingConfig.enabled
|
|
846
859
|
&& (routingConfig.allow_flat_rate_providers
|
|
@@ -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 {
|
|
@@ -1495,7 +1495,7 @@ export async function startAuto(
|
|
|
1495
1495
|
// ── Auto-worktree / branch-mode: re-enter on resume ──
|
|
1496
1496
|
if (
|
|
1497
1497
|
s.currentMilestoneId &&
|
|
1498
|
-
getIsolationMode() !== "none" &&
|
|
1498
|
+
getIsolationMode(s.originalBasePath || s.basePath) !== "none" &&
|
|
1499
1499
|
s.originalBasePath &&
|
|
1500
1500
|
!isInAutoWorktree(s.basePath) &&
|
|
1501
1501
|
!detectWorktreeName(s.basePath) &&
|
|
@@ -1534,7 +1534,7 @@ export async function startAuto(
|
|
|
1534
1534
|
await openProjectDbIfPresent(s.basePath);
|
|
1535
1535
|
try {
|
|
1536
1536
|
await rebuildState(s.basePath);
|
|
1537
|
-
syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
|
|
1537
|
+
syncCmuxSidebar(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, await deriveState(s.basePath));
|
|
1538
1538
|
} catch (e) {
|
|
1539
1539
|
debugLog("resume-rebuild-state-failed", {
|
|
1540
1540
|
error: e instanceof Error ? e.message : String(e),
|
|
@@ -1584,7 +1584,7 @@ export async function startAuto(
|
|
|
1584
1584
|
"resuming",
|
|
1585
1585
|
s.currentMilestoneId ?? "unknown",
|
|
1586
1586
|
);
|
|
1587
|
-
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");
|
|
1588
1588
|
|
|
1589
1589
|
captureProjectRootEnv(s.originalBasePath || s.basePath);
|
|
1590
1590
|
startAutoCommandPolling(s.basePath);
|
|
@@ -1622,12 +1622,12 @@ export async function startAuto(
|
|
|
1622
1622
|
|
|
1623
1623
|
captureProjectRootEnv(s.originalBasePath || s.basePath);
|
|
1624
1624
|
try {
|
|
1625
|
-
syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
|
|
1625
|
+
syncCmuxSidebar(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, await deriveState(s.basePath));
|
|
1626
1626
|
} catch (err) {
|
|
1627
1627
|
// Best-effort only — sidebar sync must never block auto-mode startup
|
|
1628
1628
|
logWarning("engine", `cmux sync failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
1629
1629
|
}
|
|
1630
|
-
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");
|
|
1631
1631
|
|
|
1632
1632
|
startAutoCommandPolling(s.basePath);
|
|
1633
1633
|
|
|
@@ -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
|
|