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/stdio.js
CHANGED
|
@@ -365,10 +365,15 @@ function publicMessageFor(error) {
|
|
|
365
365
|
case "program_file_missing":
|
|
366
366
|
case "program_file_invalid":
|
|
367
367
|
return error.message;
|
|
368
|
+
case "binary_not_found":
|
|
369
|
+
case "spawn_failed":
|
|
370
|
+
case "emulator_crashed_on_startup":
|
|
371
|
+
return error.message;
|
|
368
372
|
case "port_allocation_failed":
|
|
369
373
|
case "port_in_use":
|
|
370
|
-
case "monitor_timeout":
|
|
371
374
|
return "The server could not start a usable emulator session. Check the emulator configuration and try again.";
|
|
375
|
+
case "monitor_timeout":
|
|
376
|
+
return error.message;
|
|
372
377
|
case "not_connected":
|
|
373
378
|
case "connection_closed":
|
|
374
379
|
case "socket_write_failed":
|
|
@@ -399,14 +404,21 @@ function publicMessageFor(error) {
|
|
|
399
404
|
}
|
|
400
405
|
}
|
|
401
406
|
function publicDetailsFor(error) {
|
|
402
|
-
switch (error.
|
|
403
|
-
case "
|
|
404
|
-
case "
|
|
405
|
-
case "
|
|
406
|
-
case "io":
|
|
407
|
+
switch (error.code) {
|
|
408
|
+
case "binary_not_found":
|
|
409
|
+
case "spawn_failed":
|
|
410
|
+
case "emulator_crashed_on_startup":
|
|
407
411
|
return error.details;
|
|
408
412
|
default:
|
|
409
|
-
|
|
413
|
+
switch (error.category) {
|
|
414
|
+
case "validation":
|
|
415
|
+
case "session_state":
|
|
416
|
+
case "unsupported":
|
|
417
|
+
case "io":
|
|
418
|
+
return error.details;
|
|
419
|
+
default:
|
|
420
|
+
return void 0;
|
|
421
|
+
}
|
|
410
422
|
}
|
|
411
423
|
}
|
|
412
424
|
function normalizeToolError(error) {
|
|
@@ -1524,6 +1536,10 @@ var ViceSession = class {
|
|
|
1524
1536
|
this.#syncMonitorRuntimeState();
|
|
1525
1537
|
});
|
|
1526
1538
|
}
|
|
1539
|
+
async getSessionState() {
|
|
1540
|
+
await this.#ensureReady();
|
|
1541
|
+
return this.snapshot();
|
|
1542
|
+
}
|
|
1527
1543
|
snapshot() {
|
|
1528
1544
|
return {
|
|
1529
1545
|
transportState: this.#transportState,
|
|
@@ -1760,6 +1776,17 @@ var ViceSession = class {
|
|
|
1760
1776
|
async continueExecution(waitUntilRunningStable = false) {
|
|
1761
1777
|
return this.#withExecutionLock(async () => {
|
|
1762
1778
|
await this.#ensureReady();
|
|
1779
|
+
if (this.#executionState === "running") {
|
|
1780
|
+
const runtime2 = this.#client.runtimeState();
|
|
1781
|
+
const debugState2 = this.#lastRegisters == null ? await this.#readDebugState() : this.#buildDebugState(this.#lastRegisters);
|
|
1782
|
+
return {
|
|
1783
|
+
executionState: "running",
|
|
1784
|
+
lastStopReason: this.#lastStopReason,
|
|
1785
|
+
programCounter: runtime2.programCounter ?? debugState2.programCounter,
|
|
1786
|
+
registers: debugState2.registers,
|
|
1787
|
+
warnings: []
|
|
1788
|
+
};
|
|
1789
|
+
}
|
|
1763
1790
|
if (this.#executionState !== "stopped") {
|
|
1764
1791
|
debuggerNotPausedError("execute resume", {
|
|
1765
1792
|
executionState: this.#executionState,
|
|
@@ -1938,7 +1965,6 @@ var ViceSession = class {
|
|
|
1938
1965
|
async programLoad(options) {
|
|
1939
1966
|
const filePath = path.resolve(options.filePath);
|
|
1940
1967
|
await this.#assertReadableProgramFile(filePath);
|
|
1941
|
-
await this.#ensureRunning("program_load");
|
|
1942
1968
|
this.#explicitPauseActive = false;
|
|
1943
1969
|
const result = await this.autostartProgram(filePath, options.autoStart ?? true, options.fileIndex ?? 0);
|
|
1944
1970
|
return {
|
|
@@ -2189,134 +2215,137 @@ var ViceSession = class {
|
|
|
2189
2215
|
}
|
|
2190
2216
|
}
|
|
2191
2217
|
async writeText(text) {
|
|
2192
|
-
await this.#
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2218
|
+
return await this.#withAutoResumeForInput("write_text", async () => {
|
|
2219
|
+
const encoded = decodeWriteTextToPetscii(text);
|
|
2220
|
+
if (encoded.length > MAX_WRITE_TEXT_BYTES) {
|
|
2221
|
+
validationError("write_text exceeds the maximum allowed byte length for one request", {
|
|
2222
|
+
length: encoded.length,
|
|
2223
|
+
max: MAX_WRITE_TEXT_BYTES
|
|
2224
|
+
});
|
|
2225
|
+
}
|
|
2226
|
+
this.#writeProcessLogLine(`[tx] write_text length=${encoded.length} text=${JSON.stringify(text)}`);
|
|
2227
|
+
await this.#client.sendKeys(Buffer.from(encoded).toString("binary"));
|
|
2228
|
+
await this.#settleInputState("write_text", "running");
|
|
2229
|
+
return {
|
|
2230
|
+
sent: true,
|
|
2231
|
+
length: encoded.length
|
|
2232
|
+
};
|
|
2233
|
+
});
|
|
2207
2234
|
}
|
|
2208
2235
|
async keyboardInput(action, keys, durationMs) {
|
|
2209
|
-
await this.#
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
}
|
|
2213
|
-
const resolvedKeys = keys.map((key) => resolveKeyboardInputKey(key));
|
|
2214
|
-
const normalizedKeys = resolvedKeys.map((key) => key.canonical);
|
|
2215
|
-
this.#writeProcessLogLine(
|
|
2216
|
-
`[tx] keyboard_input action=${action} keys=${normalizedKeys.join(",")}${durationMs == null ? "" : ` durationMs=${durationMs}`}`
|
|
2217
|
-
);
|
|
2218
|
-
switch (action) {
|
|
2219
|
-
case "tap": {
|
|
2220
|
-
const duration = clampTapDuration(durationMs);
|
|
2221
|
-
const bytes = Uint8Array.from(resolvedKeys.flatMap((key) => Array.from(key.bytes)));
|
|
2222
|
-
await this.#client.sendKeys(Buffer.from(bytes).toString("binary"));
|
|
2223
|
-
await this.#settleInputState("keyboard_input", "running");
|
|
2224
|
-
await sleep(duration);
|
|
2225
|
-
return {
|
|
2226
|
-
action,
|
|
2227
|
-
keys: normalizedKeys,
|
|
2228
|
-
applied: true,
|
|
2229
|
-
held: false,
|
|
2230
|
-
mode: "buffered_text"
|
|
2231
|
-
};
|
|
2236
|
+
return await this.#withAutoResumeForInput("keyboard_input", async () => {
|
|
2237
|
+
if (!Array.isArray(keys) || keys.length === 0 || keys.length > 4) {
|
|
2238
|
+
validationError("keyboard_input requires between 1 and 4 keys", { keys });
|
|
2232
2239
|
}
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
}
|
|
2240
|
+
const resolvedKeys = keys.map((key) => resolveKeyboardInputKey(key));
|
|
2241
|
+
const normalizedKeys = resolvedKeys.map((key) => key.canonical);
|
|
2242
|
+
this.#writeProcessLogLine(
|
|
2243
|
+
`[tx] keyboard_input action=${action} keys=${normalizedKeys.join(",")}${durationMs == null ? "" : ` durationMs=${durationMs}`}`
|
|
2244
|
+
);
|
|
2245
|
+
switch (action) {
|
|
2246
|
+
case "tap": {
|
|
2247
|
+
const duration = clampTapDuration(durationMs);
|
|
2248
|
+
const bytes = Uint8Array.from(resolvedKeys.flatMap((key) => Array.from(key.bytes)));
|
|
2249
|
+
await this.#client.sendKeys(Buffer.from(bytes).toString("binary"));
|
|
2250
|
+
await this.#settleInputState("keyboard_input", "running");
|
|
2251
|
+
await sleep(duration);
|
|
2252
|
+
return {
|
|
2253
|
+
action,
|
|
2254
|
+
keys: normalizedKeys,
|
|
2255
|
+
applied: true,
|
|
2256
|
+
held: false,
|
|
2257
|
+
mode: "buffered_text"
|
|
2258
|
+
};
|
|
2253
2259
|
}
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2260
|
+
case "press": {
|
|
2261
|
+
const singleByteKeys = resolvedKeys.map((key) => {
|
|
2262
|
+
if (key.bytes.length !== 1) {
|
|
2263
|
+
unsupportedError("keyboard_input press/release only supports keys that map to a single PETSCII byte.", {
|
|
2264
|
+
key: key.canonical
|
|
2265
|
+
});
|
|
2266
|
+
}
|
|
2267
|
+
return key.bytes[0];
|
|
2268
|
+
});
|
|
2269
|
+
for (let index = 0; index < normalizedKeys.length; index += 1) {
|
|
2270
|
+
const heldKey = normalizedKeys[index];
|
|
2271
|
+
const byte = singleByteKeys[index];
|
|
2272
|
+
if (!this.#heldKeyboardIntervals.has(heldKey)) {
|
|
2273
|
+
await this.#client.sendKeys(Buffer.from([byte]).toString("binary"));
|
|
2274
|
+
await this.#settleInputState("keyboard_input", "running");
|
|
2275
|
+
const interval = setInterval(() => {
|
|
2276
|
+
void this.#client.sendKeys(Buffer.from([byte]).toString("binary")).then(() => this.#settleInputState("keyboard_input", "running")).catch(() => void 0);
|
|
2277
|
+
}, DEFAULT_KEYBOARD_REPEAT_MS);
|
|
2278
|
+
this.#heldKeyboardIntervals.set(heldKey, interval);
|
|
2279
|
+
}
|
|
2268
2280
|
}
|
|
2281
|
+
return {
|
|
2282
|
+
action,
|
|
2283
|
+
keys: normalizedKeys,
|
|
2284
|
+
applied: true,
|
|
2285
|
+
held: true,
|
|
2286
|
+
mode: "buffered_text_repeat"
|
|
2287
|
+
};
|
|
2269
2288
|
}
|
|
2270
|
-
|
|
2271
|
-
const
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2289
|
+
case "release": {
|
|
2290
|
+
for (const key of resolvedKeys) {
|
|
2291
|
+
if (key.bytes.length !== 1) {
|
|
2292
|
+
unsupportedError("keyboard_input press/release only supports keys that map to a single PETSCII byte.", {
|
|
2293
|
+
key: key.canonical
|
|
2294
|
+
});
|
|
2295
|
+
}
|
|
2275
2296
|
}
|
|
2297
|
+
for (const heldKey of normalizedKeys) {
|
|
2298
|
+
const interval = this.#heldKeyboardIntervals.get(heldKey);
|
|
2299
|
+
if (interval) {
|
|
2300
|
+
clearInterval(interval);
|
|
2301
|
+
this.#heldKeyboardIntervals.delete(heldKey);
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
return {
|
|
2305
|
+
action,
|
|
2306
|
+
keys: normalizedKeys,
|
|
2307
|
+
applied: true,
|
|
2308
|
+
held: false,
|
|
2309
|
+
mode: "buffered_text_repeat"
|
|
2310
|
+
};
|
|
2276
2311
|
}
|
|
2277
|
-
return {
|
|
2278
|
-
action,
|
|
2279
|
-
keys: normalizedKeys,
|
|
2280
|
-
applied: true,
|
|
2281
|
-
held: false,
|
|
2282
|
-
mode: "buffered_text_repeat"
|
|
2283
|
-
};
|
|
2284
2312
|
}
|
|
2285
|
-
}
|
|
2313
|
+
});
|
|
2286
2314
|
}
|
|
2287
2315
|
async joystickInput(port, action, control, durationMs) {
|
|
2288
|
-
await this.#
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
}
|
|
2294
|
-
this.#writeProcessLogLine(
|
|
2295
|
-
`[tx] joystick_input port=${port} action=${action} control=${control}${durationMs == null ? "" : ` durationMs=${durationMs}`}`
|
|
2296
|
-
);
|
|
2297
|
-
switch (action) {
|
|
2298
|
-
case "tap": {
|
|
2299
|
-
const duration = clampTapDuration(durationMs);
|
|
2300
|
-
await this.#applyJoystickMask(port, this.#getJoystickMask(port) & ~bit);
|
|
2301
|
-
await sleep(duration);
|
|
2302
|
-
await this.#applyJoystickMask(port, this.#getJoystickMask(port) | bit);
|
|
2303
|
-
break;
|
|
2316
|
+
return await this.#withAutoResumeForInput("joystick_input", async () => {
|
|
2317
|
+
const previousExecutionState = this.#executionState;
|
|
2318
|
+
const bit = JOYSTICK_CONTROL_BITS[control];
|
|
2319
|
+
if (bit == null) {
|
|
2320
|
+
validationError("Unsupported joystick control", { control });
|
|
2304
2321
|
}
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2322
|
+
this.#writeProcessLogLine(
|
|
2323
|
+
`[tx] joystick_input port=${port} action=${action} control=${control}${durationMs == null ? "" : ` durationMs=${durationMs}`}`
|
|
2324
|
+
);
|
|
2325
|
+
switch (action) {
|
|
2326
|
+
case "tap": {
|
|
2327
|
+
const duration = clampTapDuration(durationMs);
|
|
2328
|
+
await this.#applyJoystickMask(port, this.#getJoystickMask(port) & ~bit);
|
|
2329
|
+
await sleep(duration);
|
|
2330
|
+
await this.#applyJoystickMask(port, this.#getJoystickMask(port) | bit);
|
|
2331
|
+
break;
|
|
2332
|
+
}
|
|
2333
|
+
case "press":
|
|
2334
|
+
await this.#applyJoystickMask(port, this.#getJoystickMask(port) & ~bit);
|
|
2335
|
+
break;
|
|
2336
|
+
case "release":
|
|
2337
|
+
await this.#applyJoystickMask(port, this.#getJoystickMask(port) | bit);
|
|
2338
|
+
break;
|
|
2339
|
+
}
|
|
2340
|
+
await this.#settleInputState("joystick_input", previousExecutionState);
|
|
2341
|
+
return {
|
|
2342
|
+
port,
|
|
2343
|
+
action,
|
|
2344
|
+
control,
|
|
2345
|
+
applied: true,
|
|
2346
|
+
state: this.#describeJoystickState(port)
|
|
2347
|
+
};
|
|
2348
|
+
});
|
|
2320
2349
|
}
|
|
2321
2350
|
async waitForState(targetState, timeoutMs = 5e3, stableMs = targetState === "running" ? INPUT_RUNNING_STABLE_MS : 0) {
|
|
2322
2351
|
await this.#ensureReady();
|
|
@@ -2379,6 +2408,27 @@ var ViceSession = class {
|
|
|
2379
2408
|
});
|
|
2380
2409
|
}
|
|
2381
2410
|
}
|
|
2411
|
+
async #withAutoResumeForInput(commandName, operation) {
|
|
2412
|
+
await this.#ensureReady();
|
|
2413
|
+
this.#syncMonitorRuntimeState();
|
|
2414
|
+
const wasRunning = this.#executionState === "running";
|
|
2415
|
+
const wasPaused = this.#explicitPauseActive;
|
|
2416
|
+
if (!wasRunning) {
|
|
2417
|
+
this.#writeProcessLogLine(`[${commandName}] auto-resuming for input operation`);
|
|
2418
|
+
await this.#client.continueExecution();
|
|
2419
|
+
await this.waitForState("running", 5e3, INPUT_RUNNING_STABLE_MS);
|
|
2420
|
+
}
|
|
2421
|
+
try {
|
|
2422
|
+
return await operation();
|
|
2423
|
+
} finally {
|
|
2424
|
+
if (!wasRunning && wasPaused) {
|
|
2425
|
+
this.#writeProcessLogLine(`[${commandName}] restoring paused state after input`);
|
|
2426
|
+
await this.#client.ping();
|
|
2427
|
+
await this.waitForState("stopped", 5e3, 0);
|
|
2428
|
+
this.#explicitPauseActive = true;
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2382
2432
|
async #ensureHealthyConnection() {
|
|
2383
2433
|
if (this.#recoveryPromise) {
|
|
2384
2434
|
await this.#recoveryPromise;
|
|
@@ -2427,6 +2477,16 @@ var ViceSession = class {
|
|
|
2427
2477
|
const port = await this.#portAllocator.allocate();
|
|
2428
2478
|
await this.#portAllocator.ensureFree(port, host);
|
|
2429
2479
|
const binary = config.binaryPath ?? DEFAULT_C64_BINARY;
|
|
2480
|
+
const binaryCheck = await checkBinaryExists(binary);
|
|
2481
|
+
if (!binaryCheck.exists) {
|
|
2482
|
+
throw new ViceMcpError(
|
|
2483
|
+
"binary_not_found",
|
|
2484
|
+
`VICE emulator binary '${binary}' not found. Please install VICE or configure the correct path using the 'binaryPath' setting.`,
|
|
2485
|
+
"process_launch",
|
|
2486
|
+
false,
|
|
2487
|
+
{ binary, searchedPath: process.env.PATH }
|
|
2488
|
+
);
|
|
2489
|
+
}
|
|
2430
2490
|
const args = ["-autostartprgmode", "1", "-binarymonitor", "-binarymonitoraddress", `${host}:${port}`];
|
|
2431
2491
|
if (config.arguments) {
|
|
2432
2492
|
args.push(...splitCommandLine(config.arguments));
|
|
@@ -2444,11 +2504,15 @@ var ViceSession = class {
|
|
|
2444
2504
|
this.#lastRuntimeEventType = "unknown";
|
|
2445
2505
|
this.#lastRuntimeProgramCounter = null;
|
|
2446
2506
|
const env = await buildViceLaunchEnv();
|
|
2507
|
+
let spawnError = void 0;
|
|
2447
2508
|
const child = spawn(binary, args, {
|
|
2448
2509
|
cwd: config.workingDirectory ? path.resolve(config.workingDirectory) : void 0,
|
|
2449
2510
|
env,
|
|
2450
2511
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2451
2512
|
});
|
|
2513
|
+
child.once("error", (err) => {
|
|
2514
|
+
spawnError = err;
|
|
2515
|
+
});
|
|
2452
2516
|
this.#process = child;
|
|
2453
2517
|
this.#attachProcessLogging(child, binary, args);
|
|
2454
2518
|
this.#bindProcessLifecycle(child);
|
|
@@ -2456,6 +2520,16 @@ var ViceSession = class {
|
|
|
2456
2520
|
this.#transportState = "waiting_for_monitor";
|
|
2457
2521
|
try {
|
|
2458
2522
|
await waitForMonitor(host, port, 5e3);
|
|
2523
|
+
if (spawnError !== void 0) {
|
|
2524
|
+
const err = spawnError;
|
|
2525
|
+
throw new ViceMcpError(
|
|
2526
|
+
"spawn_failed",
|
|
2527
|
+
`Failed to start VICE emulator '${binary}': ${err.message}`,
|
|
2528
|
+
"process_launch",
|
|
2529
|
+
false,
|
|
2530
|
+
{ binary, error: err.message, resolvedPath: binaryCheck.path }
|
|
2531
|
+
);
|
|
2532
|
+
}
|
|
2459
2533
|
await this.#client.connect(host, port);
|
|
2460
2534
|
this.#transportState = "connected";
|
|
2461
2535
|
this.#connectedSince = nowIso();
|
|
@@ -2468,6 +2542,19 @@ var ViceSession = class {
|
|
|
2468
2542
|
} catch (error) {
|
|
2469
2543
|
this.#processState = "crashed";
|
|
2470
2544
|
this.#transportState = "faulted";
|
|
2545
|
+
if (error instanceof ViceMcpError && error.code === "monitor_timeout" && spawnError !== void 0) {
|
|
2546
|
+
const err = spawnError;
|
|
2547
|
+
const enhancedError = new ViceMcpError(
|
|
2548
|
+
"emulator_crashed_on_startup",
|
|
2549
|
+
`VICE emulator '${binary}' crashed during startup: ${err.message}`,
|
|
2550
|
+
"process_launch",
|
|
2551
|
+
false,
|
|
2552
|
+
{ binary, error: err.message, resolvedPath: binaryCheck.path }
|
|
2553
|
+
);
|
|
2554
|
+
this.#warnings = [...this.#warnings.filter((warning) => warning.code !== "launch_failed"), makeWarning(enhancedError.message, "launch_failed")];
|
|
2555
|
+
await this.#stopManagedProcess(true);
|
|
2556
|
+
throw enhancedError;
|
|
2557
|
+
}
|
|
2471
2558
|
this.#warnings = [...this.#warnings.filter((warning) => warning.code !== "launch_failed"), makeWarning(String(error.message ?? error), "launch_failed")];
|
|
2472
2559
|
await this.#stopManagedProcess(true);
|
|
2473
2560
|
throw error;
|
|
@@ -3208,6 +3295,27 @@ function splitCommandLine(input) {
|
|
|
3208
3295
|
}
|
|
3209
3296
|
return result;
|
|
3210
3297
|
}
|
|
3298
|
+
async function checkBinaryExists(binaryPath) {
|
|
3299
|
+
if (path.isAbsolute(binaryPath)) {
|
|
3300
|
+
try {
|
|
3301
|
+
await fs.access(binaryPath, fs.constants.X_OK);
|
|
3302
|
+
return { exists: true, path: binaryPath };
|
|
3303
|
+
} catch {
|
|
3304
|
+
return { exists: false };
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
const pathEnv = process.env.PATH || "";
|
|
3308
|
+
const pathDirs = pathEnv.split(path.delimiter);
|
|
3309
|
+
for (const dir of pathDirs) {
|
|
3310
|
+
const fullPath = path.join(dir, binaryPath);
|
|
3311
|
+
try {
|
|
3312
|
+
await fs.access(fullPath, fs.constants.X_OK);
|
|
3313
|
+
return { exists: true, path: fullPath };
|
|
3314
|
+
} catch {
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
return { exists: false };
|
|
3318
|
+
}
|
|
3211
3319
|
async function waitForMonitor(host, port, timeoutMs) {
|
|
3212
3320
|
const deadline = Date.now() + timeoutMs;
|
|
3213
3321
|
while (Date.now() < deadline) {
|
|
@@ -3216,7 +3324,7 @@ async function waitForMonitor(host, port, timeoutMs) {
|
|
|
3216
3324
|
}
|
|
3217
3325
|
await sleep(100);
|
|
3218
3326
|
}
|
|
3219
|
-
throw new ViceMcpError("monitor_timeout", `Debugger monitor did not open on ${host}:${port}
|
|
3327
|
+
throw new ViceMcpError("monitor_timeout", `Debugger monitor did not open on ${host}:${port}. The emulator may have failed to start or crashed during startup.`, "timeout", true, {
|
|
3220
3328
|
host,
|
|
3221
3329
|
port
|
|
3222
3330
|
});
|
|
@@ -3281,7 +3389,7 @@ var getSessionStateTool = createViceTool({
|
|
|
3281
3389
|
description: "Returns emulator session state including transport/process status, auto-resume state, and the most recent hit checkpoint.",
|
|
3282
3390
|
inputSchema: noInputSchema,
|
|
3283
3391
|
dataSchema: sessionStateResultSchema,
|
|
3284
|
-
execute: async () => c64Session.
|
|
3392
|
+
execute: async () => await c64Session.getSessionState()
|
|
3285
3393
|
});
|
|
3286
3394
|
var getRegistersTool = createViceTool({
|
|
3287
3395
|
id: "get_registers",
|
|
@@ -3347,7 +3455,7 @@ var writeMemoryTool = createViceTool({
|
|
|
3347
3455
|
});
|
|
3348
3456
|
var executeTool = createViceTool({
|
|
3349
3457
|
id: "execute",
|
|
3350
|
-
description: "Controls execution with pause, resume, step, step_over, step_out, or reset.
|
|
3458
|
+
description: "Controls execution with pause, resume, step, step_over, step_out, or reset. Pause and resume are idempotent (safe to call multiple times).",
|
|
3351
3459
|
inputSchema: z3.object({
|
|
3352
3460
|
action: z3.enum(["pause", "resume", "step", "step_over", "step_out", "reset"]),
|
|
3353
3461
|
count: z3.number().int().positive().default(1).describe("Instruction count for step and step_over actions"),
|
|
@@ -3469,7 +3577,7 @@ var getDisplayTextTool = createViceTool({
|
|
|
3469
3577
|
});
|
|
3470
3578
|
var writeTextTool = createViceTool({
|
|
3471
3579
|
id: "write_text",
|
|
3472
|
-
description:
|
|
3580
|
+
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.",
|
|
3473
3581
|
inputSchema: z3.object({
|
|
3474
3582
|
text: z3.string()
|
|
3475
3583
|
}),
|
|
@@ -3481,7 +3589,7 @@ var writeTextTool = createViceTool({
|
|
|
3481
3589
|
});
|
|
3482
3590
|
var keyboardInputTool = createViceTool({
|
|
3483
3591
|
id: "keyboard_input",
|
|
3484
|
-
description:
|
|
3592
|
+
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.",
|
|
3485
3593
|
inputSchema: z3.object({
|
|
3486
3594
|
action: inputActionSchema.describe("Use tap for a single key event or press/release for repeated buffered input"),
|
|
3487
3595
|
keys: z3.array(z3.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"),
|
|
@@ -3492,7 +3600,7 @@ var keyboardInputTool = createViceTool({
|
|
|
3492
3600
|
});
|
|
3493
3601
|
var joystickInputTool = createViceTool({
|
|
3494
3602
|
id: "joystick_input",
|
|
3495
|
-
description:
|
|
3603
|
+
description: "Sends joystick input to C64 joystick port 1 or 2. Automatically resumes if stopped and restores pause state after.",
|
|
3496
3604
|
inputSchema: z3.object({
|
|
3497
3605
|
port: joystickPortSchema.describe("Joystick port number"),
|
|
3498
3606
|
action: inputActionSchema.describe("Joystick action to apply"),
|