c64bridge 1.0.2 → 1.0.4
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/README.md +12 -12
- package/dist/.tsbuildinfo +1 -1
- package/dist/audio/record_and_analyze_audio.js +81 -13
- package/dist/c64Client.js +243 -40
- package/dist/device.js +32 -14
- package/dist/tools/audio.js +11 -5
- package/dist/tools/meta/artifacts.js +8 -3
- package/dist/tools/meta/audio.js +25 -28
- package/dist/tools/meta/config.js +12 -4
- package/dist/tools/meta/configInventory.js +4 -1
- package/dist/tools/meta/program.js +25 -12
- package/dist/tools/registry/batch.js +4 -2
- package/dist/tools/registry/config.js +1 -1
- package/dist/tools/registry/disk.js +5 -0
- package/dist/tools/registry/extract.js +3 -0
- package/dist/tools/registry/graphics.js +1 -0
- package/dist/tools/registry/program.js +1 -1
- package/dist/tools/registry/sound.js +4 -4
- package/dist/tools/registry/system.js +3 -3
- package/dist/tools/storage.js +2 -2
- package/dist/tools/streaming.js +11 -2
- package/mcp/schemas/c64_config.schema.json +2 -2
- package/mcp/schemas/c64_program.schema.json +10 -18
- package/mcp/schemas/c64_sound.schema.json +11 -9
- package/mcp/schemas/c64_system.schema.json +3 -3
- package/mcp/server.json +1 -1
- package/mcp/tools.json +55 -42
- package/mcp.json +1 -1
- package/package.json +4 -4
- package/scripts/mockC64Server.mjs +33 -6
- package/scripts/validate-release-artifact.mjs +62 -0
- package/scripts/validate-release.mjs +40 -0
- package/server.json +2 -2
package/dist/tools/audio.js
CHANGED
|
@@ -4,7 +4,8 @@ import { recordAndAnalyzeAudio } from "../audio/record_and_analyze_audio.js";
|
|
|
4
4
|
import { defineToolModule } from "./types.js";
|
|
5
5
|
import { booleanSchema, numberSchema, objectSchema, optionalSchema, stringSchema, } from "./schema.js";
|
|
6
6
|
import { jsonResult, textResult } from "./responses.js";
|
|
7
|
-
import { ToolError, ToolExecutionError, ToolValidationError, toolErrorResult, unknownErrorResult, } from "./errors.js";
|
|
7
|
+
import { ToolError, ToolExecutionError, ToolValidationError, ToolUnsupportedPlatformError, toolErrorResult, unknownErrorResult, } from "./errors.js";
|
|
8
|
+
import { getPlatformStatus } from "../platform.js";
|
|
8
9
|
const NOTE_PATTERN = /^([A-Ga-g])([#b]?)(-?\d+)$/;
|
|
9
10
|
const DEFAULT_SILENCE_VERIFY_DURATION_SECONDS = 1.5;
|
|
10
11
|
const DEFAULT_SILENCE_VERIFY_RMS_THRESHOLD = 0.02;
|
|
@@ -244,7 +245,7 @@ const musicCompileArgsSchema = objectSchema({
|
|
|
244
245
|
enum: ["yaml", "json"],
|
|
245
246
|
})),
|
|
246
247
|
output: optionalSchema(stringSchema({
|
|
247
|
-
description: "Playback target format.",
|
|
248
|
+
description: "Playback target format. PRG works on all backends; SID playback requires C64U/U64 or U2 (SID export with dryRun works everywhere).",
|
|
248
249
|
enum: ["prg", "sid"],
|
|
249
250
|
default: "prg",
|
|
250
251
|
}), "prg"),
|
|
@@ -256,7 +257,7 @@ const musicCompileArgsSchema = objectSchema({
|
|
|
256
257
|
additionalProperties: false,
|
|
257
258
|
});
|
|
258
259
|
const recordAndAnalyzeArgsSchema = objectSchema({
|
|
259
|
-
description: "Record audio from the
|
|
260
|
+
description: "Record audio and analyze SID playback characteristics. On the c64u backend the audio comes from the Ultimate audio stream; on other backends it is recorded from the host default input device (microphone).",
|
|
260
261
|
properties: {
|
|
261
262
|
durationSeconds: numberSchema({
|
|
262
263
|
description: "Capture duration in seconds (0.5 - 30).",
|
|
@@ -902,6 +903,10 @@ export const audioModule = defineToolModule({
|
|
|
902
903
|
throw new ToolValidationError("Provide sidwave or cpg source", { path: "$.sidwave" });
|
|
903
904
|
}
|
|
904
905
|
const outputFormat = parsed.output ?? "prg";
|
|
906
|
+
const platform = ctx.platform?.id ?? getPlatformStatus().id;
|
|
907
|
+
if (outputFormat === "sid" && !parsed.dryRun && platform === "vice") {
|
|
908
|
+
throw new ToolUnsupportedPlatformError("sidplay_attachment (use output: prg or dryRun: true on VICE)", platform, ["c64u", "u2"]);
|
|
909
|
+
}
|
|
905
910
|
ctx.logger.info("Compiling SIDWAVE composition", {
|
|
906
911
|
output: outputFormat,
|
|
907
912
|
dryRun: parsed.dryRun,
|
|
@@ -963,7 +968,7 @@ export const audioModule = defineToolModule({
|
|
|
963
968
|
},
|
|
964
969
|
{
|
|
965
970
|
name: "record_and_analyze_audio",
|
|
966
|
-
description: "Record audio from the
|
|
971
|
+
description: "Record audio and analyze SID playback characteristics. On the c64u backend the audio comes from the Ultimate audio stream; on other backends it is recorded from the host default input device (microphone).",
|
|
967
972
|
summary: "Captures PCM data, extracts notes, tempo, and deviation metrics for SID verification workflows.",
|
|
968
973
|
inputSchema: recordAndAnalyzeArgsSchema.jsonSchema,
|
|
969
974
|
relatedResources: ["c64://sound/sid/spec", "c64://sound/sid/file-format"],
|
|
@@ -981,7 +986,8 @@ export const audioModule = defineToolModule({
|
|
|
981
986
|
try {
|
|
982
987
|
const parsed = recordAndAnalyzeArgsSchema.parse(args ?? {});
|
|
983
988
|
ctx.logger.info("Recording audio for analysis", { durationSeconds: parsed.durationSeconds });
|
|
984
|
-
const
|
|
989
|
+
const analyzer = resolveAnalyzer(ctx);
|
|
990
|
+
const result = await analyzer({
|
|
985
991
|
durationSeconds: parsed.durationSeconds,
|
|
986
992
|
expectedSidwave: normaliseSidwaveInput(parsed.expectedSidwave),
|
|
987
993
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { objectSchema, stringSchema, arraySchema, numberSchema, optionalSchema, booleanSchema } from "../schema.js";
|
|
2
2
|
import { jsonResult } from "../responses.js";
|
|
3
|
-
import { ToolError, ToolValidationError, toolErrorResult, unknownErrorResult } from "../errors.js";
|
|
3
|
+
import { ToolError, ToolValidationError, ToolUnsupportedPlatformError, toolErrorResult, unknownErrorResult } from "../errors.js";
|
|
4
|
+
import { getPlatformStatus } from "../../platform.js";
|
|
4
5
|
import { promises as fs } from "node:fs";
|
|
5
6
|
import { resolve as resolvePath, join as joinPath, relative, sep, isAbsolute } from "node:path";
|
|
6
7
|
const bundleRunArtifactsArgsSchema = objectSchema({
|
|
@@ -18,7 +19,7 @@ const bundleRunArtifactsArgsSchema = objectSchema({
|
|
|
18
19
|
required: ["address", "length"],
|
|
19
20
|
additionalProperties: false,
|
|
20
21
|
}))),
|
|
21
|
-
captureDebugReg: optionalSchema(booleanSchema({ description: "Capture
|
|
22
|
+
captureDebugReg: optionalSchema(booleanSchema({ description: "Capture Ultimate debug registers (C64U/U64 only). Defaults to true on C64U/U64 and false on U2/VICE." })),
|
|
22
23
|
},
|
|
23
24
|
required: ["runId", "outputPath"],
|
|
24
25
|
additionalProperties: false,
|
|
@@ -46,7 +47,11 @@ export const tools = [
|
|
|
46
47
|
}
|
|
47
48
|
const outputPath = resolvePath(String(parsed.outputPath));
|
|
48
49
|
const captureScreen = parsed.captureScreen !== false;
|
|
49
|
-
const
|
|
50
|
+
const platform = ctx.platform?.id ?? getPlatformStatus().id;
|
|
51
|
+
const captureDebugReg = parsed.captureDebugReg ?? (platform === "c64u");
|
|
52
|
+
if (captureDebugReg && platform !== "c64u") {
|
|
53
|
+
throw new ToolUnsupportedPlatformError("debugreg_read", platform, ["c64u"]);
|
|
54
|
+
}
|
|
50
55
|
const memoryRanges = (parsed.memoryRanges ?? []);
|
|
51
56
|
await fs.mkdir(outputPath, { recursive: true });
|
|
52
57
|
const runPath = containedPath(outputPath, runId);
|
package/dist/tools/meta/audio.js
CHANGED
|
@@ -2,7 +2,8 @@ import { compileSidwaveToPrg, compileSidwaveToSid } from "../../sidwaveCompiler.
|
|
|
2
2
|
import { parseSidwave } from "../../sidwave.js";
|
|
3
3
|
import { recordAndAnalyzeAudio } from "../../audio/record_and_analyze_audio.js";
|
|
4
4
|
import { jsonResult } from "../responses.js";
|
|
5
|
-
import { ToolExecutionError, toolErrorResult, unknownErrorResult } from "../errors.js";
|
|
5
|
+
import { ToolExecutionError, ToolUnsupportedPlatformError, toolErrorResult, unknownErrorResult } from "../errors.js";
|
|
6
|
+
import { getPlatformStatus } from "../../platform.js";
|
|
6
7
|
import { sleep } from "./util.js";
|
|
7
8
|
import { arraySchema, booleanSchema, numberSchema, objectSchema, optionalSchema, stringSchema } from "../schema.js";
|
|
8
9
|
const DEFAULT_SILENCE_DURATION_SECONDS = 1.5;
|
|
@@ -68,7 +69,7 @@ const musicCompilePlayAnalyzeArgsSchema = objectSchema({
|
|
|
68
69
|
properties: {
|
|
69
70
|
sidwave: optionalSchema(stringSchema({ description: "SIDWAVE source in YAML or JSON format.", minLength: 1 })),
|
|
70
71
|
cpg: optionalSchema(stringSchema({ description: "Legacy CPG input format.", minLength: 1 })),
|
|
71
|
-
output: optionalSchema(stringSchema({ description: "Playback artifact format.", enum: ["prg", "sid"], default: "prg" }), "prg"),
|
|
72
|
+
output: optionalSchema(stringSchema({ description: "Playback artifact format. PRG works on all backends; SID attachment playback requires C64U/U64 or U2.", enum: ["prg", "sid"], default: "prg" }), "prg"),
|
|
72
73
|
waitBeforeCaptureMs: optionalSchema(numberSchema({ description: "Delay between starting playback and beginning analysis capture (milliseconds).", minimum: 0, maximum: 5000 }), DEFAULT_PLAYBACK_WAIT_MS),
|
|
73
74
|
analysisDurationSeconds: optionalSchema(numberSchema({ description: "Audio capture length in seconds.", minimum: 0.5, maximum: 20 }), DEFAULT_ANALYSIS_DURATION_SECONDS),
|
|
74
75
|
expectedSidwave: optionalSchema(stringSchema({ description: "Optional expected SIDWAVE used to refine analysis comparisons.", minLength: 1 })),
|
|
@@ -91,11 +92,11 @@ const musicPlayPresetArgsSchema = objectSchema({
|
|
|
91
92
|
}), "fuer_elise"),
|
|
92
93
|
platforms: optionalSchema(arraySchema(stringSchema({
|
|
93
94
|
description: "Backends to target in sequence.",
|
|
94
|
-
enum: ["vice", "c64u"],
|
|
95
|
+
enum: ["vice", "c64u", "u2"],
|
|
95
96
|
minLength: 1,
|
|
96
97
|
}))),
|
|
97
98
|
verify: optionalSchema(booleanSchema({
|
|
98
|
-
description: "Capture and analyze audio on
|
|
99
|
+
description: "Capture and analyze audio after playback starts: native streaming on C64U/U64, host audio input on U2/VICE (PortAudio required).",
|
|
99
100
|
default: true,
|
|
100
101
|
}), true),
|
|
101
102
|
analysisDurationSeconds: optionalSchema(numberSchema({
|
|
@@ -147,7 +148,7 @@ function normalizeSidwaveInput(input) {
|
|
|
147
148
|
return undefined;
|
|
148
149
|
}
|
|
149
150
|
function canonicalPresetBackends() {
|
|
150
|
-
return ["vice", "c64u"];
|
|
151
|
+
return ["vice", "c64u", "u2"];
|
|
151
152
|
}
|
|
152
153
|
function uniquePresetBackends(backends) {
|
|
153
154
|
return backends.filter((backend, index) => backends.indexOf(backend) === index);
|
|
@@ -285,30 +286,22 @@ export const tools = [
|
|
|
285
286
|
}
|
|
286
287
|
let verification = null;
|
|
287
288
|
if (verify) {
|
|
288
|
-
if (
|
|
289
|
-
|
|
290
|
-
await sleep(waitBeforeCaptureMs);
|
|
291
|
-
}
|
|
292
|
-
const analysis = await analyzer({
|
|
293
|
-
durationSeconds: analysisDurationSeconds,
|
|
294
|
-
expectedSidwave: preset.sidwave,
|
|
295
|
-
});
|
|
296
|
-
const metrics = extractRmsMetrics((analysis?.analysis?.global_metrics ?? {}));
|
|
297
|
-
verification = {
|
|
298
|
-
mode: "audio-analysis",
|
|
299
|
-
durationSeconds: analysis.analysis?.durationSeconds ?? analysisDurationSeconds,
|
|
300
|
-
averageRms: metrics.average,
|
|
301
|
-
maxRms: metrics.max,
|
|
302
|
-
voices: analysis.analysis?.voices ?? [],
|
|
303
|
-
analysis,
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
else {
|
|
307
|
-
verification = {
|
|
308
|
-
mode: "playback-launch",
|
|
309
|
-
note: "Audio analysis is only available on c64u; verified that playback launched on this backend.",
|
|
310
|
-
};
|
|
289
|
+
if (waitBeforeCaptureMs > 0) {
|
|
290
|
+
await sleep(waitBeforeCaptureMs);
|
|
311
291
|
}
|
|
292
|
+
const analysis = await analyzer({
|
|
293
|
+
durationSeconds: analysisDurationSeconds,
|
|
294
|
+
expectedSidwave: preset.sidwave,
|
|
295
|
+
});
|
|
296
|
+
const metrics = extractRmsMetrics((analysis?.analysis?.global_metrics ?? {}));
|
|
297
|
+
verification = {
|
|
298
|
+
mode: "audio-analysis",
|
|
299
|
+
durationSeconds: analysis.analysis?.durationSeconds ?? analysisDurationSeconds,
|
|
300
|
+
averageRms: metrics.average,
|
|
301
|
+
maxRms: metrics.max,
|
|
302
|
+
voices: analysis.analysis?.voices ?? [],
|
|
303
|
+
analysis,
|
|
304
|
+
};
|
|
312
305
|
}
|
|
313
306
|
await trySilenceSid(context);
|
|
314
307
|
backendResult.verification = verification;
|
|
@@ -409,6 +402,10 @@ export const tools = [
|
|
|
409
402
|
throw new ToolExecutionError("Provide sidwave or cpg source");
|
|
410
403
|
}
|
|
411
404
|
const format = (parsed.output ?? "prg");
|
|
405
|
+
const platform = context.platform?.id ?? getPlatformStatus().id;
|
|
406
|
+
if (format === "sid" && platform === "vice") {
|
|
407
|
+
throw new ToolUnsupportedPlatformError("sidplay_attachment (use output: prg on VICE)", platform, ["c64u", "u2"]);
|
|
408
|
+
}
|
|
412
409
|
const waitBeforeCaptureMs = parsed.waitBeforeCaptureMs ?? DEFAULT_PLAYBACK_WAIT_MS;
|
|
413
410
|
const analysisDurationSeconds = parsed.analysisDurationSeconds ?? DEFAULT_ANALYSIS_DURATION_SECONDS;
|
|
414
411
|
const verifySilenceBefore = parsed.verifySilenceBefore ?? true;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { objectSchema, stringSchema, optionalSchema, booleanSchema } from "../schema.js";
|
|
2
2
|
import { jsonResult } from "../responses.js";
|
|
3
|
-
import { ToolError, ToolExecutionError, ToolValidationError, toolErrorResult, unknownErrorResult } from "../errors.js";
|
|
3
|
+
import { ToolError, ToolUnsupportedPlatformError, ToolExecutionError, ToolValidationError, toolErrorResult, unknownErrorResult } from "../errors.js";
|
|
4
|
+
import { getPlatformStatus } from "../../platform.js";
|
|
4
5
|
import { promises as fs } from "node:fs";
|
|
5
6
|
import { dirname, resolve as resolvePath } from "node:path";
|
|
6
7
|
import { captureConfigSnapshot, validateSnapshotCategories } from "./configInventory.js";
|
|
@@ -10,7 +11,7 @@ const configSnapshotAndRestoreArgsSchema = objectSchema({
|
|
|
10
11
|
properties: {
|
|
11
12
|
action: stringSchema({ description: "One of: snapshot|restore|diff", enum: ["snapshot", "restore", "diff"] }),
|
|
12
13
|
path: stringSchema({ description: "Snapshot file path", minLength: 1 }),
|
|
13
|
-
applyToFlash: optionalSchema(booleanSchema({ description: "Save configuration to flash after restore", default: false }), false),
|
|
14
|
+
applyToFlash: optionalSchema(booleanSchema({ description: "Save configuration to flash after restore (C64U/U64 and U2 only)", default: false }), false),
|
|
14
15
|
},
|
|
15
16
|
required: ["action", "path"],
|
|
16
17
|
additionalProperties: false,
|
|
@@ -26,9 +27,13 @@ export const tools = [
|
|
|
26
27
|
try {
|
|
27
28
|
const parsed = configSnapshotAndRestoreArgsSchema.parse(args ?? {});
|
|
28
29
|
const action = parsed.action;
|
|
30
|
+
const platform = ctx.platform?.id ?? getPlatformStatus().id;
|
|
31
|
+
if (action === "restore" && parsed.applyToFlash && platform === "vice") {
|
|
32
|
+
throw new ToolUnsupportedPlatformError("config_save_to_flash", platform, ["c64u", "u2"]);
|
|
33
|
+
}
|
|
29
34
|
const path = resolvePath(String(parsed.path));
|
|
30
|
-
await fs.mkdir(dirname(path), { recursive: true });
|
|
31
35
|
if (action === "snapshot") {
|
|
36
|
+
await fs.mkdir(dirname(path), { recursive: true });
|
|
32
37
|
const [version, info, snapshotData] = await Promise.all([
|
|
33
38
|
ctx.client.version(),
|
|
34
39
|
ctx.client.info(),
|
|
@@ -66,7 +71,10 @@ export const tools = [
|
|
|
66
71
|
throw new ToolExecutionError("Batch update failed", { details: normalizeErrorDetails(result.details) });
|
|
67
72
|
}
|
|
68
73
|
if (parsed.applyToFlash) {
|
|
69
|
-
await ctx.client.configSaveToFlash();
|
|
74
|
+
const saved = await ctx.client.configSaveToFlash();
|
|
75
|
+
if (!saved.success) {
|
|
76
|
+
throw new ToolExecutionError("Configuration restored, but saving to flash failed", { details: normalizeErrorDetails(saved.details) });
|
|
77
|
+
}
|
|
70
78
|
}
|
|
71
79
|
return jsonResult({ restored: true, categories: Object.keys(payload).length }, { success: true });
|
|
72
80
|
}
|
|
@@ -96,7 +96,10 @@ function normalizeSnapshotValue(value, entry) {
|
|
|
96
96
|
if (value
|
|
97
97
|
&& typeof value === "object"
|
|
98
98
|
&& !Array.isArray(value)
|
|
99
|
-
&& Object.keys(value).length === 1
|
|
99
|
+
&& (Object.keys(value).length === 1
|
|
100
|
+
|| (value.category === entry.category
|
|
101
|
+
&& value.item === entry.item
|
|
102
|
+
&& ["int", "string"].includes(String(value.type))))
|
|
100
103
|
&& Object.prototype.hasOwnProperty.call(value, "value")) {
|
|
101
104
|
return value.value;
|
|
102
105
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { objectSchema, stringSchema, arraySchema, numberSchema, optionalSchema, booleanSchema } from "../schema.js";
|
|
2
2
|
import { jsonResult } from "../responses.js";
|
|
3
|
-
import { ToolError, ToolExecutionError, toolErrorResult, unknownErrorResult } from "../errors.js";
|
|
3
|
+
import { ToolError, ToolUnsupportedPlatformError, ToolExecutionError, toolErrorResult, unknownErrorResult } from "../errors.js";
|
|
4
|
+
import { getPlatformStatus } from "../../platform.js";
|
|
4
5
|
import { promises as fs } from "node:fs";
|
|
5
6
|
import { resolve as resolvePath, join as joinPath } from "node:path";
|
|
6
7
|
import { sleep, formatTimestampSpec } from "./util.js";
|
|
@@ -56,7 +57,7 @@ function shouldUseVisibleViceGreetingFastPath(rawArgs, requestedBackends) {
|
|
|
56
57
|
&& !hasOwnProperty(args, "outputPath");
|
|
57
58
|
}
|
|
58
59
|
function canonicalGreetingBackends() {
|
|
59
|
-
return ["vice", "c64u"];
|
|
60
|
+
return ["vice", "c64u", "u2"];
|
|
60
61
|
}
|
|
61
62
|
function uniqueGreetingBackends(backends) {
|
|
62
63
|
return backends.filter((backend, index) => backends.indexOf(backend) === index);
|
|
@@ -226,9 +227,9 @@ const crossPlatformGreetingArgsSchema = objectSchema({
|
|
|
226
227
|
properties: {
|
|
227
228
|
platforms: optionalSchema(arraySchema(stringSchema({
|
|
228
229
|
description: "Backends to target in sequence.",
|
|
229
|
-
enum: ["vice", "c64u"],
|
|
230
|
+
enum: ["vice", "c64u", "u2"],
|
|
230
231
|
minLength: 1,
|
|
231
|
-
}))
|
|
232
|
+
}))),
|
|
232
233
|
messageTemplate: optionalSchema(stringSchema({
|
|
233
234
|
description: "Greeting template. Use {PLATFORM}/{BACKEND} for uppercase or {platform}/{backend} for lowercase substitution.",
|
|
234
235
|
minLength: 1,
|
|
@@ -238,7 +239,7 @@ const crossPlatformGreetingArgsSchema = objectSchema({
|
|
|
238
239
|
default: true,
|
|
239
240
|
}), true),
|
|
240
241
|
captureScreenshot: optionalSchema(booleanSchema({
|
|
241
|
-
description: "Capture a framebuffer screenshot
|
|
242
|
+
description: "Capture a framebuffer screenshot on C64U/U64 or VICE. U2 skips screenshots by default; explicitly requesting one on U2 fails before execution.",
|
|
242
243
|
default: true,
|
|
243
244
|
}), true),
|
|
244
245
|
outputPath: optionalSchema(stringSchema({
|
|
@@ -311,11 +312,14 @@ export const tools = [
|
|
|
311
312
|
details: { availableBackends },
|
|
312
313
|
});
|
|
313
314
|
}
|
|
315
|
+
if (args?.captureScreenshot === true && requestedBackends.includes("u2")) {
|
|
316
|
+
throw new ToolUnsupportedPlatformError("capture_frame", "u2", ["c64u", "vice"]);
|
|
317
|
+
}
|
|
314
318
|
const visibleViceFastPath = shouldUseVisibleViceGreetingFastPath(args ?? {}, requestedBackends);
|
|
315
319
|
const verify = visibleViceFastPath ? false : parsed.verify !== false;
|
|
316
320
|
const captureScreenshot = visibleViceFastPath ? false : parsed.captureScreenshot !== false;
|
|
317
321
|
const restoreActiveBackend = parsed.restoreActiveBackend !== false;
|
|
318
|
-
const outputPath = captureScreenshot || parsed.outputPath
|
|
322
|
+
const outputPath = (captureScreenshot && requestedBackends.some((backend) => backend !== "u2")) || parsed.outputPath
|
|
319
323
|
? resolvePath(String(parsed.outputPath ?? joinPath(process.cwd(), "artifacts", "greetings", `run_${Date.now()}`)))
|
|
320
324
|
: undefined;
|
|
321
325
|
const timeoutMs = parsed.timeoutMs ?? 1500;
|
|
@@ -330,6 +334,7 @@ export const tools = [
|
|
|
330
334
|
}
|
|
331
335
|
try {
|
|
332
336
|
for (const backend of requestedBackends) {
|
|
337
|
+
const backendCaptureScreenshot = captureScreenshot && backend !== "u2";
|
|
333
338
|
const expectedText = applyGreetingTemplate(template, backend);
|
|
334
339
|
const program = buildGreetingProgram(expectedText);
|
|
335
340
|
const timeline = [];
|
|
@@ -358,7 +363,7 @@ export const tools = [
|
|
|
358
363
|
ctx.setPlatform(backend);
|
|
359
364
|
});
|
|
360
365
|
let prepareCapturePromise;
|
|
361
|
-
if (
|
|
366
|
+
if (backendCaptureScreenshot && backend === "c64u" && typeof client.prepareVideoCapture === "function") {
|
|
362
367
|
prepareCapturePromise = timedStep("prepare_capture", async () => {
|
|
363
368
|
await client.prepareVideoCapture?.({ keepAliveMs: Math.max(500, timeoutMs) });
|
|
364
369
|
preparedCaptureSession = true;
|
|
@@ -410,7 +415,7 @@ export const tools = [
|
|
|
410
415
|
let screenshotPath;
|
|
411
416
|
let screenshotAnalysis;
|
|
412
417
|
let screenshotError;
|
|
413
|
-
if (
|
|
418
|
+
if (backendCaptureScreenshot) {
|
|
414
419
|
try {
|
|
415
420
|
const capture = await timedStep("capture_screenshot", () => client.captureFrames({
|
|
416
421
|
count: 1,
|
|
@@ -447,7 +452,7 @@ export const tools = [
|
|
|
447
452
|
}
|
|
448
453
|
backendResult.verification = {
|
|
449
454
|
screenContainsExpectedText: verify ? screenMatched : undefined,
|
|
450
|
-
screenshotCaptured:
|
|
455
|
+
screenshotCaptured: backendCaptureScreenshot ? !screenshotError : undefined,
|
|
451
456
|
screenshotAnalysis,
|
|
452
457
|
};
|
|
453
458
|
if (screenshotPath) {
|
|
@@ -458,7 +463,7 @@ export const tools = [
|
|
|
458
463
|
}
|
|
459
464
|
backendResult.success = runResult.success
|
|
460
465
|
&& (!verify || screenMatched)
|
|
461
|
-
&& (!
|
|
466
|
+
&& (!backendCaptureScreenshot || !screenshotError);
|
|
462
467
|
backendResult.promptToVisibleLatencyMs = timeline
|
|
463
468
|
.filter((entry) => entry.ok === true && (entry.name === "switch_backend" || entry.name === "render_greeting_screen" || entry.name === "verify_screen"))
|
|
464
469
|
.reduce((total, entry) => total + Number(entry.latencyMs ?? 0), 0);
|
|
@@ -642,6 +647,10 @@ export const tools = [
|
|
|
642
647
|
try {
|
|
643
648
|
const parsed = batchRunWithAssertionsArgsSchema.parse(args ?? {});
|
|
644
649
|
const programs = parsed.programs;
|
|
650
|
+
const platform = ctx.platform?.id ?? getPlatformStatus().id;
|
|
651
|
+
if (platform === "vice" && programs.some((program) => program.path.toLowerCase().endsWith(".crt"))) {
|
|
652
|
+
throw new ToolUnsupportedPlatformError("run_crt", platform, ["c64u", "u2"]);
|
|
653
|
+
}
|
|
645
654
|
const continueOnError = parsed.continueOnError ?? false;
|
|
646
655
|
const durationMs = parsed.durationMs ?? 2000;
|
|
647
656
|
const resetDelayMs = parsed.resetDelayMs ?? 100;
|
|
@@ -659,10 +668,14 @@ export const tools = [
|
|
|
659
668
|
// Run program
|
|
660
669
|
const ext = program.path.toLowerCase().split(".").pop();
|
|
661
670
|
if (ext === "prg") {
|
|
662
|
-
await ctx.client.runPrgFile(program.path);
|
|
671
|
+
const run = await ctx.client.runPrgFile(program.path);
|
|
672
|
+
if (!run.success)
|
|
673
|
+
throw new ToolExecutionError("PRG playback failed", { details: run.details });
|
|
663
674
|
}
|
|
664
675
|
else if (ext === "crt") {
|
|
665
|
-
await ctx.client.runCrtFile(program.path);
|
|
676
|
+
const run = await ctx.client.runCrtFile(program.path);
|
|
677
|
+
if (!run.success)
|
|
678
|
+
throw new ToolExecutionError("CRT playback failed", { details: run.details });
|
|
666
679
|
}
|
|
667
680
|
await sleep(durationMs);
|
|
668
681
|
// Check assertions
|
|
@@ -114,10 +114,12 @@ export const batchModuleGroup = defineToolModule({
|
|
|
114
114
|
try {
|
|
115
115
|
const toolResult = await toolRegistry.invoke(toolName, args, {
|
|
116
116
|
...ctx,
|
|
117
|
-
platform:
|
|
117
|
+
platform: typeof ctx.client.getActiveBackendType === "function"
|
|
118
|
+
? { ...(ctx.platform ?? getPlatformStatus()), id: await ctx.client.getActiveBackendType() }
|
|
119
|
+
: ctx.platform ?? getPlatformStatus(),
|
|
118
120
|
});
|
|
119
121
|
const elapsedMs = Date.now() - cmdStart;
|
|
120
|
-
const isError = toolResult.isError === true;
|
|
122
|
+
const isError = toolResult.isError === true || toolResult.metadata?.success === false;
|
|
121
123
|
const text = toolResult.content.map((c) => c.text).join("\n");
|
|
122
124
|
const resultPayload = toolResult.structuredContent?.data ?? text;
|
|
123
125
|
if (isError) {
|
|
@@ -24,7 +24,7 @@ const configRestoreArgsSchema = objectSchema({
|
|
|
24
24
|
minLength: 1,
|
|
25
25
|
}),
|
|
26
26
|
applyToFlash: optionalSchema(booleanSchema({
|
|
27
|
-
description: "When true, save the restored configuration to flash immediately.",
|
|
27
|
+
description: "When true, save the restored configuration to flash immediately (C64U/U64 and U2 only).",
|
|
28
28
|
default: false,
|
|
29
29
|
})),
|
|
30
30
|
},
|
|
@@ -161,6 +161,11 @@ const diskOperations = [
|
|
|
161
161
|
const typedAttachment = attachmentMode;
|
|
162
162
|
const typedDriveMode = driveMode;
|
|
163
163
|
if (verify) {
|
|
164
|
+
if (typedType || typedAttachment) {
|
|
165
|
+
throw new ToolValidationError("type and attachmentMode are not applied by the verified mount workflow; set verify to false to use them", {
|
|
166
|
+
path: typedType ? "$.type" : "$.attachmentMode",
|
|
167
|
+
});
|
|
168
|
+
}
|
|
164
169
|
return metaModule.invoke("drive_mount_and_verify", {
|
|
165
170
|
drive: parsed.drive,
|
|
166
171
|
imagePath: parsed.image,
|
|
@@ -32,6 +32,7 @@ const extractOperations = [
|
|
|
32
32
|
const extractOperationHandlers = createOperationHandlers(extractOperations);
|
|
33
33
|
export const extractModule = defineToolModule({
|
|
34
34
|
domain: "extract",
|
|
35
|
+
supportedPlatforms: ["c64u", "u2", "vice"],
|
|
35
36
|
summary: "Grouped extraction helpers for sprites, charsets, memory dumps, and diagnostics.",
|
|
36
37
|
resources: ["c64://guide/bootstrap", "c64://basic/spec", "c64://assembly/6510-spec"],
|
|
37
38
|
defaultTags: ["extract", "diagnostics"],
|
|
@@ -49,6 +50,8 @@ export const extractModule = defineToolModule({
|
|
|
49
50
|
variants: extractOperations.map((operation) => operation.schema),
|
|
50
51
|
}),
|
|
51
52
|
tags: ["extract", "diagnostics", "grouped"],
|
|
53
|
+
operationPlatforms: { fs_stats: ["c64u", "u2"] },
|
|
54
|
+
operationToolNames: { fs_stats: "filesystem_stats_by_extension" },
|
|
52
55
|
examples: [
|
|
53
56
|
{
|
|
54
57
|
name: "Dump RAM to file",
|
|
@@ -216,6 +216,7 @@ export const graphicsModuleGroup = defineToolModule({
|
|
|
216
216
|
tools: [
|
|
217
217
|
{
|
|
218
218
|
name: "c64_graphics",
|
|
219
|
+
operationPlatforms: { capture_frame: ["c64u", "vice"] },
|
|
219
220
|
description: "Grouped entry point for frame capture and graphics rendering workflows.",
|
|
220
221
|
summary: "Captures frames, renders PETSCII text or art, previews sprites, and displays bitmap images from one tool.",
|
|
221
222
|
inputSchema: discriminatedUnionSchema({
|
|
@@ -69,7 +69,7 @@ export const programModule = defineToolModule({
|
|
|
69
69
|
variants: programOperations.map((operation) => operation.schema),
|
|
70
70
|
}),
|
|
71
71
|
tags: ["programs", "execution", "grouped"],
|
|
72
|
-
operationPlatforms: { load_prg: ["c64u", "u2"], run_crt: ["c64u", "u2"]
|
|
72
|
+
operationPlatforms: { load_prg: ["c64u", "u2"], run_crt: ["c64u", "u2"] },
|
|
73
73
|
operationToolNames: {
|
|
74
74
|
load_prg: "load_prg",
|
|
75
75
|
run_crt: "run_crt",
|
|
@@ -131,7 +131,7 @@ const soundOperations = [
|
|
|
131
131
|
},
|
|
132
132
|
{
|
|
133
133
|
op: "record_analyze",
|
|
134
|
-
schema: extendSchemaWithOp("record_analyze", ensureDescriptor(audioDescriptorIndex, "record_and_analyze_audio").inputSchema, { description: "Record audio for a fixed duration and return SID analysis metrics." }),
|
|
134
|
+
schema: extendSchemaWithOp("record_analyze", ensureDescriptor(audioDescriptorIndex, "record_and_analyze_audio").inputSchema, { description: "Record audio for a fixed duration and return SID analysis metrics. Uses the Ultimate audio stream on c64u and the host microphone on other backends." }),
|
|
135
135
|
handler: async (rawArgs, ctx) => invokeModuleTool(audioModule, "record_and_analyze_audio", rawArgs, ctx),
|
|
136
136
|
},
|
|
137
137
|
];
|
|
@@ -161,9 +161,9 @@ export const soundModuleGroup = defineToolModule({
|
|
|
161
161
|
capture_samples: ["c64u"],
|
|
162
162
|
play_sid_file: ["c64u", "u2"],
|
|
163
163
|
play_mod_file: ["c64u", "u2"],
|
|
164
|
-
pipeline: ["c64u", "u2"],
|
|
165
|
-
analyze: ["c64u"],
|
|
166
|
-
record_analyze: ["c64u"],
|
|
164
|
+
pipeline: ["c64u", "u2", "vice"],
|
|
165
|
+
analyze: ["c64u", "u2", "vice"],
|
|
166
|
+
record_analyze: ["c64u", "u2", "vice"],
|
|
167
167
|
},
|
|
168
168
|
operationToolNames: {
|
|
169
169
|
play_sid_file: "sidplay_file",
|
|
@@ -27,7 +27,7 @@ const systemOperations = [
|
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
29
|
op: "poweroff",
|
|
30
|
-
schema: extendSchemaWithOp("poweroff", ensureDescriptor(machineDescriptorIndex, "poweroff").inputSchema, { description: "
|
|
30
|
+
schema: extendSchemaWithOp("poweroff", ensureDescriptor(machineDescriptorIndex, "poweroff").inputSchema, { description: "Shut down C64U/U64 or quit VICE. U2-family hardware cannot power off." }),
|
|
31
31
|
handler: async (rawArgs, ctx) => invokeModuleTool(machineControlModule, "poweroff", rawArgs, ctx),
|
|
32
32
|
},
|
|
33
33
|
{
|
|
@@ -93,8 +93,8 @@ export const systemModuleGroup = defineToolModule({
|
|
|
93
93
|
variants: systemOperations.map((operation) => operation.schema),
|
|
94
94
|
}),
|
|
95
95
|
tags: ["system", "control", "grouped"],
|
|
96
|
-
operationPlatforms: { pause: ["c64u", "u2"], resume: ["c64u", "u2"], menu: ["c64u", "u2"], read_menu_screen: ["c64u", "u2"] },
|
|
97
|
-
operationToolNames: { pause: "pause", resume: "resume", menu: "menu_button", read_menu_screen: "read_menu_screen" },
|
|
96
|
+
operationPlatforms: { poweroff: ["c64u", "vice"], pause: ["c64u", "u2"], resume: ["c64u", "u2"], menu: ["c64u", "u2"], read_menu_screen: ["c64u", "u2"] },
|
|
97
|
+
operationToolNames: { poweroff: "poweroff", pause: "pause", resume: "resume", menu: "menu_button", read_menu_screen: "read_menu_screen" },
|
|
98
98
|
examples: [
|
|
99
99
|
{
|
|
100
100
|
name: "Soft reset",
|
package/dist/tools/storage.js
CHANGED
|
@@ -39,11 +39,11 @@ const driveMountArgsSchema = objectSchema({
|
|
|
39
39
|
drive: driveIdentifierSchema,
|
|
40
40
|
image: diskImagePathSchema,
|
|
41
41
|
type: optionalSchema(stringSchema({
|
|
42
|
-
description: "Override the disk image type when auto-detection is incorrect.",
|
|
42
|
+
description: "Override the disk image type when auto-detection is incorrect (C64U/U64 and U2 only; VICE uses auto-detection).",
|
|
43
43
|
enum: ["d64", "g64", "d71", "g71", "d81"],
|
|
44
44
|
})),
|
|
45
45
|
mode: optionalSchema(stringSchema({
|
|
46
|
-
description: "Attachment mode controlling how the firmware treats the mounted image.",
|
|
46
|
+
description: "Attachment mode controlling how the firmware treats the mounted image (C64U/U64 and U2 only).",
|
|
47
47
|
enum: ["readwrite", "readonly", "unlinked"],
|
|
48
48
|
})),
|
|
49
49
|
},
|
package/dist/tools/streaming.js
CHANGED
|
@@ -32,6 +32,15 @@ function toRecord(details) {
|
|
|
32
32
|
}
|
|
33
33
|
return undefined;
|
|
34
34
|
}
|
|
35
|
+
function withFailureReason(message, details) {
|
|
36
|
+
const record = toRecord(details);
|
|
37
|
+
const body = toRecord(record?.data) ?? toRecord(record?.response) ?? record;
|
|
38
|
+
const errors = body?.errors;
|
|
39
|
+
const reason = Array.isArray(errors) && errors.length > 0
|
|
40
|
+
? errors.map(String).join("; ")
|
|
41
|
+
: record?.message ?? record?.reason ?? (typeof details === "string" ? details : undefined);
|
|
42
|
+
return typeof reason === "string" && reason.length > 0 ? `${message}: ${reason}` : message;
|
|
43
|
+
}
|
|
35
44
|
export const streamingModule = defineToolModule({
|
|
36
45
|
domain: "streaming",
|
|
37
46
|
summary: "Long-running or streaming workflows such as audio capture or SID playback monitoring.",
|
|
@@ -71,7 +80,7 @@ export const streamingModule = defineToolModule({
|
|
|
71
80
|
});
|
|
72
81
|
const result = await ctx.client.streamStart(parsed.stream, parsed.target);
|
|
73
82
|
if (!result.success) {
|
|
74
|
-
throw new ToolExecutionError("C64 firmware reported failure while starting stream", {
|
|
83
|
+
throw new ToolExecutionError(withFailureReason("C64 firmware reported failure while starting stream", result.details), {
|
|
75
84
|
details: toRecord(result.details),
|
|
76
85
|
});
|
|
77
86
|
}
|
|
@@ -111,7 +120,7 @@ export const streamingModule = defineToolModule({
|
|
|
111
120
|
});
|
|
112
121
|
const result = await ctx.client.streamStop(parsed.stream);
|
|
113
122
|
if (!result.success) {
|
|
114
|
-
throw new ToolExecutionError("C64 firmware reported failure while stopping stream", {
|
|
123
|
+
throw new ToolExecutionError(withFailureReason("C64 firmware reported failure while stopping stream", result.details), {
|
|
115
124
|
details: toRecord(result.details),
|
|
116
125
|
});
|
|
117
126
|
}
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"boolean",
|
|
54
54
|
"null"
|
|
55
55
|
],
|
|
56
|
-
"description": "When true, save the restored configuration to flash immediately.",
|
|
56
|
+
"description": "When true, save the restored configuration to flash immediately (C64U/U64 and U2 only).",
|
|
57
57
|
"default": false
|
|
58
58
|
},
|
|
59
59
|
"root": {
|
|
@@ -452,7 +452,7 @@
|
|
|
452
452
|
"boolean",
|
|
453
453
|
"null"
|
|
454
454
|
],
|
|
455
|
-
"description": "When true, save the restored configuration to flash immediately.",
|
|
455
|
+
"description": "When true, save the restored configuration to flash immediately (C64U/U64 and U2 only).",
|
|
456
456
|
"default": false
|
|
457
457
|
},
|
|
458
458
|
"op": {
|
|
@@ -185,8 +185,7 @@
|
|
|
185
185
|
"boolean",
|
|
186
186
|
"null"
|
|
187
187
|
],
|
|
188
|
-
"description": "Capture
|
|
189
|
-
"default": true
|
|
188
|
+
"description": "Capture Ultimate debug registers (C64U/U64 only). Defaults to true on C64U/U64 and false on U2/VICE."
|
|
190
189
|
},
|
|
191
190
|
"platforms": {
|
|
192
191
|
"type": [
|
|
@@ -199,13 +198,10 @@
|
|
|
199
198
|
"minLength": 1,
|
|
200
199
|
"enum": [
|
|
201
200
|
"vice",
|
|
202
|
-
"c64u"
|
|
201
|
+
"c64u",
|
|
202
|
+
"u2"
|
|
203
203
|
]
|
|
204
|
-
}
|
|
205
|
-
"default": [
|
|
206
|
-
"vice",
|
|
207
|
-
"c64u"
|
|
208
|
-
]
|
|
204
|
+
}
|
|
209
205
|
},
|
|
210
206
|
"messageTemplate": {
|
|
211
207
|
"type": [
|
|
@@ -221,7 +217,7 @@
|
|
|
221
217
|
"boolean",
|
|
222
218
|
"null"
|
|
223
219
|
],
|
|
224
|
-
"description": "Capture a framebuffer screenshot
|
|
220
|
+
"description": "Capture a framebuffer screenshot on C64U/U64 or VICE. U2 skips screenshots by default; explicitly requesting one on U2 fails before execution.",
|
|
225
221
|
"default": true
|
|
226
222
|
},
|
|
227
223
|
"restoreActiveBackend": {
|
|
@@ -623,8 +619,7 @@
|
|
|
623
619
|
"boolean",
|
|
624
620
|
"null"
|
|
625
621
|
],
|
|
626
|
-
"description": "Capture
|
|
627
|
-
"default": true
|
|
622
|
+
"description": "Capture Ultimate debug registers (C64U/U64 only). Defaults to true on C64U/U64 and false on U2/VICE."
|
|
628
623
|
},
|
|
629
624
|
"op": {
|
|
630
625
|
"const": "bundle_run"
|
|
@@ -667,13 +662,10 @@
|
|
|
667
662
|
"minLength": 1,
|
|
668
663
|
"enum": [
|
|
669
664
|
"vice",
|
|
670
|
-
"c64u"
|
|
665
|
+
"c64u",
|
|
666
|
+
"u2"
|
|
671
667
|
]
|
|
672
|
-
}
|
|
673
|
-
"default": [
|
|
674
|
-
"vice",
|
|
675
|
-
"c64u"
|
|
676
|
-
]
|
|
668
|
+
}
|
|
677
669
|
},
|
|
678
670
|
"messageTemplate": {
|
|
679
671
|
"type": [
|
|
@@ -697,7 +689,7 @@
|
|
|
697
689
|
"boolean",
|
|
698
690
|
"null"
|
|
699
691
|
],
|
|
700
|
-
"description": "Capture a framebuffer screenshot
|
|
692
|
+
"description": "Capture a framebuffer screenshot on C64U/U64 or VICE. U2 skips screenshots by default; explicitly requesting one on U2 fails before execution.",
|
|
701
693
|
"default": true
|
|
702
694
|
},
|
|
703
695
|
"outputPath": {
|