c64bridge 1.0.3 → 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/dist/c64Client.js CHANGED
@@ -1141,6 +1141,9 @@ export class C64Client {
1141
1141
  if (facade.type === "vice") {
1142
1142
  return this.captureViceFrames(requestedCount);
1143
1143
  }
1144
+ if (facade.type !== "c64u") {
1145
+ throw new Error("Video frame capture is only available on C64U/U64 and VICE; U2 has no video stream");
1146
+ }
1144
1147
  return this.captureC64uVideoFrames(facade, requestedCount, {
1145
1148
  reuseSession: options?.reuseSession === true,
1146
1149
  keepAliveMs: Math.max(0, Math.trunc(options?.keepAliveMs ?? 0)),
package/dist/device.js CHANGED
@@ -632,10 +632,6 @@ export class ViceBackend {
632
632
  }
633
633
  async powerCycle() { return this.nuclearReset(); }
634
634
  async nuclearReset() {
635
- const poweroffResult = await this.poweroff();
636
- if (!poweroffResult.success) {
637
- return poweroffResult;
638
- }
639
635
  if (!this.manageProcess) {
640
636
  return {
641
637
  success: false,
@@ -645,6 +641,10 @@ export class ViceBackend {
645
641
  },
646
642
  };
647
643
  }
644
+ const poweroffResult = await this.poweroff();
645
+ if (!poweroffResult.success) {
646
+ return poweroffResult;
647
+ }
648
648
  try {
649
649
  await this.ensureProcess();
650
650
  return { success: true };
@@ -694,7 +694,10 @@ export class ViceBackend {
694
694
  return drives;
695
695
  });
696
696
  }
697
- async driveMount(drive, imagePath) {
697
+ async driveMount(drive, imagePath, options) {
698
+ if (options?.type !== undefined || options?.mode !== undefined) {
699
+ throw unsupported("driveMount type/mode overrides; omit these options on VICE");
700
+ }
698
701
  const n = parseDriveNumber(drive);
699
702
  await this.withClient(async (client) => {
700
703
  await client.resourceSet(`Drive${n}CPUEnabled`, 1);
@@ -760,8 +763,18 @@ export class ViceBackend {
760
763
  };
761
764
  }
762
765
  async configGet(_category, item) {
763
- if (!item)
764
- throw unsupported("configGet without item name");
766
+ if (!item) {
767
+ if (_category !== "VICE")
768
+ throw new Error(`Unknown VICE configuration category '${_category}'`);
769
+ const inventory = await this.configsList();
770
+ return this.withClient(async (client) => {
771
+ const values = {};
772
+ for (const name of inventory.categories[0].items) {
773
+ values[name] = (await client.resourceGet(name)).value;
774
+ }
775
+ return values;
776
+ });
777
+ }
765
778
  return await this.withClient(async (client) => {
766
779
  const res = await client.resourceGet(item);
767
780
  return { category: _category, item, value: res.value, type: res.type };
@@ -771,9 +784,7 @@ export class ViceBackend {
771
784
  let parsed = value;
772
785
  await this.withClient(async (client) => {
773
786
  const current = await client.resourceGet(item);
774
- parsed = current.type === "int" ? Number(value) : value;
775
- if (current.type === "int" && !Number.isFinite(parsed))
776
- throw new Error(`VICE resource '${item}' requires an integer value`);
787
+ parsed = parseViceConfigValue(value, current.type, item);
777
788
  await client.resourceSet(item, parsed);
778
789
  });
779
790
  return { success: true, details: { item, value: parsed } };
@@ -784,11 +795,8 @@ export class ViceBackend {
784
795
  for (const [category, items] of Object.entries(payload)) {
785
796
  for (const [item, value] of Object.entries(items)) {
786
797
  try {
787
- const str = String(value ?? "");
788
798
  const current = await client.resourceGet(item);
789
- const parsed = current.type === "int" ? Number(str) : str;
790
- if (current.type === "int" && !Number.isFinite(parsed))
791
- throw new Error(`VICE resource '${item}' requires an integer value`);
799
+ const parsed = parseViceConfigValue(value, current.type, item);
792
800
  await client.resourceSet(item, parsed);
793
801
  results.push({ item: `${category}/${item}`, success: true });
794
802
  }
@@ -814,6 +822,16 @@ export class ViceBackend {
814
822
  }
815
823
  ViceBackend.supervisors = new Map();
816
824
  ViceBackend.cleanupRegistered = false;
825
+ function parseViceConfigValue(value, type, item) {
826
+ if (type !== "int")
827
+ return String(value ?? "");
828
+ const parsed = value === true || value === "true" ? 1
829
+ : value === false || value === "false" ? 0 : Number(value);
830
+ if (!Number.isInteger(parsed) || parsed < -0x80000000 || parsed > 0x7fffffff) {
831
+ throw new Error(`VICE resource '${item}' requires a 32-bit integer value`);
832
+ }
833
+ return parsed;
834
+ }
817
835
  function unsupported(name) { const err = new Error(`Operation '${name}' is not supported by the VICE backend in phase one`); err.code = "UNSUPPORTED"; return err; }
818
836
  /** Ultimate file APIs treat slash as a path separator. Encode each name once,
819
837
  * preserving leading and nested separators for firmware routing. */
@@ -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"),
@@ -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,
@@ -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 debugreg state", default: true }), true),
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 captureDebugReg = parsed.captureDebugReg !== false;
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);
@@ -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 supported backends after playback starts.",
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 (backend === "c64u") {
289
- if (waitBeforeCaptureMs > 0) {
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
- })), ["vice", "c64u"]),
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 and save it as a PNG per backend.",
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 (captureScreenshot && backend === "c64u" && typeof client.prepareVideoCapture === "function") {
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 (captureScreenshot) {
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: captureScreenshot ? !screenshotError : undefined,
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
- && (!captureScreenshot || !screenshotError);
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: getPlatformStatus(),
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"], bundle_run: ["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",
@@ -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: "Request a controlled shutdown via the Ultimate firmware." }),
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",
@@ -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
  },
@@ -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": {