c64bridge 0.9.1 → 0.9.3

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.
Files changed (46) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +115 -15
  3. package/data/assembly/assembly-spec.md +1 -1
  4. package/data/basic/rom-routines.md +269 -0
  5. package/data/context/bootstrap.md +4 -4
  6. package/data/context/fast-paths.md +3 -1
  7. package/data/graphics/petscii-style-guide.md +1 -1
  8. package/data/graphics/sprite-charset-best-practices.md +14 -13
  9. package/data/io/cia-spec.md +2 -2
  10. package/data/{memory/kernal-memory-map.md → kernal/rom-routines.md} +3 -2
  11. package/data/memory/low-memory-map.md +1 -0
  12. package/data/memory/memory-map.md +13 -0
  13. package/data/sound/sid-file-structure.md +5 -5
  14. package/data/sound/sid-programming-best-practices.md +5 -5
  15. package/data/vice/vice-binary-monitor-spec.md +5 -6
  16. package/dist/.tsbuildinfo +1 -1
  17. package/dist/c64Client.js +33 -15
  18. package/dist/config.js +22 -0
  19. package/dist/mcp-server.js +17 -5
  20. package/dist/rag/knowledgeIndex.js +54 -9
  21. package/dist/rag/resourceUris.js +2 -0
  22. package/dist/sidwaveCompiler.js +1 -1
  23. package/dist/tools/input.js +5 -7
  24. package/dist/tools/memory.js +11 -2
  25. package/dist/tools/programRunners.js +3 -3
  26. package/dist/tools/responses.js +14 -1
  27. package/dist/tools/{assember.js → translation/assembler.js} +56 -162
  28. package/dist/tools/{basicTokenizer.js → translation/basicTokenizer.js} +1 -1
  29. package/dist/tools/translation/disassembler.js +150 -0
  30. package/dist/tools/translation/opcodeInventory.js +126 -0
  31. package/doc/developer.md +3 -2
  32. package/doc/reviews/audit.md +143 -0
  33. package/mcp/resources.json +60 -7
  34. package/mcp/schemas/c64_input.schema.json +231 -113
  35. package/mcp/server.json +1 -1
  36. package/mcp/tools.json +234 -115
  37. package/mcp.json +8 -4
  38. package/package.json +12 -8
  39. package/scripts/c64-cli.mjs +2 -2
  40. package/scripts/run-coverage.mjs +54 -3
  41. package/scripts/run-tests.mjs +1 -0
  42. package/scripts/run-tests.ts +1 -0
  43. package/scripts/update-readme.ts +1 -0
  44. package/server.json +3 -3
  45. package/dist/tools/disassembler.js +0 -478
  46. /package/dist/tools/{symbolRegistry.js → translation/symbolRegistry.js} +0 -0
package/dist/c64Client.js CHANGED
@@ -8,8 +8,8 @@ See <https://www.gnu.org/licenses/> for details.
8
8
  import { Buffer } from "node:buffer";
9
9
  import { createSocket } from "node:dgram";
10
10
  import axios from "axios";
11
- import { basicToPrg } from "./tools/basicTokenizer.js";
12
- import { assemblyToPrg } from "./tools/assember.js";
11
+ import { basicToPrg } from "./tools/translation/basicTokenizer.js";
12
+ import { assemblyToPrg } from "./tools/translation/assembler.js";
13
13
  import { screenCodesToAscii } from "./petscii.js";
14
14
  import { resolveAddressSymbol } from "./knowledge.js";
15
15
  import { createAllFacades, createFacade } from "./device.js";
@@ -28,6 +28,17 @@ const DEFAULT_TEXT_FOREGROUND = 1;
28
28
  const DEFAULT_BORDER_COLOR = 6;
29
29
  const DEFAULT_BACKGROUND_COLOR = 0;
30
30
  const SPACE_SCREEN_CODE = 0x20;
31
+ function validateMemoryRange(address, length) {
32
+ if (!Number.isInteger(address) || address < 0 || address > 0xffff) {
33
+ throw new Error("Address must be within 0x0000 - 0xFFFF");
34
+ }
35
+ if (!Number.isInteger(length) || length <= 0) {
36
+ throw new Error("Length must be greater than zero");
37
+ }
38
+ if (address + length - 1 > 0xffff) {
39
+ throw new Error("Memory range must stay within 0x0000 - 0xFFFF");
40
+ }
41
+ }
31
42
  export class C64Client {
32
43
  constructor(baseUrl, options = {}) {
33
44
  this.allFacades = new Map();
@@ -48,7 +59,6 @@ export class C64Client {
48
59
  }).then((sel) => sel.facade);
49
60
  this.allFacades.set("c64u", this.facadePromise);
50
61
  this.initPromise = Promise.resolve();
51
- void this.primeGreetingDd00Cache(["c64u"]);
52
62
  return;
53
63
  }
54
64
  const allFacadesPromise = createAllFacades(undefined, {
@@ -65,12 +75,6 @@ export class C64Client {
65
75
  this.allFacades.set(secondaryType, Promise.resolve(secondary));
66
76
  }
67
77
  });
68
- void this.initPromise.then(async () => {
69
- if (this.allFacades.has("vice")) {
70
- await this.prewarmBackends(["vice"]);
71
- }
72
- void this.primeGreetingDd00Cache();
73
- }).catch(() => { });
74
78
  }
75
79
  async requireViceBackend() {
76
80
  const facade = await this.facadePromise;
@@ -481,9 +485,7 @@ export class C64Client {
481
485
  const resolved = resolveAddressSymbol(addressInput);
482
486
  const address = resolved ?? this.parseNumeric(addressInput);
483
487
  const length = this.parseNumeric(lengthInput);
484
- if (length <= 0) {
485
- throw new Error("Length must be greater than zero");
486
- }
488
+ validateMemoryRange(address, length);
487
489
  const rawBytes = await this.readMemoryRaw(address, length);
488
490
  const bytes = rawBytes.slice(0, length);
489
491
  return {
@@ -507,9 +509,7 @@ export class C64Client {
507
509
  const resolved = resolveAddressSymbol(addressInput);
508
510
  const address = resolved ?? this.parseNumeric(addressInput);
509
511
  const dataBuffer = this.hexStringToBuffer(bytesInput);
510
- if (dataBuffer.length === 0) {
511
- throw new Error("No bytes provided");
512
- }
512
+ validateMemoryRange(address, dataBuffer.length);
513
513
  // Prefer PUT with hex data for up to 128 bytes; fall back to POST binary for larger writes
514
514
  const addrStr = this.formatAddress(address);
515
515
  try {
@@ -1467,6 +1467,7 @@ export class C64Client {
1467
1467
  * Public to allow advanced polling and monitoring use cases.
1468
1468
  */
1469
1469
  async readMemoryRaw(address, length) {
1470
+ validateMemoryRange(address, length);
1470
1471
  try {
1471
1472
  const facade = await this.facadePromise;
1472
1473
  return await facade.readMemory(address, length);
@@ -1498,8 +1499,12 @@ export class C64Client {
1498
1499
  * keyboard-buffer injection) can avoid backend-specific paths.
1499
1500
  */
1500
1501
  async writeMemoryRaw(address, bytes) {
1502
+ if (!Number.isInteger(address) || address < 0 || address > 0xffff) {
1503
+ throw new Error("Address must be within 0x0000 - 0xFFFF");
1504
+ }
1501
1505
  const facade = await this.facadePromise;
1502
1506
  const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes);
1507
+ validateMemoryRange(address, buf.length);
1503
1508
  await facade.writeMemory(address, buf);
1504
1509
  }
1505
1510
  /**
@@ -1695,17 +1700,27 @@ export class C64Client {
1695
1700
  const lower = trimmed.toLowerCase();
1696
1701
  let radix = 10;
1697
1702
  let literal = lower;
1703
+ let pattern;
1698
1704
  if (lower.startsWith("$")) {
1699
1705
  radix = 16;
1700
1706
  literal = lower.slice(1);
1707
+ pattern = /^[0-9a-f]+$/;
1701
1708
  }
1702
1709
  else if (lower.startsWith("0x")) {
1703
1710
  radix = 16;
1704
1711
  literal = lower.slice(2);
1712
+ pattern = /^[0-9a-f]+$/;
1705
1713
  }
1706
1714
  else if (lower.startsWith("%")) {
1707
1715
  radix = 2;
1708
1716
  literal = lower.slice(1);
1717
+ pattern = /^[01]+$/;
1718
+ }
1719
+ else {
1720
+ pattern = /^[0-9]+$/;
1721
+ }
1722
+ if (!pattern.test(literal)) {
1723
+ throw new Error(`Unable to parse numeric value "${value}"`);
1709
1724
  }
1710
1725
  const parsed = Number.parseInt(literal, radix);
1711
1726
  if (!Number.isFinite(parsed) || Number.isNaN(parsed) || parsed < 0) {
@@ -1739,6 +1754,9 @@ export class C64Client {
1739
1754
  if (cleaned.length % 2 !== 0) {
1740
1755
  throw new Error("Hex string must contain an even number of characters");
1741
1756
  }
1757
+ if (!/^[0-9a-f]+$/.test(cleaned)) {
1758
+ throw new Error("Hex string contains non-hexadecimal characters");
1759
+ }
1742
1760
  return Buffer.from(cleaned, "hex");
1743
1761
  }
1744
1762
  hzToSidFrequency(hz, system = "PAL") {
package/dist/config.js CHANGED
@@ -7,6 +7,7 @@ const DEFAULT_CONFIG = {
7
7
  c64_host: DEFAULT_HOST,
8
8
  baseUrl: buildBaseUrl(DEFAULT_HOST, DEFAULT_PORT),
9
9
  c64_port: DEFAULT_PORT,
10
+ vicePrewarm: false,
10
11
  };
11
12
  let cachedConfig = null;
12
13
  export function loadConfig() {
@@ -35,6 +36,7 @@ export function loadConfig() {
35
36
  rawConfig ?? (rawConfig = {});
36
37
  // New schema: prefer c64u.{host,port,baseUrl}; keep legacy fields as fallback
37
38
  const c64u = rawConfig?.c64u;
39
+ const vice = rawConfig?.vice;
38
40
  const parsedC64uHost = parseEndpoint(configuredString(c64u?.host));
39
41
  const parsedC64uHostname = parseEndpoint(configuredString(c64u?.hostname));
40
42
  const parsedLegacyHost = parseEndpoint(configuredString(rawConfig?.c64_host));
@@ -73,6 +75,7 @@ export function loadConfig() {
73
75
  baseUrl,
74
76
  c64_port: port,
75
77
  networkPassword: firstDefined(configuredString(process.env.C64U_PASSWORD), configuredString(c64u?.networkPassword), configuredString(rawConfig?.networkPassword)),
78
+ vicePrewarm: firstDefined(configuredBoolean(process.env.VICE_PREWARM), configuredBoolean(vice?.prewarm), configuredBoolean(rawConfig?.vicePrewarm)) ?? false,
76
79
  };
77
80
  cachedConfig = config;
78
81
  return config;
@@ -92,6 +95,25 @@ function configuredPort(value) {
92
95
  }
93
96
  return undefined;
94
97
  }
98
+ function configuredBoolean(value) {
99
+ if (typeof value === "boolean") {
100
+ return value;
101
+ }
102
+ if (typeof value === "number") {
103
+ if (value === 1)
104
+ return true;
105
+ if (value === 0)
106
+ return false;
107
+ }
108
+ if (typeof value === "string") {
109
+ const normalized = value.trim().toLowerCase();
110
+ if (["1", "true", "yes", "on"].includes(normalized))
111
+ return true;
112
+ if (["0", "false", "no", "off"].includes(normalized))
113
+ return false;
114
+ }
115
+ return undefined;
116
+ }
95
117
  function normaliseBaseUrl(value) {
96
118
  const input = configuredString(value);
97
119
  if (!input)
@@ -60,6 +60,7 @@ async function main() {
60
60
  writeDiagnosticEvent("config_loaded", {
61
61
  baseUrl,
62
62
  hasNetworkPassword: Boolean(config.networkPassword),
63
+ vicePrewarm: config.vicePrewarm,
63
64
  });
64
65
  // Initialize C64 client (reuse existing)
65
66
  const client = await withDiagnosticSpan("startup", "create_client", { baseUrl }, async () => new C64Client(baseUrl, {
@@ -69,11 +70,22 @@ async function main() {
69
70
  const initialBackendType = await withDiagnosticSpan("startup", "resolve_active_backend", {}, () => client.getActiveBackendType());
70
71
  setPlatform(initialBackendType);
71
72
  writeDiagnosticEvent("platform_initialised", { platform: initialBackendType });
72
- void withDiagnosticSpan("startup", "prewarm_backends", { backends: ["vice"] }, () => client.prewarmBackends(["vice"])).then((results) => {
73
- writeDiagnosticEvent("backend_prewarm_complete", { results });
74
- }).catch((error) => {
75
- writeDiagnosticEvent("backend_prewarm_failed", { error });
76
- });
73
+ if (config.vicePrewarm) {
74
+ const availableBackends = client.getAvailableBackends();
75
+ if (availableBackends.includes("vice")) {
76
+ writeDiagnosticEvent("vice_prewarm_start");
77
+ void client.prewarmBackends(["vice"])
78
+ .then((result) => {
79
+ writeDiagnosticEvent("vice_prewarm_complete", { result });
80
+ })
81
+ .catch((error) => {
82
+ writeDiagnosticEvent("vice_prewarm_failed", { error });
83
+ });
84
+ }
85
+ else {
86
+ writeDiagnosticEvent("vice_prewarm_skipped", { reason: "vice_not_configured" });
87
+ }
88
+ }
77
89
  const rag = createLazyRagRetriever(() => initRag(), {
78
90
  onInitStart() {
79
91
  writeDiagnosticEvent("rag_init_start");
@@ -286,7 +286,14 @@ const KNOWLEDGE_BUNDLES = [
286
286
  summary: "Page-by-page breakdown of the 64 KB address space with hardware, ROM, and RAM regions.",
287
287
  prompts: ["memory-debug", "assembly-program"],
288
288
  tools: ["read", "write", "verify_and_write_memory"],
289
- relatedResources: [RESOURCE_URIS.assembly.spec6510],
289
+ relatedResources: [
290
+ RESOURCE_URIS.memory.zeroPageAndWorkspace,
291
+ RESOURCE_URIS.basic.romRoutines,
292
+ RESOURCE_URIS.kernal.romRoutines,
293
+ RESOURCE_URIS.io.spec,
294
+ RESOURCE_URIS.io.cia.spec,
295
+ RESOURCE_URIS.assembly.spec6510,
296
+ ],
290
297
  tags: ["memory", "hardware"],
291
298
  },
292
299
  {
@@ -298,19 +305,48 @@ const KNOWLEDGE_BUNDLES = [
298
305
  summary: "Documents zero-page variables, BASIC pointers, and KERNAL workspace addresses.",
299
306
  prompts: ["memory-debug", "assembly-program"],
300
307
  tools: ["read", "write", "verify_and_write_memory"],
301
- relatedResources: [RESOURCE_URIS.memory.map, RESOURCE_URIS.assembly.spec6510],
308
+ relatedResources: [
309
+ RESOURCE_URIS.memory.map,
310
+ RESOURCE_URIS.basic.romRoutines,
311
+ RESOURCE_URIS.kernal.romRoutines,
312
+ RESOURCE_URIS.assembly.spec6510,
313
+ ],
302
314
  tags: ["memory", "zero-page"],
303
315
  },
316
+ {
317
+ uri: RESOURCE_URIS.basic.romRoutines,
318
+ name: "BASIC ROM Routines Memory Map",
319
+ description: "Memory-map-style breakdown of BASIC ROM routines, tables, parser helpers, and entry points.",
320
+ relativePath: "data/basic/rom-routines.md",
321
+ priority: "reference",
322
+ summary: "Maps BASIC ROM landmarks, reusable interpreter routines, numeric helpers, string helpers, and C64-specific BASIC continuation entries.",
323
+ prompts: ["memory-debug", "assembly-program", "basic-program"],
324
+ tools: ["read", "verify_and_write_memory"],
325
+ relatedResources: [
326
+ RESOURCE_URIS.memory.map,
327
+ RESOURCE_URIS.memory.zeroPageAndWorkspace,
328
+ RESOURCE_URIS.kernal.romRoutines,
329
+ RESOURCE_URIS.basic.spec,
330
+ RESOURCE_URIS.assembly.spec6510,
331
+ ],
332
+ tags: ["memory", "basic", "rom"],
333
+ },
304
334
  {
305
335
  uri: RESOURCE_URIS.kernal.romRoutines,
306
- name: "KERNAL Memory Map",
307
- description: "Detailed breakdown of KERNAL ROM routines and entry points.",
308
- relativePath: "data/memory/kernal-memory-map.md",
336
+ name: "KERNAL ROM Routines Memory Map",
337
+ description: "Memory-map-style breakdown of KERNAL ROM routines, vectors, tables, and entry points.",
338
+ relativePath: "data/kernal/rom-routines.md",
309
339
  priority: "reference",
310
- summary: "Lists KERNAL ROM vectors and service routines for OS-level functionality.",
340
+ summary: "Maps KERNAL ROM vectors, jump table entries, internal routines, serial/tape/screen services, and reset handlers.",
311
341
  prompts: ["memory-debug", "assembly-program"],
312
342
  tools: ["read", "verify_and_write_memory"],
313
- relatedResources: [RESOURCE_URIS.memory.map, RESOURCE_URIS.assembly.spec6510],
343
+ relatedResources: [
344
+ RESOURCE_URIS.memory.map,
345
+ RESOURCE_URIS.memory.zeroPageAndWorkspace,
346
+ RESOURCE_URIS.basic.romRoutines,
347
+ RESOURCE_URIS.io.spec,
348
+ RESOURCE_URIS.assembly.spec6510,
349
+ ],
314
350
  tags: ["memory", "kernal"],
315
351
  },
316
352
  {
@@ -322,7 +358,12 @@ const KNOWLEDGE_BUNDLES = [
322
358
  summary: "Covers VIC-II, SID, CIA, and system control registers with address ranges and usage notes.",
323
359
  prompts: ["memory-debug", "assembly-program"],
324
360
  tools: ["read", "write", "verify_and_write_memory", "debugreg_read", "debugreg_write"],
325
- relatedResources: [RESOURCE_URIS.memory.map, RESOURCE_URIS.assembly.spec6510],
361
+ relatedResources: [
362
+ RESOURCE_URIS.memory.map,
363
+ RESOURCE_URIS.memory.zeroPageAndWorkspace,
364
+ RESOURCE_URIS.io.cia.spec,
365
+ RESOURCE_URIS.assembly.spec6510,
366
+ ],
326
367
  tags: ["io", "hardware"],
327
368
  },
328
369
  {
@@ -334,7 +375,11 @@ const KNOWLEDGE_BUNDLES = [
334
375
  summary: "Details CIA 1/2 registers, timers, interrupts, and keyboard matrix layout.",
335
376
  prompts: ["memory-debug", "assembly-program"],
336
377
  tools: ["read", "write", "verify_and_write_memory", "debugreg_read"],
337
- relatedResources: [RESOURCE_URIS.io.spec, RESOURCE_URIS.memory.map],
378
+ relatedResources: [
379
+ RESOURCE_URIS.io.spec,
380
+ RESOURCE_URIS.memory.map,
381
+ RESOURCE_URIS.memory.zeroPageAndWorkspace,
382
+ ],
338
383
  tags: ["io", "hardware"],
339
384
  },
340
385
  ],
@@ -10,6 +10,7 @@ export const RESOURCE_URIS = {
10
10
  basic: {
11
11
  spec: "c64://basic/spec",
12
12
  pitfalls: "c64://basic/pitfalls",
13
+ romRoutines: "c64://basic/rom-routines",
13
14
  },
14
15
  assembly: {
15
16
  spec6510: "c64://assembly/6510-spec",
@@ -80,6 +81,7 @@ export const CANONICAL_KNOWLEDGE_RESOURCE_URIS = Object.freeze([
80
81
  RESOURCE_URIS.graphics.spriteCharset.bestPractices,
81
82
  RESOURCE_URIS.memory.map,
82
83
  RESOURCE_URIS.memory.zeroPageAndWorkspace,
84
+ RESOURCE_URIS.basic.romRoutines,
83
85
  RESOURCE_URIS.kernal.romRoutines,
84
86
  RESOURCE_URIS.io.spec,
85
87
  RESOURCE_URIS.io.cia.spec,
@@ -6,7 +6,7 @@ Licensed under the GNU General Public License v2.0 or later.
6
6
  See <https://www.gnu.org/licenses/> for details.
7
7
  */
8
8
  import { Buffer } from "node:buffer";
9
- import { assemblyToPrg } from "./tools/assember.js";
9
+ import { assemblyToPrg } from "./tools/translation/assembler.js";
10
10
  import { midiToHz, noteNameToMidi } from "./sidwave.js";
11
11
  /**
12
12
  * Small SIDWAVE to PRG compiler: generates a 50/60Hz player that writes SID registers from a precomputed frame table.
@@ -1,4 +1,4 @@
1
- import { createOperationDispatcher, defineToolModule, } from "./types.js";
1
+ import { createOperationDispatcher, defineToolModule, discriminatedUnionSchema, } from "./types.js";
2
2
  import { arraySchema, literalSchema, numberSchema, objectSchema, optionalSchema, stringSchema, } from "./schema.js";
3
3
  import { textResult } from "./responses.js";
4
4
  import { ToolValidationError, toolErrorResult, unknownErrorResult, } from "./errors.js";
@@ -285,7 +285,7 @@ export const inputModule = defineToolModule({
285
285
  domain: "input",
286
286
  summary: "Cross-platform keyboard input plus VICE-only joystick simulation.",
287
287
  supportedPlatforms: ["c64u", "vice"],
288
- resources: ["c64://specs/assembly", "c64://specs/memory-map"],
288
+ resources: ["c64://guide/bootstrap", "c64://memory/map", "c64://io/cia/spec"],
289
289
  prompts: [],
290
290
  defaultTags: ["input"],
291
291
  workflowHints: [
@@ -298,16 +298,14 @@ export const inputModule = defineToolModule({
298
298
  name: "c64_input",
299
299
  description: "Keyboard buffer injection (cross-platform) and joystick simulation (VICE only).",
300
300
  summary: "Types text, taps keys, and simulates joystick movements.",
301
- inputSchema: {
302
- type: "object",
301
+ inputSchema: discriminatedUnionSchema({
303
302
  description: "Input operations: write_text, key, joystick.",
304
- oneOf: [
303
+ variants: [
305
304
  writeTextArgsSchema.jsonSchema,
306
305
  keyArgsSchema.jsonSchema,
307
306
  joystickArgsSchema.jsonSchema,
308
307
  ],
309
- discriminator: { propertyName: "op" },
310
- },
308
+ }),
311
309
  operationPlatforms: { joystick: ["vice"] },
312
310
  tags: ["input", "keyboard", "joystick"],
313
311
  examples: [
@@ -5,8 +5,8 @@ import { defineToolModule, OPERATION_DISCRIMINATOR, } from "./types.js";
5
5
  import { booleanSchema, numberSchema, objectSchema, optionalSchema, stringSchema } from "./schema.js";
6
6
  import { jsonResult, textResult } from "./responses.js";
7
7
  import { ToolError, ToolExecutionError, ToolValidationError, toolErrorResult, unknownErrorResult, } from "./errors.js";
8
- import { disassemble, formatDisassembly } from "./disassembler.js";
9
- import { getViceSymbols } from "./symbolRegistry.js";
8
+ import { disassemble, formatDisassembly } from "./translation/disassembler.js";
9
+ import { getViceSymbols } from "./translation/symbolRegistry.js";
10
10
  import { resolveAddressSymbol } from "../knowledge.js";
11
11
  function toRecord(details) {
12
12
  if (details && typeof details === "object") {
@@ -39,6 +39,12 @@ function parseHexInternal(value, path) {
39
39
  if (cleaned.length === 0) {
40
40
  return { bytes: new Uint8Array(), canonical: "$" };
41
41
  }
42
+ if (!/^[0-9A-F]+$/.test(cleaned)) {
43
+ throw new ToolValidationError("Hex string contains non-hexadecimal characters", {
44
+ path,
45
+ details: { value },
46
+ });
47
+ }
42
48
  const bytes = Uint8Array.from(Buffer.from(cleaned, "hex"));
43
49
  return { bytes, canonical: `$${cleaned}` };
44
50
  }
@@ -399,6 +405,7 @@ async function executeDisassemble(rawArgs, ctx) {
399
405
  const length = parsed.length ?? 64;
400
406
  ctx.logger.info("Disassembling VICE memory", { address: parsed.address, length });
401
407
  const address = parseAddressArg(parsed.address, "$.address");
408
+ validateRangeWithinAddressSpace(address, length, "$.address");
402
409
  const bytes = await ctx.client.readMemoryRaw(address, length);
403
410
  const symbols = ctx.platform?.id === "vice" ? getViceSymbols() : undefined;
404
411
  const lines = disassemble(bytes, address, parsed.instructionCount, symbols);
@@ -597,6 +604,8 @@ async function executeCompareMemory(rawArgs, ctx) {
597
604
  const a2 = parseAddressArg(parsed.address2, "$.address2");
598
605
  const len = parsed.length;
599
606
  const maxDiffs = parsed.maxDiffs ?? 10;
607
+ validateRangeWithinAddressSpace(a1, len, "$.address1");
608
+ validateRangeWithinAddressSpace(a2, len, "$.address2");
600
609
  ctx.logger.info("Comparing memory regions", { a1: fmtAddr(a1), a2: fmtAddr(a2), len });
601
610
  const [r1, r2] = await Promise.all([
602
611
  ctx.client.readMemoryRaw(a1, len),
@@ -1,12 +1,12 @@
1
1
  import fs from "node:fs";
2
- import { assemblyToPrgDetailed, AssemblyError } from "./assember.js";
3
- import { basicToPrg } from "./basicTokenizer.js";
2
+ import { assemblyToPrgDetailed, AssemblyError } from "./translation/assembler.js";
3
+ import { basicToPrg } from "./translation/basicTokenizer.js";
4
4
  import { defineToolModule, OPERATION_DISCRIMINATOR, } from "./types.js";
5
5
  import { booleanSchema, objectSchema, optionalSchema, stringSchema } from "./schema.js";
6
6
  import { textResult } from "./responses.js";
7
7
  import { ToolExecutionError, ToolError, ToolValidationError, toolErrorResult, unknownErrorResult, } from "./errors.js";
8
8
  import { pollForProgramOutcome } from "./pollValidator.js";
9
- import { clearViceSymbols, setViceSymbols, parseViceSymbolFile } from "./symbolRegistry.js";
9
+ import { clearViceSymbols, setViceSymbols, parseViceSymbolFile } from "./translation/symbolRegistry.js";
10
10
  function extractFailureDetails(details) {
11
11
  if (details === undefined || details === null) {
12
12
  return undefined;
@@ -1,5 +1,5 @@
1
1
  export function textResult(text, metadata) {
2
- return {
2
+ const result = {
3
3
  content: [
4
4
  {
5
5
  type: "text",
@@ -8,6 +8,19 @@ export function textResult(text, metadata) {
8
8
  ],
9
9
  metadata,
10
10
  };
11
+ if (metadata !== undefined) {
12
+ return {
13
+ ...result,
14
+ structuredContent: {
15
+ type: "json",
16
+ data: {
17
+ message: text,
18
+ ...metadata,
19
+ },
20
+ },
21
+ };
22
+ }
23
+ return result;
11
24
  }
12
25
  export function jsonResult(data, metadata) {
13
26
  const text = typeof data === "string"