veryfront 0.1.210 → 0.1.211
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/esm/cli/mcp/advanced-tools.d.ts.map +1 -1
- package/esm/cli/mcp/advanced-tools.js +4 -0
- package/esm/cli/mcp/standalone.d.ts.map +1 -1
- package/esm/cli/mcp/standalone.js +21 -0
- package/esm/cli/mcp/tools/build-tool.d.ts +28 -0
- package/esm/cli/mcp/tools/build-tool.d.ts.map +1 -0
- package/esm/cli/mcp/tools/build-tool.js +88 -0
- package/esm/cli/mcp/tools/run-lint-tool.d.ts +14 -0
- package/esm/cli/mcp/tools/run-lint-tool.d.ts.map +1 -0
- package/esm/cli/mcp/tools/run-lint-tool.js +55 -0
- package/esm/deno.js +1 -1
- package/esm/src/agent/data-stream.d.ts.map +1 -1
- package/esm/src/agent/data-stream.js +20 -16
- package/esm/src/agent/runtime/index.d.ts +52 -1
- package/esm/src/agent/runtime/index.d.ts.map +1 -1
- package/esm/src/agent/runtime/index.js +95 -10
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +1 -1
- package/src/cli/mcp/advanced-tools.ts +4 -0
- package/src/cli/mcp/standalone.ts +23 -0
- package/src/cli/mcp/tools/build-tool.ts +115 -0
- package/src/cli/mcp/tools/run-lint-tool.ts +69 -0
- package/src/deno.js +1 -1
- package/src/src/agent/data-stream.ts +21 -17
- package/src/src/agent/runtime/index.ts +140 -10
- package/src/src/utils/version-constant.ts +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"advanced-tools.d.ts","sourceRoot":"","sources":["../../../src/cli/mcp/advanced-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"advanced-tools.d.ts","sourceRoot":"","sources":["../../../src/cli/mcp/advanced-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AA+B1C,eAAO,MAAM,aAAa,EAAE,OAAO,EAyBlC,CAAC"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { vfCreateProject, vfListExamples, vfListIntegrations, vfListTemplates, vfListUsecases, } from "./tools/catalog-tools.js";
|
|
2
2
|
import { vfGetDebugContext, vfGetFlywheelStatus, vfHotReload, vfPreviewRoute, vfTriggerHmr, vfWaitForReady, } from "./tools/dev-tools.js";
|
|
3
3
|
import { vfGetComponentTree, vfGetProjectContext, vfListLocalProjects, vfListRoutes, } from "./tools/project-tools.js";
|
|
4
|
+
import { vfBuild } from "./tools/build-tool.js";
|
|
5
|
+
import { vfRunLint } from "./tools/run-lint-tool.js";
|
|
4
6
|
import { vfRunTests } from "./tools/run-tests-tool.js";
|
|
5
7
|
import { vfGetConventions, vfScaffold } from "./tools/scaffold-tools.js";
|
|
6
8
|
import { vfGetSkillReference, vfGetSkills } from "./tools/skill-tools.js";
|
|
@@ -24,9 +26,11 @@ export const advancedTools = [
|
|
|
24
26
|
vfPreviewRoute,
|
|
25
27
|
vfGetDebugContext,
|
|
26
28
|
vfGetComponentTree,
|
|
29
|
+
vfBuild,
|
|
27
30
|
vfHotReload,
|
|
28
31
|
vfTriggerHmr,
|
|
29
32
|
vfWaitForReady,
|
|
33
|
+
vfRunLint,
|
|
30
34
|
vfGetFlywheelStatus,
|
|
31
35
|
vfRunTests,
|
|
32
36
|
];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"standalone.d.ts","sourceRoot":"","sources":["../../../src/cli/mcp/standalone.ts"],"names":[],"mappings":"AAsCA,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,WAAW,CAA4B;gBAEnC,MAAM,GAAE,mBAAwB;IAM5C,KAAK,IAAI,IAAI;IAWb,IAAI,IAAI,IAAI;YAME,aAAa;IAW3B,OAAO,CAAC,cAAc;YAgCR,eAAe;IAqB7B,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,mBAAmB;YAyBb,mBAAmB;IA6CjC,OAAO,CAAC,iBAAiB;YAkBX,gBAAgB;IAwB9B,OAAO,CAAC,WAAW;
|
|
1
|
+
{"version":3,"file":"standalone.d.ts","sourceRoot":"","sources":["../../../src/cli/mcp/standalone.ts"],"names":[],"mappings":"AAsCA,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,WAAW,CAA4B;gBAEnC,MAAM,GAAE,mBAAwB;IAM5C,KAAK,IAAI,IAAI;IAWb,IAAI,IAAI,IAAI;YAME,aAAa;IAW3B,OAAO,CAAC,cAAc;YAgCR,eAAe;IAqB7B,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,mBAAmB;YAyBb,mBAAmB;IA6CjC,OAAO,CAAC,iBAAiB;YAkBX,gBAAgB;IAwB9B,OAAO,CAAC,WAAW;IAwNnB,OAAO,CAAC,mBAAmB;CA0F5B;AAED,wBAAgB,yBAAyB,CAAC,MAAM,GAAE,mBAAwB,GAAG,mBAAmB,CAI/F"}
|
|
@@ -382,6 +382,27 @@ export class StandaloneMCPServer {
|
|
|
382
382
|
});
|
|
383
383
|
},
|
|
384
384
|
},
|
|
385
|
+
{
|
|
386
|
+
name: "vf_run_lint",
|
|
387
|
+
description: "Run the linter. Returns structured diagnostics with file, line, column, rule code, and message. " +
|
|
388
|
+
"Do not use for test results — use vf_run_tests instead. " +
|
|
389
|
+
"Do not use for compile/runtime errors — use vf_get_errors instead.",
|
|
390
|
+
inputSchema: {
|
|
391
|
+
type: "object",
|
|
392
|
+
properties: {
|
|
393
|
+
timeout: {
|
|
394
|
+
type: "number",
|
|
395
|
+
description: "Maximum time to wait for lint completion in milliseconds (default: 120000)",
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
async execute(args) {
|
|
400
|
+
const { executeLint } = await import("./tools/run-lint-tool.js");
|
|
401
|
+
return executeLint({
|
|
402
|
+
timeout: args.timeout,
|
|
403
|
+
});
|
|
404
|
+
},
|
|
405
|
+
},
|
|
385
406
|
...this.createContext7Tools(),
|
|
386
407
|
];
|
|
387
408
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tool for production builds.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import type { MCPTool } from "../tools.js";
|
|
6
|
+
declare const buildInput: z.ZodObject<{
|
|
7
|
+
outputDir: z.ZodOptional<z.ZodString>;
|
|
8
|
+
splitting: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
9
|
+
compress: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
10
|
+
ssg: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
11
|
+
dryRun: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
12
|
+
}, z.core.$strip>;
|
|
13
|
+
type BuildInput = z.infer<typeof buildInput>;
|
|
14
|
+
interface BuildResult {
|
|
15
|
+
success: boolean;
|
|
16
|
+
pages?: number;
|
|
17
|
+
chunks?: number;
|
|
18
|
+
assets?: number;
|
|
19
|
+
totalSize?: number;
|
|
20
|
+
duration_ms?: number;
|
|
21
|
+
outputDir?: string;
|
|
22
|
+
dryRun?: boolean;
|
|
23
|
+
ssgPaths?: string[];
|
|
24
|
+
error?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare const vfBuild: MCPTool<BuildInput, BuildResult>;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=build-tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-tool.d.ts","sourceRoot":"","sources":["../../../../src/cli/mcp/tools/build-tool.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAM3C,QAAA,MAAM,UAAU;;;;;;iBAyBd,CAAC;AAEH,KAAK,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAE7C,UAAU,WAAW;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,OAAO,EAAE,OAAO,CAAC,UAAU,EAAE,WAAW,CAyDpD,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tool for production builds.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { cwd } from "../../../src/platform/index.js";
|
|
6
|
+
import { join } from "../../../src/platform/compat/path/index.js";
|
|
7
|
+
import { buildProduction } from "../../../src/build/index.js";
|
|
8
|
+
import { withSpan } from "../../../src/observability/tracing/otlp-setup.js";
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Tool: vf_build
|
|
11
|
+
// ============================================================================
|
|
12
|
+
const buildInput = z.object({
|
|
13
|
+
outputDir: z
|
|
14
|
+
.string()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("Output directory for the build. Defaults to '<projectDir>/dist'."),
|
|
17
|
+
splitting: z
|
|
18
|
+
.boolean()
|
|
19
|
+
.optional()
|
|
20
|
+
.default(true)
|
|
21
|
+
.describe("Enable code splitting. Defaults to true."),
|
|
22
|
+
compress: z
|
|
23
|
+
.boolean()
|
|
24
|
+
.optional()
|
|
25
|
+
.default(true)
|
|
26
|
+
.describe("Enable compression (gzip/brotli). Defaults to true."),
|
|
27
|
+
ssg: z
|
|
28
|
+
.boolean()
|
|
29
|
+
.optional()
|
|
30
|
+
.default(true)
|
|
31
|
+
.describe("Enable static site generation. Defaults to true."),
|
|
32
|
+
dryRun: z
|
|
33
|
+
.boolean()
|
|
34
|
+
.optional()
|
|
35
|
+
.default(false)
|
|
36
|
+
.describe("Preview the build without writing files to disk. Defaults to false."),
|
|
37
|
+
});
|
|
38
|
+
export const vfBuild = {
|
|
39
|
+
name: "vf_build",
|
|
40
|
+
title: "Production Build",
|
|
41
|
+
annotations: {
|
|
42
|
+
readOnlyHint: false,
|
|
43
|
+
destructiveHint: false,
|
|
44
|
+
idempotentHint: true,
|
|
45
|
+
openWorldHint: false,
|
|
46
|
+
},
|
|
47
|
+
description: "Use this when you need to run a production build for the current project. " +
|
|
48
|
+
"Bundles, optimises, and writes output to the dist directory. " +
|
|
49
|
+
"Use dryRun=true to preview the build without writing files. " +
|
|
50
|
+
"Do not use for development — use the dev server tools instead. " +
|
|
51
|
+
"Do not use for lint checks — use vf_run_lint instead.",
|
|
52
|
+
inputSchema: buildInput,
|
|
53
|
+
execute: (input) => withSpan("cli.mcp.tool.vf_build", async () => {
|
|
54
|
+
const projectDir = cwd();
|
|
55
|
+
const outputDir = input.outputDir ?? join(projectDir, "dist");
|
|
56
|
+
const startTime = Date.now();
|
|
57
|
+
try {
|
|
58
|
+
const stats = await buildProduction({
|
|
59
|
+
projectDir,
|
|
60
|
+
outputDir,
|
|
61
|
+
enableSplitting: input.splitting,
|
|
62
|
+
enableCompression: input.compress,
|
|
63
|
+
ssg: input.ssg,
|
|
64
|
+
dryRun: input.dryRun,
|
|
65
|
+
});
|
|
66
|
+
const duration_ms = Date.now() - startTime;
|
|
67
|
+
return {
|
|
68
|
+
success: true,
|
|
69
|
+
pages: stats.pages,
|
|
70
|
+
chunks: stats.chunks,
|
|
71
|
+
assets: stats.assets,
|
|
72
|
+
totalSize: stats.totalSize,
|
|
73
|
+
duration_ms,
|
|
74
|
+
outputDir,
|
|
75
|
+
dryRun: input.dryRun,
|
|
76
|
+
ssgPaths: stats.ssgPaths,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
const duration_ms = Date.now() - startTime;
|
|
81
|
+
return {
|
|
82
|
+
success: false,
|
|
83
|
+
error: error instanceof Error ? error.message : String(error),
|
|
84
|
+
duration_ms,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}, { "tool.dryRun": input.dryRun }),
|
|
88
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { MCPTool } from "../tools.js";
|
|
3
|
+
import { type LintResult } from "../../commands/lint/command.js";
|
|
4
|
+
declare const runLintInput: z.ZodObject<{
|
|
5
|
+
timeout: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
6
|
+
}, z.core.$strip>;
|
|
7
|
+
type RunLintInput = z.infer<typeof runLintInput>;
|
|
8
|
+
/** Spawn deno lint and return structured results. Exported for standalone reuse. */
|
|
9
|
+
export declare function executeLint(input?: {
|
|
10
|
+
timeout?: number;
|
|
11
|
+
}): Promise<LintResult>;
|
|
12
|
+
export declare const vfRunLint: MCPTool<RunLintInput, LintResult>;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=run-lint-tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-lint-tool.d.ts","sourceRoot":"","sources":["../../../../src/cli/mcp/tools/run-lint-tool.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,KAAK,UAAU,EAAuB,MAAM,gCAAgC,CAAC;AAEtF,QAAA,MAAM,YAAY;;iBAIhB,CAAC;AAEH,KAAK,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAEjD,oFAAoF;AACpF,wBAAsB,WAAW,CAC/B,KAAK,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,UAAU,CAAC,CA2BrB;AAED,eAAO,MAAM,SAAS,EAAE,OAAO,CAAC,YAAY,EAAE,UAAU,CAevD,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tool: vf_run_lint
|
|
3
|
+
*
|
|
4
|
+
* Runs the linter via subprocess and returns structured diagnostics.
|
|
5
|
+
* Reuses parseLintJsonOutput from the CLI lint command.
|
|
6
|
+
*/
|
|
7
|
+
import * as dntShim from "../../../_dnt.shims.js";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { parseLintJsonOutput } from "../../commands/lint/command.js";
|
|
10
|
+
const runLintInput = z.object({
|
|
11
|
+
timeout: z.number().optional().default(120000).describe("Maximum time to wait for lint completion in milliseconds. Defaults to 120000 (2 minutes)."),
|
|
12
|
+
});
|
|
13
|
+
/** Spawn deno lint and return structured results. Exported for standalone reuse. */
|
|
14
|
+
export async function executeLint(input = {}) {
|
|
15
|
+
const cmd = new dntShim.Deno.Command("deno", {
|
|
16
|
+
args: ["lint", "--json"],
|
|
17
|
+
stdout: "piped",
|
|
18
|
+
stderr: "piped",
|
|
19
|
+
});
|
|
20
|
+
const child = cmd.spawn();
|
|
21
|
+
const timeoutMs = input.timeout ?? 120000;
|
|
22
|
+
const result = await Promise.race([
|
|
23
|
+
child.output(),
|
|
24
|
+
new Promise((_, reject) => {
|
|
25
|
+
const timer = dntShim.setTimeout(() => {
|
|
26
|
+
try {
|
|
27
|
+
child.kill();
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Process may have already exited
|
|
31
|
+
}
|
|
32
|
+
reject(new Error(`Lint execution timed out after ${timeoutMs}ms`));
|
|
33
|
+
}, timeoutMs);
|
|
34
|
+
dntShim.Deno.unrefTimer(timer);
|
|
35
|
+
}),
|
|
36
|
+
]);
|
|
37
|
+
const stdout = new TextDecoder().decode(result.stdout);
|
|
38
|
+
return parseLintJsonOutput(stdout, result.code);
|
|
39
|
+
}
|
|
40
|
+
export const vfRunLint = {
|
|
41
|
+
name: "vf_run_lint",
|
|
42
|
+
title: "Run Lint",
|
|
43
|
+
annotations: {
|
|
44
|
+
readOnlyHint: true,
|
|
45
|
+
destructiveHint: false,
|
|
46
|
+
idempotentHint: true,
|
|
47
|
+
openWorldHint: false,
|
|
48
|
+
},
|
|
49
|
+
description: "Use this when you need to check for lint issues in the project. " +
|
|
50
|
+
"Returns structured diagnostics with file path, line, column, rule code, and message for each issue. " +
|
|
51
|
+
"Do not use for test results — use vf_run_tests instead. " +
|
|
52
|
+
"Do not use for compile/runtime errors — use vf_get_errors instead.",
|
|
53
|
+
inputSchema: runLintInput,
|
|
54
|
+
execute: (input) => executeLint(input),
|
|
55
|
+
};
|
package/esm/deno.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-stream.d.ts","sourceRoot":"","sources":["../../../src/src/agent/data-stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAMzE,wBAAgB,kCAAkC,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAmB1E;AAED,wBAAgB,mBAAmB,CAAC,gBAAgB,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"data-stream.d.ts","sourceRoot":"","sources":["../../../src/src/agent/data-stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAMzE,wBAAgB,kCAAkC,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAmB1E;AAED,wBAAgB,mBAAmB,CAAC,gBAAgB,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAwCvF;AAED,wBAAgB,kBAAkB,CAAC,gBAAgB,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CActF;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAiB5E;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG;IACvD,MAAM,EAAE,sBAAsB,EAAE,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;CACnB,CAoBA;AAED,wBAAuB,sBAAsB,CAC3C,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,GACjC,cAAc,CAAC,sBAAsB,CAAC,CAkCxC"}
|
|
@@ -18,13 +18,15 @@ export function stripLeadingEmptyObjectPlaceholder(rawArgs) {
|
|
|
18
18
|
return normalized;
|
|
19
19
|
}
|
|
20
20
|
export function mergeToolInputDelta(currentArguments, nextDelta) {
|
|
21
|
+
const normalizedDelta = nextDelta.trimStart();
|
|
22
|
+
const candidateDeltas = normalizedDelta.startsWith('"')
|
|
23
|
+
? [normalizedDelta, `{${normalizedDelta}`]
|
|
24
|
+
: [normalizedDelta];
|
|
21
25
|
if (currentArguments === "{}") {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (normalizedDelta.startsWith('"')) {
|
|
27
|
-
return `{${normalizedDelta}`;
|
|
26
|
+
for (const candidate of candidateDeltas) {
|
|
27
|
+
if (candidate.startsWith("{")) {
|
|
28
|
+
return candidate;
|
|
29
|
+
}
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
32
|
if (nextDelta.length === 0) {
|
|
@@ -33,16 +35,18 @@ export function mergeToolInputDelta(currentArguments, nextDelta) {
|
|
|
33
35
|
if (currentArguments.length === 0) {
|
|
34
36
|
return nextDelta;
|
|
35
37
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
for (const candidate of candidateDeltas) {
|
|
39
|
+
if (candidate === currentArguments || currentArguments.includes(candidate)) {
|
|
40
|
+
return currentArguments;
|
|
41
|
+
}
|
|
42
|
+
if (candidate.startsWith(currentArguments)) {
|
|
43
|
+
return candidate;
|
|
44
|
+
}
|
|
45
|
+
const maxOverlap = Math.min(currentArguments.length, candidate.length);
|
|
46
|
+
for (let overlap = maxOverlap; overlap > 0; overlap--) {
|
|
47
|
+
if (currentArguments.endsWith(candidate.slice(0, overlap))) {
|
|
48
|
+
return currentArguments + candidate.slice(overlap);
|
|
49
|
+
}
|
|
46
50
|
}
|
|
47
51
|
}
|
|
48
52
|
return currentArguments + nextDelta;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @module ai/agent/runtime
|
|
12
12
|
*/
|
|
13
|
-
import { type AgentConfig, type AgentResponse, type Message, type ToolCall, type ToolResultPart } from "../types.js";
|
|
13
|
+
import { type AgentConfig, type AgentResponse, type Message, type MessagePart, type ToolCall, type ToolResultPart } from "../types.js";
|
|
14
14
|
import { type Memory } from "../memory/index.js";
|
|
15
15
|
import { type ChatStreamState, type StreamingToolCall, type StreamingToolResult } from "./chat-stream-handler.js";
|
|
16
16
|
export { closeSSEStream, generateMessageId, sendSSE } from "./sse-utils.js";
|
|
@@ -31,6 +31,57 @@ export declare function captureStreamedToolCallInput(toolCall: Pick<StreamingToo
|
|
|
31
31
|
inputText?: string;
|
|
32
32
|
parseError?: string;
|
|
33
33
|
};
|
|
34
|
+
/**
|
|
35
|
+
* A streamed tool call is "incomplete" when the provider stream terminated
|
|
36
|
+
* (abort, stall, timeout, transport error) before the SDK emitted the
|
|
37
|
+
* finalizing `tool-call` event that sets `inputAvailable: true`. In that state
|
|
38
|
+
* `arguments` only holds partial JSON fragments from `tool-input-delta` events,
|
|
39
|
+
* so the tool call is NOT a committed model choice and must not be parsed or
|
|
40
|
+
* executed. This is semantically distinct from a parse failure on a finalized
|
|
41
|
+
* tool call (`inputAvailable: true` but malformed JSON — which only happens on
|
|
42
|
+
* genuine provider bugs) and needs to be reported as a stream-termination
|
|
43
|
+
* error rather than a tool-argument error.
|
|
44
|
+
*/
|
|
45
|
+
export declare function isStreamedToolCallIncomplete(toolCall: Pick<StreamingToolCall, "inputAvailable">): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Classification of a streamed tool call when we reach end-of-stream and need
|
|
48
|
+
* to persist it into the assistant message. Three distinct cases, each with
|
|
49
|
+
* different semantics downstream:
|
|
50
|
+
*
|
|
51
|
+
* - `complete`: provider emitted the finalizing `tool-call` event and the
|
|
52
|
+
* arguments parsed cleanly. Execute the tool normally.
|
|
53
|
+
* - `parse-error`: provider emitted the finalizing `tool-call` event but the
|
|
54
|
+
* arguments are not valid JSON. This is a provider/SDK bug; record it as a
|
|
55
|
+
* tool-argument error so the step can recover.
|
|
56
|
+
* - `incomplete`: stream terminated before the finalizing event fired. The
|
|
57
|
+
* model never committed this tool use; record it as a stream-termination
|
|
58
|
+
* error so the parent (e.g. child-fork watchdog) can decide whether to
|
|
59
|
+
* retry the step cleanly instead of seeing a malformed tool call.
|
|
60
|
+
*/
|
|
61
|
+
export type StreamedToolCallMaterialization = {
|
|
62
|
+
readonly kind: "complete";
|
|
63
|
+
readonly part: MessagePart;
|
|
64
|
+
} | {
|
|
65
|
+
readonly kind: "parse-error";
|
|
66
|
+
readonly part: MessagePart;
|
|
67
|
+
readonly parseError: string;
|
|
68
|
+
} | {
|
|
69
|
+
readonly kind: "incomplete";
|
|
70
|
+
readonly part: MessagePart;
|
|
71
|
+
readonly partialArgumentsLength: number;
|
|
72
|
+
readonly partialArgumentsPreview: string;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Classify and build the persisted `MessagePart` for a single streamed tool
|
|
76
|
+
* call. Pure function — no logging, no SSE, no memory. Callers decide what to
|
|
77
|
+
* do with the result so this stays unit-testable.
|
|
78
|
+
*
|
|
79
|
+
* The resulting `part` is always pushed into the assistant message so the
|
|
80
|
+
* conversation history is transparent: even incomplete tool calls leave a
|
|
81
|
+
* visible trace with their partial `inputText`. What differs is the caller's
|
|
82
|
+
* error-surfacing behavior (log warning, SSE event, tool-result error).
|
|
83
|
+
*/
|
|
84
|
+
export declare function materializeStreamedToolCall(tc: StreamingToolCall): StreamedToolCallMaterialization;
|
|
34
85
|
/**
|
|
35
86
|
* Extract and validate the skill policy from a load-skill tool result.
|
|
36
87
|
* Returns `[]` (no tools allowed) for invalid/missing policies instead of
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/src/agent/runtime/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,KAAK,WAAW,EAEhB,KAAK,aAAa,EAGlB,KAAK,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/src/agent/runtime/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -99,6 +99,59 @@ export function captureStreamedToolCallInput(toolCall) {
|
|
|
99
99
|
...(error ? { parseError: error } : {}),
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* A streamed tool call is "incomplete" when the provider stream terminated
|
|
104
|
+
* (abort, stall, timeout, transport error) before the SDK emitted the
|
|
105
|
+
* finalizing `tool-call` event that sets `inputAvailable: true`. In that state
|
|
106
|
+
* `arguments` only holds partial JSON fragments from `tool-input-delta` events,
|
|
107
|
+
* so the tool call is NOT a committed model choice and must not be parsed or
|
|
108
|
+
* executed. This is semantically distinct from a parse failure on a finalized
|
|
109
|
+
* tool call (`inputAvailable: true` but malformed JSON — which only happens on
|
|
110
|
+
* genuine provider bugs) and needs to be reported as a stream-termination
|
|
111
|
+
* error rather than a tool-argument error.
|
|
112
|
+
*/
|
|
113
|
+
export function isStreamedToolCallIncomplete(toolCall) {
|
|
114
|
+
return toolCall.inputAvailable !== true;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Classify and build the persisted `MessagePart` for a single streamed tool
|
|
118
|
+
* call. Pure function — no logging, no SSE, no memory. Callers decide what to
|
|
119
|
+
* do with the result so this stays unit-testable.
|
|
120
|
+
*
|
|
121
|
+
* The resulting `part` is always pushed into the assistant message so the
|
|
122
|
+
* conversation history is transparent: even incomplete tool calls leave a
|
|
123
|
+
* visible trace with their partial `inputText`. What differs is the caller's
|
|
124
|
+
* error-surfacing behavior (log warning, SSE event, tool-result error).
|
|
125
|
+
*/
|
|
126
|
+
export function materializeStreamedToolCall(tc) {
|
|
127
|
+
const basePart = {
|
|
128
|
+
type: `tool-${tc.name}`,
|
|
129
|
+
toolCallId: tc.id,
|
|
130
|
+
toolName: tc.name,
|
|
131
|
+
args: {},
|
|
132
|
+
...(tc.arguments.length > 0 ? { inputText: tc.arguments } : {}),
|
|
133
|
+
};
|
|
134
|
+
if (isStreamedToolCallIncomplete(tc)) {
|
|
135
|
+
return {
|
|
136
|
+
kind: "incomplete",
|
|
137
|
+
part: basePart,
|
|
138
|
+
partialArgumentsLength: tc.arguments.length,
|
|
139
|
+
partialArgumentsPreview: tc.arguments.slice(0, 200),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
const capturedInput = captureStreamedToolCallInput(tc);
|
|
143
|
+
const part = {
|
|
144
|
+
type: `tool-${tc.name}`,
|
|
145
|
+
toolCallId: tc.id,
|
|
146
|
+
toolName: tc.name,
|
|
147
|
+
args: capturedInput.args,
|
|
148
|
+
...(capturedInput.inputText ? { inputText: capturedInput.inputText } : {}),
|
|
149
|
+
};
|
|
150
|
+
if (capturedInput.parseError) {
|
|
151
|
+
return { kind: "parse-error", part, parseError: capturedInput.parseError };
|
|
152
|
+
}
|
|
153
|
+
return { kind: "complete", part };
|
|
154
|
+
}
|
|
102
155
|
function isToolResultPart(part) {
|
|
103
156
|
return part.type === "tool-result" && "result" in part;
|
|
104
157
|
}
|
|
@@ -649,20 +702,35 @@ export class AgentRuntime {
|
|
|
649
702
|
if (state.accumulatedText)
|
|
650
703
|
streamParts.push({ type: "text", text: state.accumulatedText });
|
|
651
704
|
for (const tc of state.toolCalls.values()) {
|
|
652
|
-
const
|
|
653
|
-
|
|
705
|
+
const materialized = materializeStreamedToolCall(tc);
|
|
706
|
+
streamParts.push(materialized.part);
|
|
707
|
+
if (materialized.kind === "incomplete") {
|
|
708
|
+
// Stream terminated before the provider emitted the finalizing
|
|
709
|
+
// `tool-call` event for this block. The model never committed this
|
|
710
|
+
// tool use. Surface the failure via SSE so the live client can
|
|
711
|
+
// react, and leave the partial fragment under `inputText` in the
|
|
712
|
+
// persisted part above so the history is replayable and transparent.
|
|
713
|
+
logger.warn("Streamed tool call terminated before tool-call event", {
|
|
714
|
+
toolCallId: tc.id,
|
|
715
|
+
toolName: tc.name,
|
|
716
|
+
partialArgumentsLength: materialized.partialArgumentsLength,
|
|
717
|
+
partialArgumentsPreview: materialized.partialArgumentsPreview,
|
|
718
|
+
});
|
|
719
|
+
const dynamicIncomplete = isDynamicTool(tc.name);
|
|
720
|
+
sendSSE(controller, encoder, {
|
|
721
|
+
type: "tool-input-error",
|
|
722
|
+
toolCallId: tc.id,
|
|
723
|
+
errorText: `Stream terminated before tool-call event fired for "${tc.name}". ` +
|
|
724
|
+
`Received ${materialized.partialArgumentsLength} chars of partial tool-input deltas.`,
|
|
725
|
+
...(dynamicIncomplete ? { dynamic: true } : {}),
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
else if (materialized.kind === "parse-error") {
|
|
654
729
|
logger.warn("Failed to parse streamed tool arguments", {
|
|
655
730
|
toolCallId: tc.id,
|
|
656
|
-
error:
|
|
731
|
+
error: materialized.parseError,
|
|
657
732
|
});
|
|
658
733
|
}
|
|
659
|
-
streamParts.push({
|
|
660
|
-
type: `tool-${tc.name}`,
|
|
661
|
-
toolCallId: tc.id,
|
|
662
|
-
toolName: tc.name,
|
|
663
|
-
args: capturedInput.args,
|
|
664
|
-
...(capturedInput.inputText ? { inputText: capturedInput.inputText } : {}),
|
|
665
|
-
});
|
|
666
734
|
}
|
|
667
735
|
const assistantMessage = {
|
|
668
736
|
id: `msg_${Date.now()}_${step}`,
|
|
@@ -711,6 +779,23 @@ export class AgentRuntime {
|
|
|
711
779
|
streamedToolCalls.some((tc) => tc.name === LOAD_SKILL_TOOL_ID);
|
|
712
780
|
for (const tc of streamedToolCalls) {
|
|
713
781
|
throwIfAborted(abortSignal);
|
|
782
|
+
if (isStreamedToolCallIncomplete(tc)) {
|
|
783
|
+
// Stream ended before the provider finalized this tool call. We
|
|
784
|
+
// cannot execute it — record a distinct stream-termination error
|
|
785
|
+
// (not a tool-argument parse error) so the parent step and any
|
|
786
|
+
// upstream orchestrator (e.g. the child-fork watchdog) see a
|
|
787
|
+
// completed step with a clearly-labelled failure and can recover.
|
|
788
|
+
const incompleteToolCall = {
|
|
789
|
+
id: tc.id,
|
|
790
|
+
name: tc.name,
|
|
791
|
+
args: {},
|
|
792
|
+
...(tc.arguments.length > 0 ? { inputText: tc.arguments } : {}),
|
|
793
|
+
status: "pending",
|
|
794
|
+
};
|
|
795
|
+
await this.recordToolError(incompleteToolCall, `Stream terminated before tool-call event fired for "${tc.name}". ` +
|
|
796
|
+
`Received ${tc.arguments.length} chars of partial tool-input deltas.`, controller, encoder, currentMessages, toolCalls);
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
714
799
|
const capturedInput = captureStreamedToolCallInput(tc);
|
|
715
800
|
const toolCall = {
|
|
716
801
|
id: tc.id,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.1.
|
|
1
|
+
export declare const VERSION = "0.1.211";
|
|
2
2
|
//# sourceMappingURL=version-constant.d.ts.map
|
package/package.json
CHANGED
|
@@ -21,6 +21,8 @@ import {
|
|
|
21
21
|
vfListLocalProjects,
|
|
22
22
|
vfListRoutes,
|
|
23
23
|
} from "./tools/project-tools.js";
|
|
24
|
+
import { vfBuild } from "./tools/build-tool.js";
|
|
25
|
+
import { vfRunLint } from "./tools/run-lint-tool.js";
|
|
24
26
|
import { vfRunTests } from "./tools/run-tests-tool.js";
|
|
25
27
|
import { vfGetConventions, vfScaffold } from "./tools/scaffold-tools.js";
|
|
26
28
|
import { vfGetSkillReference, vfGetSkills } from "./tools/skill-tools.js";
|
|
@@ -45,9 +47,11 @@ export const advancedTools: MCPTool[] = [
|
|
|
45
47
|
vfPreviewRoute,
|
|
46
48
|
vfGetDebugContext,
|
|
47
49
|
vfGetComponentTree,
|
|
50
|
+
vfBuild,
|
|
48
51
|
vfHotReload,
|
|
49
52
|
vfTriggerHmr,
|
|
50
53
|
vfWaitForReady,
|
|
54
|
+
vfRunLint,
|
|
51
55
|
vfGetFlywheelStatus,
|
|
52
56
|
vfRunTests,
|
|
53
57
|
];
|
|
@@ -444,6 +444,29 @@ export class StandaloneMCPServer {
|
|
|
444
444
|
});
|
|
445
445
|
},
|
|
446
446
|
},
|
|
447
|
+
{
|
|
448
|
+
name: "vf_run_lint",
|
|
449
|
+
description:
|
|
450
|
+
"Run the linter. Returns structured diagnostics with file, line, column, rule code, and message. " +
|
|
451
|
+
"Do not use for test results — use vf_run_tests instead. " +
|
|
452
|
+
"Do not use for compile/runtime errors — use vf_get_errors instead.",
|
|
453
|
+
inputSchema: {
|
|
454
|
+
type: "object",
|
|
455
|
+
properties: {
|
|
456
|
+
timeout: {
|
|
457
|
+
type: "number",
|
|
458
|
+
description:
|
|
459
|
+
"Maximum time to wait for lint completion in milliseconds (default: 120000)",
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
async execute(args) {
|
|
464
|
+
const { executeLint } = await import("./tools/run-lint-tool.js");
|
|
465
|
+
return executeLint({
|
|
466
|
+
timeout: args.timeout as number | undefined,
|
|
467
|
+
});
|
|
468
|
+
},
|
|
469
|
+
},
|
|
447
470
|
...this.createContext7Tools(),
|
|
448
471
|
];
|
|
449
472
|
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tool for production builds.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { cwd } from "../../../src/platform/index.js";
|
|
7
|
+
import { join } from "../../../src/platform/compat/path/index.js";
|
|
8
|
+
import { buildProduction } from "../../../src/build/index.js";
|
|
9
|
+
import { withSpan } from "../../../src/observability/tracing/otlp-setup.js";
|
|
10
|
+
import type { MCPTool } from "../tools.js";
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Tool: vf_build
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
const buildInput = z.object({
|
|
17
|
+
outputDir: z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Output directory for the build. Defaults to '<projectDir>/dist'."),
|
|
21
|
+
splitting: z
|
|
22
|
+
.boolean()
|
|
23
|
+
.optional()
|
|
24
|
+
.default(true)
|
|
25
|
+
.describe("Enable code splitting. Defaults to true."),
|
|
26
|
+
compress: z
|
|
27
|
+
.boolean()
|
|
28
|
+
.optional()
|
|
29
|
+
.default(true)
|
|
30
|
+
.describe("Enable compression (gzip/brotli). Defaults to true."),
|
|
31
|
+
ssg: z
|
|
32
|
+
.boolean()
|
|
33
|
+
.optional()
|
|
34
|
+
.default(true)
|
|
35
|
+
.describe("Enable static site generation. Defaults to true."),
|
|
36
|
+
dryRun: z
|
|
37
|
+
.boolean()
|
|
38
|
+
.optional()
|
|
39
|
+
.default(false)
|
|
40
|
+
.describe("Preview the build without writing files to disk. Defaults to false."),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
type BuildInput = z.infer<typeof buildInput>;
|
|
44
|
+
|
|
45
|
+
interface BuildResult {
|
|
46
|
+
success: boolean;
|
|
47
|
+
pages?: number;
|
|
48
|
+
chunks?: number;
|
|
49
|
+
assets?: number;
|
|
50
|
+
totalSize?: number;
|
|
51
|
+
duration_ms?: number;
|
|
52
|
+
outputDir?: string;
|
|
53
|
+
dryRun?: boolean;
|
|
54
|
+
ssgPaths?: string[];
|
|
55
|
+
error?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const vfBuild: MCPTool<BuildInput, BuildResult> = {
|
|
59
|
+
name: "vf_build",
|
|
60
|
+
title: "Production Build",
|
|
61
|
+
annotations: {
|
|
62
|
+
readOnlyHint: false,
|
|
63
|
+
destructiveHint: false,
|
|
64
|
+
idempotentHint: true,
|
|
65
|
+
openWorldHint: false,
|
|
66
|
+
},
|
|
67
|
+
description: "Use this when you need to run a production build for the current project. " +
|
|
68
|
+
"Bundles, optimises, and writes output to the dist directory. " +
|
|
69
|
+
"Use dryRun=true to preview the build without writing files. " +
|
|
70
|
+
"Do not use for development — use the dev server tools instead. " +
|
|
71
|
+
"Do not use for lint checks — use vf_run_lint instead.",
|
|
72
|
+
inputSchema: buildInput,
|
|
73
|
+
execute: (input) =>
|
|
74
|
+
withSpan(
|
|
75
|
+
"cli.mcp.tool.vf_build",
|
|
76
|
+
async () => {
|
|
77
|
+
const projectDir = cwd();
|
|
78
|
+
const outputDir = input.outputDir ?? join(projectDir, "dist");
|
|
79
|
+
const startTime = Date.now();
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const stats = await buildProduction({
|
|
83
|
+
projectDir,
|
|
84
|
+
outputDir,
|
|
85
|
+
enableSplitting: input.splitting,
|
|
86
|
+
enableCompression: input.compress,
|
|
87
|
+
ssg: input.ssg,
|
|
88
|
+
dryRun: input.dryRun,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const duration_ms = Date.now() - startTime;
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
success: true,
|
|
95
|
+
pages: stats.pages,
|
|
96
|
+
chunks: stats.chunks,
|
|
97
|
+
assets: stats.assets,
|
|
98
|
+
totalSize: stats.totalSize,
|
|
99
|
+
duration_ms,
|
|
100
|
+
outputDir,
|
|
101
|
+
dryRun: input.dryRun,
|
|
102
|
+
ssgPaths: stats.ssgPaths,
|
|
103
|
+
};
|
|
104
|
+
} catch (error) {
|
|
105
|
+
const duration_ms = Date.now() - startTime;
|
|
106
|
+
return {
|
|
107
|
+
success: false,
|
|
108
|
+
error: error instanceof Error ? error.message : String(error),
|
|
109
|
+
duration_ms,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
{ "tool.dryRun": input.dryRun },
|
|
114
|
+
),
|
|
115
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tool: vf_run_lint
|
|
3
|
+
*
|
|
4
|
+
* Runs the linter via subprocess and returns structured diagnostics.
|
|
5
|
+
* Reuses parseLintJsonOutput from the CLI lint command.
|
|
6
|
+
*/
|
|
7
|
+
import * as dntShim from "../../../_dnt.shims.js";
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
import type { MCPTool } from "../tools.js";
|
|
12
|
+
import { type LintResult, parseLintJsonOutput } from "../../commands/lint/command.js";
|
|
13
|
+
|
|
14
|
+
const runLintInput = z.object({
|
|
15
|
+
timeout: z.number().optional().default(120000).describe(
|
|
16
|
+
"Maximum time to wait for lint completion in milliseconds. Defaults to 120000 (2 minutes).",
|
|
17
|
+
),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
type RunLintInput = z.infer<typeof runLintInput>;
|
|
21
|
+
|
|
22
|
+
/** Spawn deno lint and return structured results. Exported for standalone reuse. */
|
|
23
|
+
export async function executeLint(
|
|
24
|
+
input: { timeout?: number } = {},
|
|
25
|
+
): Promise<LintResult> {
|
|
26
|
+
const cmd = new dntShim.Deno.Command("deno", {
|
|
27
|
+
args: ["lint", "--json"],
|
|
28
|
+
stdout: "piped",
|
|
29
|
+
stderr: "piped",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const child = cmd.spawn();
|
|
33
|
+
const timeoutMs = input.timeout ?? 120000;
|
|
34
|
+
|
|
35
|
+
const result = await Promise.race([
|
|
36
|
+
child.output(),
|
|
37
|
+
new Promise<never>((_, reject) => {
|
|
38
|
+
const timer = dntShim.setTimeout(() => {
|
|
39
|
+
try {
|
|
40
|
+
child.kill();
|
|
41
|
+
} catch {
|
|
42
|
+
// Process may have already exited
|
|
43
|
+
}
|
|
44
|
+
reject(new Error(`Lint execution timed out after ${timeoutMs}ms`));
|
|
45
|
+
}, timeoutMs);
|
|
46
|
+
dntShim.Deno.unrefTimer(timer);
|
|
47
|
+
}),
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
const stdout = new TextDecoder().decode(result.stdout);
|
|
51
|
+
return parseLintJsonOutput(stdout, result.code);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const vfRunLint: MCPTool<RunLintInput, LintResult> = {
|
|
55
|
+
name: "vf_run_lint",
|
|
56
|
+
title: "Run Lint",
|
|
57
|
+
annotations: {
|
|
58
|
+
readOnlyHint: true,
|
|
59
|
+
destructiveHint: false,
|
|
60
|
+
idempotentHint: true,
|
|
61
|
+
openWorldHint: false,
|
|
62
|
+
},
|
|
63
|
+
description: "Use this when you need to check for lint issues in the project. " +
|
|
64
|
+
"Returns structured diagnostics with file path, line, column, rule code, and message for each issue. " +
|
|
65
|
+
"Do not use for test results — use vf_run_tests instead. " +
|
|
66
|
+
"Do not use for compile/runtime errors — use vf_get_errors instead.",
|
|
67
|
+
inputSchema: runLintInput,
|
|
68
|
+
execute: (input) => executeLint(input),
|
|
69
|
+
};
|
package/src/deno.js
CHANGED
|
@@ -26,14 +26,16 @@ export function stripLeadingEmptyObjectPlaceholder(rawArgs: string): string {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export function mergeToolInputDelta(currentArguments: string, nextDelta: string): string {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
29
|
+
const normalizedDelta = nextDelta.trimStart();
|
|
30
|
+
const candidateDeltas = normalizedDelta.startsWith('"')
|
|
31
|
+
? [normalizedDelta, `{${normalizedDelta}`]
|
|
32
|
+
: [normalizedDelta];
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
if (currentArguments === "{}") {
|
|
35
|
+
for (const candidate of candidateDeltas) {
|
|
36
|
+
if (candidate.startsWith("{")) {
|
|
37
|
+
return candidate;
|
|
38
|
+
}
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
|
|
@@ -45,18 +47,20 @@ export function mergeToolInputDelta(currentArguments: string, nextDelta: string)
|
|
|
45
47
|
return nextDelta;
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
for (const candidate of candidateDeltas) {
|
|
51
|
+
if (candidate === currentArguments || currentArguments.includes(candidate)) {
|
|
52
|
+
return currentArguments;
|
|
53
|
+
}
|
|
51
54
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
if (candidate.startsWith(currentArguments)) {
|
|
56
|
+
return candidate;
|
|
57
|
+
}
|
|
55
58
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
const maxOverlap = Math.min(currentArguments.length, candidate.length);
|
|
60
|
+
for (let overlap = maxOverlap; overlap > 0; overlap--) {
|
|
61
|
+
if (currentArguments.endsWith(candidate.slice(0, overlap))) {
|
|
62
|
+
return currentArguments + candidate.slice(overlap);
|
|
63
|
+
}
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
66
|
|
|
@@ -198,6 +198,97 @@ export function captureStreamedToolCallInput(
|
|
|
198
198
|
};
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
/**
|
|
202
|
+
* A streamed tool call is "incomplete" when the provider stream terminated
|
|
203
|
+
* (abort, stall, timeout, transport error) before the SDK emitted the
|
|
204
|
+
* finalizing `tool-call` event that sets `inputAvailable: true`. In that state
|
|
205
|
+
* `arguments` only holds partial JSON fragments from `tool-input-delta` events,
|
|
206
|
+
* so the tool call is NOT a committed model choice and must not be parsed or
|
|
207
|
+
* executed. This is semantically distinct from a parse failure on a finalized
|
|
208
|
+
* tool call (`inputAvailable: true` but malformed JSON — which only happens on
|
|
209
|
+
* genuine provider bugs) and needs to be reported as a stream-termination
|
|
210
|
+
* error rather than a tool-argument error.
|
|
211
|
+
*/
|
|
212
|
+
export function isStreamedToolCallIncomplete(
|
|
213
|
+
toolCall: Pick<StreamingToolCall, "inputAvailable">,
|
|
214
|
+
): boolean {
|
|
215
|
+
return toolCall.inputAvailable !== true;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Classification of a streamed tool call when we reach end-of-stream and need
|
|
220
|
+
* to persist it into the assistant message. Three distinct cases, each with
|
|
221
|
+
* different semantics downstream:
|
|
222
|
+
*
|
|
223
|
+
* - `complete`: provider emitted the finalizing `tool-call` event and the
|
|
224
|
+
* arguments parsed cleanly. Execute the tool normally.
|
|
225
|
+
* - `parse-error`: provider emitted the finalizing `tool-call` event but the
|
|
226
|
+
* arguments are not valid JSON. This is a provider/SDK bug; record it as a
|
|
227
|
+
* tool-argument error so the step can recover.
|
|
228
|
+
* - `incomplete`: stream terminated before the finalizing event fired. The
|
|
229
|
+
* model never committed this tool use; record it as a stream-termination
|
|
230
|
+
* error so the parent (e.g. child-fork watchdog) can decide whether to
|
|
231
|
+
* retry the step cleanly instead of seeing a malformed tool call.
|
|
232
|
+
*/
|
|
233
|
+
export type StreamedToolCallMaterialization =
|
|
234
|
+
| { readonly kind: "complete"; readonly part: MessagePart }
|
|
235
|
+
| {
|
|
236
|
+
readonly kind: "parse-error";
|
|
237
|
+
readonly part: MessagePart;
|
|
238
|
+
readonly parseError: string;
|
|
239
|
+
}
|
|
240
|
+
| {
|
|
241
|
+
readonly kind: "incomplete";
|
|
242
|
+
readonly part: MessagePart;
|
|
243
|
+
readonly partialArgumentsLength: number;
|
|
244
|
+
readonly partialArgumentsPreview: string;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Classify and build the persisted `MessagePart` for a single streamed tool
|
|
249
|
+
* call. Pure function — no logging, no SSE, no memory. Callers decide what to
|
|
250
|
+
* do with the result so this stays unit-testable.
|
|
251
|
+
*
|
|
252
|
+
* The resulting `part` is always pushed into the assistant message so the
|
|
253
|
+
* conversation history is transparent: even incomplete tool calls leave a
|
|
254
|
+
* visible trace with their partial `inputText`. What differs is the caller's
|
|
255
|
+
* error-surfacing behavior (log warning, SSE event, tool-result error).
|
|
256
|
+
*/
|
|
257
|
+
export function materializeStreamedToolCall(
|
|
258
|
+
tc: StreamingToolCall,
|
|
259
|
+
): StreamedToolCallMaterialization {
|
|
260
|
+
const basePart: MessagePart = {
|
|
261
|
+
type: `tool-${tc.name}`,
|
|
262
|
+
toolCallId: tc.id,
|
|
263
|
+
toolName: tc.name,
|
|
264
|
+
args: {},
|
|
265
|
+
...(tc.arguments.length > 0 ? { inputText: tc.arguments } : {}),
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
if (isStreamedToolCallIncomplete(tc)) {
|
|
269
|
+
return {
|
|
270
|
+
kind: "incomplete",
|
|
271
|
+
part: basePart,
|
|
272
|
+
partialArgumentsLength: tc.arguments.length,
|
|
273
|
+
partialArgumentsPreview: tc.arguments.slice(0, 200),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const capturedInput = captureStreamedToolCallInput(tc);
|
|
278
|
+
const part: MessagePart = {
|
|
279
|
+
type: `tool-${tc.name}`,
|
|
280
|
+
toolCallId: tc.id,
|
|
281
|
+
toolName: tc.name,
|
|
282
|
+
args: capturedInput.args,
|
|
283
|
+
...(capturedInput.inputText ? { inputText: capturedInput.inputText } : {}),
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
if (capturedInput.parseError) {
|
|
287
|
+
return { kind: "parse-error", part, parseError: capturedInput.parseError };
|
|
288
|
+
}
|
|
289
|
+
return { kind: "complete", part };
|
|
290
|
+
}
|
|
291
|
+
|
|
201
292
|
function isToolResultPart(part: MessagePart): part is ToolResultPart {
|
|
202
293
|
return part.type === "tool-result" && "result" in part;
|
|
203
294
|
}
|
|
@@ -961,20 +1052,35 @@ export class AgentRuntime {
|
|
|
961
1052
|
if (state.accumulatedText) streamParts.push({ type: "text", text: state.accumulatedText });
|
|
962
1053
|
|
|
963
1054
|
for (const tc of state.toolCalls.values()) {
|
|
964
|
-
const
|
|
965
|
-
|
|
1055
|
+
const materialized = materializeStreamedToolCall(tc);
|
|
1056
|
+
streamParts.push(materialized.part);
|
|
1057
|
+
|
|
1058
|
+
if (materialized.kind === "incomplete") {
|
|
1059
|
+
// Stream terminated before the provider emitted the finalizing
|
|
1060
|
+
// `tool-call` event for this block. The model never committed this
|
|
1061
|
+
// tool use. Surface the failure via SSE so the live client can
|
|
1062
|
+
// react, and leave the partial fragment under `inputText` in the
|
|
1063
|
+
// persisted part above so the history is replayable and transparent.
|
|
1064
|
+
logger.warn("Streamed tool call terminated before tool-call event", {
|
|
1065
|
+
toolCallId: tc.id,
|
|
1066
|
+
toolName: tc.name,
|
|
1067
|
+
partialArgumentsLength: materialized.partialArgumentsLength,
|
|
1068
|
+
partialArgumentsPreview: materialized.partialArgumentsPreview,
|
|
1069
|
+
});
|
|
1070
|
+
const dynamicIncomplete = isDynamicTool(tc.name);
|
|
1071
|
+
sendSSE(controller, encoder, {
|
|
1072
|
+
type: "tool-input-error",
|
|
1073
|
+
toolCallId: tc.id,
|
|
1074
|
+
errorText: `Stream terminated before tool-call event fired for "${tc.name}". ` +
|
|
1075
|
+
`Received ${materialized.partialArgumentsLength} chars of partial tool-input deltas.`,
|
|
1076
|
+
...(dynamicIncomplete ? { dynamic: true } : {}),
|
|
1077
|
+
});
|
|
1078
|
+
} else if (materialized.kind === "parse-error") {
|
|
966
1079
|
logger.warn("Failed to parse streamed tool arguments", {
|
|
967
1080
|
toolCallId: tc.id,
|
|
968
|
-
error:
|
|
1081
|
+
error: materialized.parseError,
|
|
969
1082
|
});
|
|
970
1083
|
}
|
|
971
|
-
streamParts.push({
|
|
972
|
-
type: `tool-${tc.name}`,
|
|
973
|
-
toolCallId: tc.id,
|
|
974
|
-
toolName: tc.name,
|
|
975
|
-
args: capturedInput.args,
|
|
976
|
-
...(capturedInput.inputText ? { inputText: capturedInput.inputText } : {}),
|
|
977
|
-
});
|
|
978
1084
|
}
|
|
979
1085
|
|
|
980
1086
|
const assistantMessage: Message = {
|
|
@@ -1033,6 +1139,30 @@ export class AgentRuntime {
|
|
|
1033
1139
|
|
|
1034
1140
|
for (const tc of streamedToolCalls) {
|
|
1035
1141
|
throwIfAborted(abortSignal);
|
|
1142
|
+
if (isStreamedToolCallIncomplete(tc)) {
|
|
1143
|
+
// Stream ended before the provider finalized this tool call. We
|
|
1144
|
+
// cannot execute it — record a distinct stream-termination error
|
|
1145
|
+
// (not a tool-argument parse error) so the parent step and any
|
|
1146
|
+
// upstream orchestrator (e.g. the child-fork watchdog) see a
|
|
1147
|
+
// completed step with a clearly-labelled failure and can recover.
|
|
1148
|
+
const incompleteToolCall: ToolCall = {
|
|
1149
|
+
id: tc.id,
|
|
1150
|
+
name: tc.name,
|
|
1151
|
+
args: {},
|
|
1152
|
+
...(tc.arguments.length > 0 ? { inputText: tc.arguments } : {}),
|
|
1153
|
+
status: "pending",
|
|
1154
|
+
};
|
|
1155
|
+
await this.recordToolError(
|
|
1156
|
+
incompleteToolCall,
|
|
1157
|
+
`Stream terminated before tool-call event fired for "${tc.name}". ` +
|
|
1158
|
+
`Received ${tc.arguments.length} chars of partial tool-input deltas.`,
|
|
1159
|
+
controller,
|
|
1160
|
+
encoder,
|
|
1161
|
+
currentMessages,
|
|
1162
|
+
toolCalls,
|
|
1163
|
+
);
|
|
1164
|
+
continue;
|
|
1165
|
+
}
|
|
1036
1166
|
const capturedInput = captureStreamedToolCallInput(tc);
|
|
1037
1167
|
const toolCall: ToolCall = {
|
|
1038
1168
|
id: tc.id,
|