Skip to content

Commit ec6490b

Browse files
committed
Fix runtime recovery output and grammar wrapper loading
1 parent f9dbcdc commit ec6490b

13 files changed

Lines changed: 117 additions & 49 deletions

File tree

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
// Re-export of tree-sitter-kotlin with a permissive tree-sitter peer range.
22
// Generated by scripts/scaffold-grammar-wrappers.mjs — do not edit by hand.
33
"use strict";
4-
module.exports = require("upstream-grammar");
4+
5+
const { dirname } = require("node:path");
6+
const { createRequire } = require("node:module");
7+
8+
const upstreamManifest = require.resolve("upstream-grammar/package.json");
9+
const upstreamRequire = createRequire(upstreamManifest);
10+
module.exports = upstreamRequire("node-gyp-build")(dirname(upstreamManifest));

scripts/scaffold-grammar-wrappers.mjs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,3 +227,15 @@ function main() {
227227
}
228228

229229
main();
230+
231+
232+
// tree-sitter-kotlin@0.3.8 omits its declared bindings/node entrypoint.
233+
// Load its shipped native binary directly until the upstream package is fixed.
234+
const { writeFile: writeKotlinWrapper } = await import("node:fs/promises");
235+
await writeKotlinWrapper(
236+
new URL(
237+
"../grammar-wrappers/sdl-mcp-tree-sitter-kotlin/index.js",
238+
import.meta.url,
239+
),
240+
"// Re-export of tree-sitter-kotlin with a permissive tree-sitter peer range.\n// Generated by scripts/scaffold-grammar-wrappers.mjs — do not edit by hand.\n\"use strict\";\n\nconst { dirname } = require(\"node:path\");\nconst { createRequire } = require(\"node:module\");\n\nconst upstreamManifest = require.resolve(\"upstream-grammar/package.json\");\nconst upstreamRequire = createRequire(upstreamManifest);\nmodule.exports = upstreamRequire(\"node-gyp-build\")(dirname(upstreamManifest));\n",
241+
);

src/code-mode/action-catalog.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export const META_ACTION_SEARCH_SCHEMA = z.object({
3434
includeExamples: z.boolean().optional(),
3535
excludeDisabled: z.boolean().optional(),
3636
summaryOnly: z.boolean().optional(),
37-
detail: z.enum(["compact", "full"]).optional().default("compact"),
37+
detail: z.enum(["compact", "standard", "full"]).optional().default("compact"),
3838
maxTokens: z.number().int().min(500).max(32000).optional().default(4000),
3939
});
4040
const META_MANUAL_SCHEMA = z.object({
@@ -43,7 +43,7 @@ const META_MANUAL_SCHEMA = z.object({
4343
actions: z.array(z.string()).optional(),
4444
includeSchemas: z.boolean().optional(),
4545
includeExamples: z.boolean().optional(),
46-
detail: z.enum(["compact", "full"]).optional().default("compact"),
46+
detail: z.enum(["compact", "standard", "full"]).optional().default("compact"),
4747
});
4848

4949
const META_TOOL_SCHEMAS: Record<string, z.ZodType> = {

src/code-mode/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,12 @@ export function registerActionSearchTool(
601601
query: { type: "string", minLength: 1 },
602602
limit: { type: "integer", minimum: 1, maximum: 50 },
603603
offset: { type: "integer", minimum: 0 },
604+
maxTokens: {
605+
type: "integer",
606+
minimum: 500,
607+
maximum: 32000,
608+
default: 4000,
609+
},
604610
includeSchemas: { type: "boolean" },
605611
includeExamples: { type: "boolean" },
606612
summaryOnly: {

src/code-mode/recovery-action-catalog.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ const RECOVERY_META_ACTION_SEARCH_SCHEMA = z.object({
142142
includeExamples: z.boolean().optional(),
143143
excludeDisabled: z.boolean().optional(),
144144
summaryOnly: z.boolean().optional(),
145-
detail: z.enum(["compact", "full"]).optional().default("compact"),
145+
detail: z.enum(["compact", "standard", "full"]).optional().default("compact"),
146146
maxTokens: z.number().int().min(500).max(32000).optional().default(4000),
147147
});
148148

@@ -152,7 +152,7 @@ const RECOVERY_META_MANUAL_SCHEMA = z.object({
152152
actions: z.array(z.string()).optional(),
153153
includeSchemas: z.boolean().optional(),
154154
includeExamples: z.boolean().optional(),
155-
detail: z.enum(["compact", "full"]).optional().default("compact"),
155+
detail: z.enum(["compact", "standard", "full"]).optional().default("compact"),
156156
});
157157

158158
const RECOVERY_META_ACTION_SCHEMAS = [

src/code-mode/types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,11 @@ const WorkflowFailureStepOutputSchema = z
249249
failureTrace: WorkflowFailureTraceOutputSchema.optional(),
250250
nextAction: WorkflowCallableActionOutputSchema.optional(),
251251
result: z
252-
.union([z.string(), WorkflowErrorDetailOutputSchema])
252+
.union([
253+
z.string(),
254+
WorkflowErrorDetailOutputSchema,
255+
z.record(z.string(), z.unknown()),
256+
])
253257
.optional(),
254258
})
255259
.strict();

src/mcp/response-projection/projectors/runtime.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,9 @@ function projectRuntimeExecute(input: ModelProjectionInput): unknown {
305305
: {};
306306

307307
if (captureTruncated) {
308+
const nextAction = artifactHandle
309+
? recovery(input, artifactHandle, observability)
310+
: undefined;
308311
return {
309312
status,
310313
...(hasPreview ? { preview } : {}),
@@ -319,6 +322,8 @@ function projectRuntimeExecute(input: ModelProjectionInput): unknown {
319322
"Runtime output exceeded capture limits; discarded bytes are not recoverable.",
320323
retryable: false,
321324
},
325+
...(artifactHandle ? { artifactHandle } : {}),
326+
...(nextAction ? { nextAction } : {}),
322327
...diagnostics,
323328
};
324329
}

src/mcp/response-projection/projectors/workflow.ts

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -86,21 +86,8 @@ function projectChildValue(
8686

8787
function errorValue(step: Record<string, unknown>): unknown {
8888
if (typeof step.error === "string") return step.error;
89-
if (
90-
isRecord(step.error)
91-
&& typeof step.error.message === "string"
92-
&& typeof step.error.code === "string"
93-
&& typeof step.error.classification === "string"
94-
&& typeof step.error.retryable === "boolean"
95-
) {
96-
// Preserve only the stable generic error fields; recovery remains once-only
97-
// at the workflow-step level.
98-
return {
99-
message: step.error.message,
100-
code: step.error.code,
101-
classification: step.error.classification,
102-
retryable: step.error.retryable,
103-
};
89+
if (isRecord(step.error) && typeof step.error.message === "string") {
90+
return sanitizeWorkflowStepValue(step.error, false, false);
10491
}
10592
if (isRecord(step.result)) {
10693
if (typeof step.result.error === "string") return step.result.error;
@@ -244,7 +231,14 @@ function compactStep(
244231
}
245232
out.fn = raw.fn;
246233
if (status !== "ok") out.status = status;
247-
if (status === "ok" && "result" in raw) {
234+
if (
235+
"result" in raw
236+
&& result !== null
237+
&& (
238+
status === "ok"
239+
|| (typeof raw.fn === "string" && getWorkflowChildAction(raw.fn) === "runtime.execute")
240+
)
241+
) {
248242
const successResult = projectWorkflowSuccessResult(raw.fn, result);
249243
if (successResult !== undefined) out.result = successResult;
250244
}

tests/integration/runtime-output-contract.test.ts

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -641,17 +641,21 @@ describe("runtime output contract", () => {
641641
canonical,
642642
{ outputMode },
643643
).value as Record<string, unknown>;
644-
assert.deepEqual(Object.keys(minimal), [
645-
"status",
646-
"incompleteCapture",
647-
"handlingError",
648-
]);
644+
assert.deepEqual(Object.keys(minimal), persistOutput
645+
? [
646+
"status",
647+
"incompleteCapture",
648+
"handlingError",
649+
"artifactHandle",
650+
"nextAction",
651+
]
652+
: ["status", "incompleteCapture", "handlingError"]);
649653
assert.equal("preview" in minimal, false);
650-
assert.equal("artifactHandle" in minimal, false);
651-
assert.equal("nextAction" in minimal, false);
654+
assert.equal("artifactHandle" in minimal, persistOutput);
655+
assert.equal("nextAction" in minimal, persistOutput);
652656
assert.equal(
653657
JSON.stringify(minimal).includes("runtime.queryOutput"),
654-
false,
658+
persistOutput,
655659
);
656660
assert.deepEqual(minimal.incompleteCapture, {
657661
stdoutTruncated: true,
@@ -664,16 +668,20 @@ describe("runtime output contract", () => {
664668
canonical,
665669
{ outputMode: "summary" },
666670
).value as Record<string, unknown>;
667-
assert.deepEqual(Object.keys(projected), [
668-
"status",
669-
"preview",
670-
"incompleteCapture",
671-
"handlingError",
672-
]);
671+
assert.deepEqual(Object.keys(projected), persistOutput
672+
? [
673+
"status",
674+
"preview",
675+
"incompleteCapture",
676+
"handlingError",
677+
"artifactHandle",
678+
"nextAction",
679+
]
680+
: ["status", "preview", "incompleteCapture", "handlingError"]);
673681
assert.match(JSON.stringify(projected.preview), /x{16}/);
674682
assert.equal(
675683
JSON.stringify(projected).includes("runtime.queryOutput"),
676-
false,
684+
persistOutput,
677685
);
678686
assert.deepEqual(projected.incompleteCapture, {
679687
stdoutTruncated: true,
@@ -686,8 +694,8 @@ describe("runtime output contract", () => {
686694
"Runtime output exceeded capture limits; discarded bytes are not recoverable.",
687695
retryable: false,
688696
});
689-
assert.equal("artifactHandle" in projected, false);
690-
assert.equal("nextAction" in projected, false);
697+
assert.equal("artifactHandle" in projected, persistOutput);
698+
assert.equal("nextAction" in projected, persistOutput);
691699
assert.ok(
692700
Buffer.byteLength(JSON.stringify(projected), "utf8") <
693701
canonical.truncation.totalStdoutBytes,

tests/unit/mcp-action-search.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ describe("sdl.action.search behavior", () => {
339339
.find((entry) => entry.action === action)
340340
?.schemaSummary?.fields.find((field) => field.name === "detail");
341341
assert.ok(detail, `expected ${action} detail schema field`);
342-
assert.deepStrictEqual(detail.enumValues, ["compact", "full"]);
342+
assert.deepStrictEqual(detail.enumValues, ["compact", "standard", "full"]);
343343
assert.strictEqual(detail.default, "compact");
344344
}
345345
});

0 commit comments

Comments
 (0)