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.
- package/AGENTS.md +5 -0
- package/README.md +115 -15
- package/data/assembly/assembly-spec.md +1 -1
- package/data/basic/rom-routines.md +269 -0
- package/data/context/bootstrap.md +4 -4
- package/data/context/fast-paths.md +3 -1
- package/data/graphics/petscii-style-guide.md +1 -1
- package/data/graphics/sprite-charset-best-practices.md +14 -13
- package/data/io/cia-spec.md +2 -2
- package/data/{memory/kernal-memory-map.md → kernal/rom-routines.md} +3 -2
- package/data/memory/low-memory-map.md +1 -0
- package/data/memory/memory-map.md +13 -0
- package/data/sound/sid-file-structure.md +5 -5
- package/data/sound/sid-programming-best-practices.md +5 -5
- package/data/vice/vice-binary-monitor-spec.md +5 -6
- package/dist/.tsbuildinfo +1 -1
- package/dist/c64Client.js +33 -15
- package/dist/config.js +22 -0
- package/dist/mcp-server.js +17 -5
- package/dist/rag/knowledgeIndex.js +54 -9
- package/dist/rag/resourceUris.js +2 -0
- package/dist/sidwaveCompiler.js +1 -1
- package/dist/tools/input.js +5 -7
- package/dist/tools/memory.js +11 -2
- package/dist/tools/programRunners.js +3 -3
- package/dist/tools/responses.js +14 -1
- package/dist/tools/{assember.js → translation/assembler.js} +56 -162
- package/dist/tools/{basicTokenizer.js → translation/basicTokenizer.js} +1 -1
- package/dist/tools/translation/disassembler.js +150 -0
- package/dist/tools/translation/opcodeInventory.js +126 -0
- package/doc/developer.md +3 -2
- package/doc/reviews/audit.md +143 -0
- package/mcp/resources.json +60 -7
- package/mcp/schemas/c64_input.schema.json +231 -113
- package/mcp/server.json +1 -1
- package/mcp/tools.json +234 -115
- package/mcp.json +8 -4
- package/package.json +12 -8
- package/scripts/c64-cli.mjs +2 -2
- package/scripts/run-coverage.mjs +54 -3
- package/scripts/run-tests.mjs +1 -0
- package/scripts/run-tests.ts +1 -0
- package/scripts/update-readme.ts +1 -0
- package/server.json +3 -3
- package/dist/tools/disassembler.js +0 -478
- /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/
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
package/dist/mcp-server.js
CHANGED
|
@@ -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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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: [
|
|
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: [
|
|
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: "
|
|
308
|
-
relativePath: "data/
|
|
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: "
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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
|
],
|
package/dist/rag/resourceUris.js
CHANGED
|
@@ -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,
|
package/dist/sidwaveCompiler.js
CHANGED
|
@@ -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/
|
|
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.
|
package/dist/tools/input.js
CHANGED
|
@@ -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://
|
|
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
|
-
|
|
303
|
+
variants: [
|
|
305
304
|
writeTextArgsSchema.jsonSchema,
|
|
306
305
|
keyArgsSchema.jsonSchema,
|
|
307
306
|
joystickArgsSchema.jsonSchema,
|
|
308
307
|
],
|
|
309
|
-
|
|
310
|
-
},
|
|
308
|
+
}),
|
|
311
309
|
operationPlatforms: { joystick: ["vice"] },
|
|
312
310
|
tags: ["input", "keyboard", "joystick"],
|
|
313
311
|
examples: [
|
package/dist/tools/memory.js
CHANGED
|
@@ -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 "./
|
|
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;
|
package/dist/tools/responses.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export function textResult(text, metadata) {
|
|
2
|
-
|
|
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"
|