c64-debug-mcp 1.0.2 → 1.0.6
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/http.cjs +238 -130
- package/dist/http.js +238 -130
- package/dist/stdio.cjs +238 -130
- package/dist/stdio.js +238 -130
- package/package.json +1 -1
package/dist/http.cjs
CHANGED
|
@@ -391,10 +391,15 @@ function publicMessageFor(error) {
|
|
|
391
391
|
case "program_file_missing":
|
|
392
392
|
case "program_file_invalid":
|
|
393
393
|
return error.message;
|
|
394
|
+
case "binary_not_found":
|
|
395
|
+
case "spawn_failed":
|
|
396
|
+
case "emulator_crashed_on_startup":
|
|
397
|
+
return error.message;
|
|
394
398
|
case "port_allocation_failed":
|
|
395
399
|
case "port_in_use":
|
|
396
|
-
case "monitor_timeout":
|
|
397
400
|
return "The server could not start a usable emulator session. Check the emulator configuration and try again.";
|
|
401
|
+
case "monitor_timeout":
|
|
402
|
+
return error.message;
|
|
398
403
|
case "not_connected":
|
|
399
404
|
case "connection_closed":
|
|
400
405
|
case "socket_write_failed":
|
|
@@ -425,14 +430,21 @@ function publicMessageFor(error) {
|
|
|
425
430
|
}
|
|
426
431
|
}
|
|
427
432
|
function publicDetailsFor(error) {
|
|
428
|
-
switch (error.
|
|
429
|
-
case "
|
|
430
|
-
case "
|
|
431
|
-
case "
|
|
432
|
-
case "io":
|
|
433
|
+
switch (error.code) {
|
|
434
|
+
case "binary_not_found":
|
|
435
|
+
case "spawn_failed":
|
|
436
|
+
case "emulator_crashed_on_startup":
|
|
433
437
|
return error.details;
|
|
434
438
|
default:
|
|
435
|
-
|
|
439
|
+
switch (error.category) {
|
|
440
|
+
case "validation":
|
|
441
|
+
case "session_state":
|
|
442
|
+
case "unsupported":
|
|
443
|
+
case "io":
|
|
444
|
+
return error.details;
|
|
445
|
+
default:
|
|
446
|
+
return void 0;
|
|
447
|
+
}
|
|
436
448
|
}
|
|
437
449
|
}
|
|
438
450
|
function normalizeToolError(error) {
|
|
@@ -1550,6 +1562,10 @@ var ViceSession = class {
|
|
|
1550
1562
|
this.#syncMonitorRuntimeState();
|
|
1551
1563
|
});
|
|
1552
1564
|
}
|
|
1565
|
+
async getSessionState() {
|
|
1566
|
+
await this.#ensureReady();
|
|
1567
|
+
return this.snapshot();
|
|
1568
|
+
}
|
|
1553
1569
|
snapshot() {
|
|
1554
1570
|
return {
|
|
1555
1571
|
transportState: this.#transportState,
|
|
@@ -1786,6 +1802,17 @@ var ViceSession = class {
|
|
|
1786
1802
|
async continueExecution(waitUntilRunningStable = false) {
|
|
1787
1803
|
return this.#withExecutionLock(async () => {
|
|
1788
1804
|
await this.#ensureReady();
|
|
1805
|
+
if (this.#executionState === "running") {
|
|
1806
|
+
const runtime2 = this.#client.runtimeState();
|
|
1807
|
+
const debugState2 = this.#lastRegisters == null ? await this.#readDebugState() : this.#buildDebugState(this.#lastRegisters);
|
|
1808
|
+
return {
|
|
1809
|
+
executionState: "running",
|
|
1810
|
+
lastStopReason: this.#lastStopReason,
|
|
1811
|
+
programCounter: runtime2.programCounter ?? debugState2.programCounter,
|
|
1812
|
+
registers: debugState2.registers,
|
|
1813
|
+
warnings: []
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1789
1816
|
if (this.#executionState !== "stopped") {
|
|
1790
1817
|
debuggerNotPausedError("execute resume", {
|
|
1791
1818
|
executionState: this.#executionState,
|
|
@@ -1964,7 +1991,6 @@ var ViceSession = class {
|
|
|
1964
1991
|
async programLoad(options) {
|
|
1965
1992
|
const filePath = import_node_path.default.resolve(options.filePath);
|
|
1966
1993
|
await this.#assertReadableProgramFile(filePath);
|
|
1967
|
-
await this.#ensureRunning("program_load");
|
|
1968
1994
|
this.#explicitPauseActive = false;
|
|
1969
1995
|
const result = await this.autostartProgram(filePath, options.autoStart ?? true, options.fileIndex ?? 0);
|
|
1970
1996
|
return {
|
|
@@ -2215,134 +2241,137 @@ var ViceSession = class {
|
|
|
2215
2241
|
}
|
|
2216
2242
|
}
|
|
2217
2243
|
async writeText(text) {
|
|
2218
|
-
await this.#
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2244
|
+
return await this.#withAutoResumeForInput("write_text", async () => {
|
|
2245
|
+
const encoded = decodeWriteTextToPetscii(text);
|
|
2246
|
+
if (encoded.length > MAX_WRITE_TEXT_BYTES) {
|
|
2247
|
+
validationError("write_text exceeds the maximum allowed byte length for one request", {
|
|
2248
|
+
length: encoded.length,
|
|
2249
|
+
max: MAX_WRITE_TEXT_BYTES
|
|
2250
|
+
});
|
|
2251
|
+
}
|
|
2252
|
+
this.#writeProcessLogLine(`[tx] write_text length=${encoded.length} text=${JSON.stringify(text)}`);
|
|
2253
|
+
await this.#client.sendKeys(Buffer.from(encoded).toString("binary"));
|
|
2254
|
+
await this.#settleInputState("write_text", "running");
|
|
2255
|
+
return {
|
|
2256
|
+
sent: true,
|
|
2257
|
+
length: encoded.length
|
|
2258
|
+
};
|
|
2259
|
+
});
|
|
2233
2260
|
}
|
|
2234
2261
|
async keyboardInput(action, keys, durationMs) {
|
|
2235
|
-
await this.#
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
}
|
|
2239
|
-
const resolvedKeys = keys.map((key) => resolveKeyboardInputKey(key));
|
|
2240
|
-
const normalizedKeys = resolvedKeys.map((key) => key.canonical);
|
|
2241
|
-
this.#writeProcessLogLine(
|
|
2242
|
-
`[tx] keyboard_input action=${action} keys=${normalizedKeys.join(",")}${durationMs == null ? "" : ` durationMs=${durationMs}`}`
|
|
2243
|
-
);
|
|
2244
|
-
switch (action) {
|
|
2245
|
-
case "tap": {
|
|
2246
|
-
const duration = clampTapDuration(durationMs);
|
|
2247
|
-
const bytes = Uint8Array.from(resolvedKeys.flatMap((key) => Array.from(key.bytes)));
|
|
2248
|
-
await this.#client.sendKeys(Buffer.from(bytes).toString("binary"));
|
|
2249
|
-
await this.#settleInputState("keyboard_input", "running");
|
|
2250
|
-
await sleep(duration);
|
|
2251
|
-
return {
|
|
2252
|
-
action,
|
|
2253
|
-
keys: normalizedKeys,
|
|
2254
|
-
applied: true,
|
|
2255
|
-
held: false,
|
|
2256
|
-
mode: "buffered_text"
|
|
2257
|
-
};
|
|
2262
|
+
return await this.#withAutoResumeForInput("keyboard_input", async () => {
|
|
2263
|
+
if (!Array.isArray(keys) || keys.length === 0 || keys.length > 4) {
|
|
2264
|
+
validationError("keyboard_input requires between 1 and 4 keys", { keys });
|
|
2258
2265
|
}
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
}
|
|
2266
|
+
const resolvedKeys = keys.map((key) => resolveKeyboardInputKey(key));
|
|
2267
|
+
const normalizedKeys = resolvedKeys.map((key) => key.canonical);
|
|
2268
|
+
this.#writeProcessLogLine(
|
|
2269
|
+
`[tx] keyboard_input action=${action} keys=${normalizedKeys.join(",")}${durationMs == null ? "" : ` durationMs=${durationMs}`}`
|
|
2270
|
+
);
|
|
2271
|
+
switch (action) {
|
|
2272
|
+
case "tap": {
|
|
2273
|
+
const duration = clampTapDuration(durationMs);
|
|
2274
|
+
const bytes = Uint8Array.from(resolvedKeys.flatMap((key) => Array.from(key.bytes)));
|
|
2275
|
+
await this.#client.sendKeys(Buffer.from(bytes).toString("binary"));
|
|
2276
|
+
await this.#settleInputState("keyboard_input", "running");
|
|
2277
|
+
await sleep(duration);
|
|
2278
|
+
return {
|
|
2279
|
+
action,
|
|
2280
|
+
keys: normalizedKeys,
|
|
2281
|
+
applied: true,
|
|
2282
|
+
held: false,
|
|
2283
|
+
mode: "buffered_text"
|
|
2284
|
+
};
|
|
2279
2285
|
}
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2286
|
+
case "press": {
|
|
2287
|
+
const singleByteKeys = resolvedKeys.map((key) => {
|
|
2288
|
+
if (key.bytes.length !== 1) {
|
|
2289
|
+
unsupportedError("keyboard_input press/release only supports keys that map to a single PETSCII byte.", {
|
|
2290
|
+
key: key.canonical
|
|
2291
|
+
});
|
|
2292
|
+
}
|
|
2293
|
+
return key.bytes[0];
|
|
2294
|
+
});
|
|
2295
|
+
for (let index = 0; index < normalizedKeys.length; index += 1) {
|
|
2296
|
+
const heldKey = normalizedKeys[index];
|
|
2297
|
+
const byte = singleByteKeys[index];
|
|
2298
|
+
if (!this.#heldKeyboardIntervals.has(heldKey)) {
|
|
2299
|
+
await this.#client.sendKeys(Buffer.from([byte]).toString("binary"));
|
|
2300
|
+
await this.#settleInputState("keyboard_input", "running");
|
|
2301
|
+
const interval = setInterval(() => {
|
|
2302
|
+
void this.#client.sendKeys(Buffer.from([byte]).toString("binary")).then(() => this.#settleInputState("keyboard_input", "running")).catch(() => void 0);
|
|
2303
|
+
}, DEFAULT_KEYBOARD_REPEAT_MS);
|
|
2304
|
+
this.#heldKeyboardIntervals.set(heldKey, interval);
|
|
2305
|
+
}
|
|
2294
2306
|
}
|
|
2307
|
+
return {
|
|
2308
|
+
action,
|
|
2309
|
+
keys: normalizedKeys,
|
|
2310
|
+
applied: true,
|
|
2311
|
+
held: true,
|
|
2312
|
+
mode: "buffered_text_repeat"
|
|
2313
|
+
};
|
|
2295
2314
|
}
|
|
2296
|
-
|
|
2297
|
-
const
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2315
|
+
case "release": {
|
|
2316
|
+
for (const key of resolvedKeys) {
|
|
2317
|
+
if (key.bytes.length !== 1) {
|
|
2318
|
+
unsupportedError("keyboard_input press/release only supports keys that map to a single PETSCII byte.", {
|
|
2319
|
+
key: key.canonical
|
|
2320
|
+
});
|
|
2321
|
+
}
|
|
2301
2322
|
}
|
|
2323
|
+
for (const heldKey of normalizedKeys) {
|
|
2324
|
+
const interval = this.#heldKeyboardIntervals.get(heldKey);
|
|
2325
|
+
if (interval) {
|
|
2326
|
+
clearInterval(interval);
|
|
2327
|
+
this.#heldKeyboardIntervals.delete(heldKey);
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
return {
|
|
2331
|
+
action,
|
|
2332
|
+
keys: normalizedKeys,
|
|
2333
|
+
applied: true,
|
|
2334
|
+
held: false,
|
|
2335
|
+
mode: "buffered_text_repeat"
|
|
2336
|
+
};
|
|
2302
2337
|
}
|
|
2303
|
-
return {
|
|
2304
|
-
action,
|
|
2305
|
-
keys: normalizedKeys,
|
|
2306
|
-
applied: true,
|
|
2307
|
-
held: false,
|
|
2308
|
-
mode: "buffered_text_repeat"
|
|
2309
|
-
};
|
|
2310
2338
|
}
|
|
2311
|
-
}
|
|
2339
|
+
});
|
|
2312
2340
|
}
|
|
2313
2341
|
async joystickInput(port2, action, control, durationMs) {
|
|
2314
|
-
await this.#
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
}
|
|
2320
|
-
this.#writeProcessLogLine(
|
|
2321
|
-
`[tx] joystick_input port=${port2} action=${action} control=${control}${durationMs == null ? "" : ` durationMs=${durationMs}`}`
|
|
2322
|
-
);
|
|
2323
|
-
switch (action) {
|
|
2324
|
-
case "tap": {
|
|
2325
|
-
const duration = clampTapDuration(durationMs);
|
|
2326
|
-
await this.#applyJoystickMask(port2, this.#getJoystickMask(port2) & ~bit);
|
|
2327
|
-
await sleep(duration);
|
|
2328
|
-
await this.#applyJoystickMask(port2, this.#getJoystickMask(port2) | bit);
|
|
2329
|
-
break;
|
|
2342
|
+
return await this.#withAutoResumeForInput("joystick_input", async () => {
|
|
2343
|
+
const previousExecutionState = this.#executionState;
|
|
2344
|
+
const bit = JOYSTICK_CONTROL_BITS[control];
|
|
2345
|
+
if (bit == null) {
|
|
2346
|
+
validationError("Unsupported joystick control", { control });
|
|
2330
2347
|
}
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2348
|
+
this.#writeProcessLogLine(
|
|
2349
|
+
`[tx] joystick_input port=${port2} action=${action} control=${control}${durationMs == null ? "" : ` durationMs=${durationMs}`}`
|
|
2350
|
+
);
|
|
2351
|
+
switch (action) {
|
|
2352
|
+
case "tap": {
|
|
2353
|
+
const duration = clampTapDuration(durationMs);
|
|
2354
|
+
await this.#applyJoystickMask(port2, this.#getJoystickMask(port2) & ~bit);
|
|
2355
|
+
await sleep(duration);
|
|
2356
|
+
await this.#applyJoystickMask(port2, this.#getJoystickMask(port2) | bit);
|
|
2357
|
+
break;
|
|
2358
|
+
}
|
|
2359
|
+
case "press":
|
|
2360
|
+
await this.#applyJoystickMask(port2, this.#getJoystickMask(port2) & ~bit);
|
|
2361
|
+
break;
|
|
2362
|
+
case "release":
|
|
2363
|
+
await this.#applyJoystickMask(port2, this.#getJoystickMask(port2) | bit);
|
|
2364
|
+
break;
|
|
2365
|
+
}
|
|
2366
|
+
await this.#settleInputState("joystick_input", previousExecutionState);
|
|
2367
|
+
return {
|
|
2368
|
+
port: port2,
|
|
2369
|
+
action,
|
|
2370
|
+
control,
|
|
2371
|
+
applied: true,
|
|
2372
|
+
state: this.#describeJoystickState(port2)
|
|
2373
|
+
};
|
|
2374
|
+
});
|
|
2346
2375
|
}
|
|
2347
2376
|
async waitForState(targetState, timeoutMs = 5e3, stableMs = targetState === "running" ? INPUT_RUNNING_STABLE_MS : 0) {
|
|
2348
2377
|
await this.#ensureReady();
|
|
@@ -2405,6 +2434,27 @@ var ViceSession = class {
|
|
|
2405
2434
|
});
|
|
2406
2435
|
}
|
|
2407
2436
|
}
|
|
2437
|
+
async #withAutoResumeForInput(commandName, operation) {
|
|
2438
|
+
await this.#ensureReady();
|
|
2439
|
+
this.#syncMonitorRuntimeState();
|
|
2440
|
+
const wasRunning = this.#executionState === "running";
|
|
2441
|
+
const wasPaused = this.#explicitPauseActive;
|
|
2442
|
+
if (!wasRunning) {
|
|
2443
|
+
this.#writeProcessLogLine(`[${commandName}] auto-resuming for input operation`);
|
|
2444
|
+
await this.#client.continueExecution();
|
|
2445
|
+
await this.waitForState("running", 5e3, INPUT_RUNNING_STABLE_MS);
|
|
2446
|
+
}
|
|
2447
|
+
try {
|
|
2448
|
+
return await operation();
|
|
2449
|
+
} finally {
|
|
2450
|
+
if (!wasRunning && wasPaused) {
|
|
2451
|
+
this.#writeProcessLogLine(`[${commandName}] restoring paused state after input`);
|
|
2452
|
+
await this.#client.ping();
|
|
2453
|
+
await this.waitForState("stopped", 5e3, 0);
|
|
2454
|
+
this.#explicitPauseActive = true;
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2408
2458
|
async #ensureHealthyConnection() {
|
|
2409
2459
|
if (this.#recoveryPromise) {
|
|
2410
2460
|
await this.#recoveryPromise;
|
|
@@ -2453,6 +2503,16 @@ var ViceSession = class {
|
|
|
2453
2503
|
const port2 = await this.#portAllocator.allocate();
|
|
2454
2504
|
await this.#portAllocator.ensureFree(port2, host2);
|
|
2455
2505
|
const binary = config.binaryPath ?? DEFAULT_C64_BINARY;
|
|
2506
|
+
const binaryCheck = await checkBinaryExists(binary);
|
|
2507
|
+
if (!binaryCheck.exists) {
|
|
2508
|
+
throw new ViceMcpError(
|
|
2509
|
+
"binary_not_found",
|
|
2510
|
+
`VICE emulator binary '${binary}' not found. Please install VICE or configure the correct path using the 'binaryPath' setting.`,
|
|
2511
|
+
"process_launch",
|
|
2512
|
+
false,
|
|
2513
|
+
{ binary, searchedPath: process.env.PATH }
|
|
2514
|
+
);
|
|
2515
|
+
}
|
|
2456
2516
|
const args = ["-autostartprgmode", "1", "-binarymonitor", "-binarymonitoraddress", `${host2}:${port2}`];
|
|
2457
2517
|
if (config.arguments) {
|
|
2458
2518
|
args.push(...splitCommandLine(config.arguments));
|
|
@@ -2470,11 +2530,15 @@ var ViceSession = class {
|
|
|
2470
2530
|
this.#lastRuntimeEventType = "unknown";
|
|
2471
2531
|
this.#lastRuntimeProgramCounter = null;
|
|
2472
2532
|
const env = await buildViceLaunchEnv();
|
|
2533
|
+
let spawnError = void 0;
|
|
2473
2534
|
const child = (0, import_node_child_process.spawn)(binary, args, {
|
|
2474
2535
|
cwd: config.workingDirectory ? import_node_path.default.resolve(config.workingDirectory) : void 0,
|
|
2475
2536
|
env,
|
|
2476
2537
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2477
2538
|
});
|
|
2539
|
+
child.once("error", (err) => {
|
|
2540
|
+
spawnError = err;
|
|
2541
|
+
});
|
|
2478
2542
|
this.#process = child;
|
|
2479
2543
|
this.#attachProcessLogging(child, binary, args);
|
|
2480
2544
|
this.#bindProcessLifecycle(child);
|
|
@@ -2482,6 +2546,16 @@ var ViceSession = class {
|
|
|
2482
2546
|
this.#transportState = "waiting_for_monitor";
|
|
2483
2547
|
try {
|
|
2484
2548
|
await waitForMonitor(host2, port2, 5e3);
|
|
2549
|
+
if (spawnError !== void 0) {
|
|
2550
|
+
const err = spawnError;
|
|
2551
|
+
throw new ViceMcpError(
|
|
2552
|
+
"spawn_failed",
|
|
2553
|
+
`Failed to start VICE emulator '${binary}': ${err.message}`,
|
|
2554
|
+
"process_launch",
|
|
2555
|
+
false,
|
|
2556
|
+
{ binary, error: err.message, resolvedPath: binaryCheck.path }
|
|
2557
|
+
);
|
|
2558
|
+
}
|
|
2485
2559
|
await this.#client.connect(host2, port2);
|
|
2486
2560
|
this.#transportState = "connected";
|
|
2487
2561
|
this.#connectedSince = nowIso();
|
|
@@ -2494,6 +2568,19 @@ var ViceSession = class {
|
|
|
2494
2568
|
} catch (error) {
|
|
2495
2569
|
this.#processState = "crashed";
|
|
2496
2570
|
this.#transportState = "faulted";
|
|
2571
|
+
if (error instanceof ViceMcpError && error.code === "monitor_timeout" && spawnError !== void 0) {
|
|
2572
|
+
const err = spawnError;
|
|
2573
|
+
const enhancedError = new ViceMcpError(
|
|
2574
|
+
"emulator_crashed_on_startup",
|
|
2575
|
+
`VICE emulator '${binary}' crashed during startup: ${err.message}`,
|
|
2576
|
+
"process_launch",
|
|
2577
|
+
false,
|
|
2578
|
+
{ binary, error: err.message, resolvedPath: binaryCheck.path }
|
|
2579
|
+
);
|
|
2580
|
+
this.#warnings = [...this.#warnings.filter((warning) => warning.code !== "launch_failed"), makeWarning(enhancedError.message, "launch_failed")];
|
|
2581
|
+
await this.#stopManagedProcess(true);
|
|
2582
|
+
throw enhancedError;
|
|
2583
|
+
}
|
|
2497
2584
|
this.#warnings = [...this.#warnings.filter((warning) => warning.code !== "launch_failed"), makeWarning(String(error.message ?? error), "launch_failed")];
|
|
2498
2585
|
await this.#stopManagedProcess(true);
|
|
2499
2586
|
throw error;
|
|
@@ -3234,6 +3321,27 @@ function splitCommandLine(input) {
|
|
|
3234
3321
|
}
|
|
3235
3322
|
return result;
|
|
3236
3323
|
}
|
|
3324
|
+
async function checkBinaryExists(binaryPath) {
|
|
3325
|
+
if (import_node_path.default.isAbsolute(binaryPath)) {
|
|
3326
|
+
try {
|
|
3327
|
+
await import_promises.default.access(binaryPath, import_promises.default.constants.X_OK);
|
|
3328
|
+
return { exists: true, path: binaryPath };
|
|
3329
|
+
} catch {
|
|
3330
|
+
return { exists: false };
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
const pathEnv = process.env.PATH || "";
|
|
3334
|
+
const pathDirs = pathEnv.split(import_node_path.default.delimiter);
|
|
3335
|
+
for (const dir of pathDirs) {
|
|
3336
|
+
const fullPath = import_node_path.default.join(dir, binaryPath);
|
|
3337
|
+
try {
|
|
3338
|
+
await import_promises.default.access(fullPath, import_promises.default.constants.X_OK);
|
|
3339
|
+
return { exists: true, path: fullPath };
|
|
3340
|
+
} catch {
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
return { exists: false };
|
|
3344
|
+
}
|
|
3237
3345
|
async function waitForMonitor(host2, port2, timeoutMs) {
|
|
3238
3346
|
const deadline = Date.now() + timeoutMs;
|
|
3239
3347
|
while (Date.now() < deadline) {
|
|
@@ -3242,7 +3350,7 @@ async function waitForMonitor(host2, port2, timeoutMs) {
|
|
|
3242
3350
|
}
|
|
3243
3351
|
await sleep(100);
|
|
3244
3352
|
}
|
|
3245
|
-
throw new ViceMcpError("monitor_timeout", `Debugger monitor did not open on ${host2}:${port2}
|
|
3353
|
+
throw new ViceMcpError("monitor_timeout", `Debugger monitor did not open on ${host2}:${port2}. The emulator may have failed to start or crashed during startup.`, "timeout", true, {
|
|
3246
3354
|
host: host2,
|
|
3247
3355
|
port: port2
|
|
3248
3356
|
});
|
|
@@ -3307,7 +3415,7 @@ var getSessionStateTool = createViceTool({
|
|
|
3307
3415
|
description: "Returns emulator session state including transport/process status, auto-resume state, and the most recent hit checkpoint.",
|
|
3308
3416
|
inputSchema: noInputSchema,
|
|
3309
3417
|
dataSchema: sessionStateResultSchema,
|
|
3310
|
-
execute: async () => c64Session.
|
|
3418
|
+
execute: async () => await c64Session.getSessionState()
|
|
3311
3419
|
});
|
|
3312
3420
|
var getRegistersTool = createViceTool({
|
|
3313
3421
|
id: "get_registers",
|
|
@@ -3373,7 +3481,7 @@ var writeMemoryTool = createViceTool({
|
|
|
3373
3481
|
});
|
|
3374
3482
|
var executeTool = createViceTool({
|
|
3375
3483
|
id: "execute",
|
|
3376
|
-
description: "Controls execution with pause, resume, step, step_over, step_out, or reset.
|
|
3484
|
+
description: "Controls execution with pause, resume, step, step_over, step_out, or reset. Pause and resume are idempotent (safe to call multiple times).",
|
|
3377
3485
|
inputSchema: import_zod4.z.object({
|
|
3378
3486
|
action: import_zod4.z.enum(["pause", "resume", "step", "step_over", "step_out", "reset"]),
|
|
3379
3487
|
count: import_zod4.z.number().int().positive().default(1).describe("Instruction count for step and step_over actions"),
|
|
@@ -3495,7 +3603,7 @@ var getDisplayTextTool = createViceTool({
|
|
|
3495
3603
|
});
|
|
3496
3604
|
var writeTextTool = createViceTool({
|
|
3497
3605
|
id: "write_text",
|
|
3498
|
-
description:
|
|
3606
|
+
description: "Types text into the C64. Automatically resumes if stopped and restores pause state after. Supports escaped characters and PETSCII brace tokens like {RETURN}, {CLR}, {HOME}, {PI}, and color names. Limit 64 bytes per request.",
|
|
3499
3607
|
inputSchema: import_zod4.z.object({
|
|
3500
3608
|
text: import_zod4.z.string()
|
|
3501
3609
|
}),
|
|
@@ -3507,7 +3615,7 @@ var writeTextTool = createViceTool({
|
|
|
3507
3615
|
});
|
|
3508
3616
|
var keyboardInputTool = createViceTool({
|
|
3509
3617
|
id: "keyboard_input",
|
|
3510
|
-
description:
|
|
3618
|
+
description: "Sends one to four keys or PETSCII tokens to the C64. Automatically resumes if stopped and restores pause state after. Use for key presses, releases, and taps.",
|
|
3511
3619
|
inputSchema: import_zod4.z.object({
|
|
3512
3620
|
action: inputActionSchema.describe("Use tap for a single key event or press/release for repeated buffered input"),
|
|
3513
3621
|
keys: import_zod4.z.array(import_zod4.z.string().min(1)).min(1).max(4).describe("One to four literal keys or PETSCII token names such as RETURN, CLR, HOME, PI, LEFT, RED, or F1"),
|
|
@@ -3518,7 +3626,7 @@ var keyboardInputTool = createViceTool({
|
|
|
3518
3626
|
});
|
|
3519
3627
|
var joystickInputTool = createViceTool({
|
|
3520
3628
|
id: "joystick_input",
|
|
3521
|
-
description:
|
|
3629
|
+
description: "Sends joystick input to C64 joystick port 1 or 2. Automatically resumes if stopped and restores pause state after.",
|
|
3522
3630
|
inputSchema: import_zod4.z.object({
|
|
3523
3631
|
port: joystickPortSchema.describe("Joystick port number"),
|
|
3524
3632
|
action: inputActionSchema.describe("Joystick action to apply"),
|