c64-debug-mcp 1.0.1 → 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 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.category) {
429
- case "validation":
430
- case "session_state":
431
- case "unsupported":
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
- return void 0;
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) {
@@ -1370,6 +1382,8 @@ var DEFAULT_INPUT_TAP_MS = 75;
1370
1382
  var DEFAULT_KEYBOARD_REPEAT_MS = 100;
1371
1383
  var VICE_PROCESS_LOG_PATH = import_node_path.default.join(import_node_os.default.tmpdir(), "c64-debug-mcp-x64sc.log");
1372
1384
  var DISPLAY_CAPTURE_DIR = import_node_path.default.resolve(process.cwd(), ".vice-debug-mcp-artifacts");
1385
+ var CLEANUP_ENABLED = !/^(0|false|no|off)$/i.test(process.env.C64_CLEANUP_SCREENSHOTS ?? "");
1386
+ var CLEANUP_MAX_AGE_MINUTES = Number.parseInt(process.env.C64_CLEANUP_MAX_AGE_MINUTES ?? "20", 10);
1373
1387
  var MIRROR_EMULATOR_LOGS_TO_STDERR = /^(1|true|yes|on)$/i.test(process.env.C64_DEBUG_CONSOLE_LOGS ?? "");
1374
1388
  var EXECUTION_EVENT_WAIT_MS = 1e3;
1375
1389
  var EXECUTION_SETTLE_DELAY_MS = 2e3;
@@ -1518,6 +1532,7 @@ var ViceSession = class {
1518
1532
  #displayOperationLock = null;
1519
1533
  constructor(portAllocator = new PortAllocator()) {
1520
1534
  this.#portAllocator = portAllocator;
1535
+ void this.#cleanupOldScreenshots();
1521
1536
  this.#client.on("response", (response) => {
1522
1537
  this.#lastResponseAt = nowIso();
1523
1538
  this.#writeProcessLogLine(`[monitor-response] type=${response.type} requestId=${response.requestId} errorCode=${response.errorCode}`);
@@ -1547,6 +1562,10 @@ var ViceSession = class {
1547
1562
  this.#syncMonitorRuntimeState();
1548
1563
  });
1549
1564
  }
1565
+ async getSessionState() {
1566
+ await this.#ensureReady();
1567
+ return this.snapshot();
1568
+ }
1550
1569
  snapshot() {
1551
1570
  return {
1552
1571
  transportState: this.#transportState,
@@ -1783,6 +1802,17 @@ var ViceSession = class {
1783
1802
  async continueExecution(waitUntilRunningStable = false) {
1784
1803
  return this.#withExecutionLock(async () => {
1785
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
+ }
1786
1816
  if (this.#executionState !== "stopped") {
1787
1817
  debuggerNotPausedError("execute resume", {
1788
1818
  executionState: this.#executionState,
@@ -1961,7 +1991,6 @@ var ViceSession = class {
1961
1991
  async programLoad(options) {
1962
1992
  const filePath = import_node_path.default.resolve(options.filePath);
1963
1993
  await this.#assertReadableProgramFile(filePath);
1964
- await this.#ensureRunning("program_load");
1965
1994
  this.#explicitPauseActive = false;
1966
1995
  const result = await this.autostartProgram(filePath, options.autoStart ?? true, options.fileIndex ?? 0);
1967
1996
  return {
@@ -2212,134 +2241,137 @@ var ViceSession = class {
2212
2241
  }
2213
2242
  }
2214
2243
  async writeText(text) {
2215
- await this.#ensureRunning("write_text");
2216
- const encoded = decodeWriteTextToPetscii(text);
2217
- if (encoded.length > MAX_WRITE_TEXT_BYTES) {
2218
- validationError("write_text exceeds the maximum allowed byte length for one request", {
2219
- length: encoded.length,
2220
- max: MAX_WRITE_TEXT_BYTES
2221
- });
2222
- }
2223
- this.#writeProcessLogLine(`[tx] write_text length=${encoded.length} text=${JSON.stringify(text)}`);
2224
- await this.#client.sendKeys(Buffer.from(encoded).toString("binary"));
2225
- await this.#settleInputState("write_text", "running");
2226
- return {
2227
- sent: true,
2228
- length: encoded.length
2229
- };
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
+ });
2230
2260
  }
2231
2261
  async keyboardInput(action, keys, durationMs) {
2232
- await this.#ensureRunning("keyboard_input");
2233
- if (!Array.isArray(keys) || keys.length === 0 || keys.length > 4) {
2234
- validationError("keyboard_input requires between 1 and 4 keys", { keys });
2235
- }
2236
- const resolvedKeys = keys.map((key) => resolveKeyboardInputKey(key));
2237
- const normalizedKeys = resolvedKeys.map((key) => key.canonical);
2238
- this.#writeProcessLogLine(
2239
- `[tx] keyboard_input action=${action} keys=${normalizedKeys.join(",")}${durationMs == null ? "" : ` durationMs=${durationMs}`}`
2240
- );
2241
- switch (action) {
2242
- case "tap": {
2243
- const duration = clampTapDuration(durationMs);
2244
- const bytes = Uint8Array.from(resolvedKeys.flatMap((key) => Array.from(key.bytes)));
2245
- await this.#client.sendKeys(Buffer.from(bytes).toString("binary"));
2246
- await this.#settleInputState("keyboard_input", "running");
2247
- await sleep(duration);
2248
- return {
2249
- action,
2250
- keys: normalizedKeys,
2251
- applied: true,
2252
- held: false,
2253
- mode: "buffered_text"
2254
- };
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 });
2255
2265
  }
2256
- case "press": {
2257
- const singleByteKeys = resolvedKeys.map((key) => {
2258
- if (key.bytes.length !== 1) {
2259
- unsupportedError("keyboard_input press/release only supports keys that map to a single PETSCII byte.", {
2260
- key: key.canonical
2261
- });
2262
- }
2263
- return key.bytes[0];
2264
- });
2265
- for (let index = 0; index < normalizedKeys.length; index += 1) {
2266
- const heldKey = normalizedKeys[index];
2267
- const byte = singleByteKeys[index];
2268
- if (!this.#heldKeyboardIntervals.has(heldKey)) {
2269
- await this.#client.sendKeys(Buffer.from([byte]).toString("binary"));
2270
- await this.#settleInputState("keyboard_input", "running");
2271
- const interval = setInterval(() => {
2272
- void this.#client.sendKeys(Buffer.from([byte]).toString("binary")).then(() => this.#settleInputState("keyboard_input", "running")).catch(() => void 0);
2273
- }, DEFAULT_KEYBOARD_REPEAT_MS);
2274
- this.#heldKeyboardIntervals.set(heldKey, interval);
2275
- }
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
+ };
2276
2285
  }
2277
- return {
2278
- action,
2279
- keys: normalizedKeys,
2280
- applied: true,
2281
- held: true,
2282
- mode: "buffered_text_repeat"
2283
- };
2284
- }
2285
- case "release": {
2286
- for (const key of resolvedKeys) {
2287
- if (key.bytes.length !== 1) {
2288
- unsupportedError("keyboard_input press/release only supports keys that map to a single PETSCII byte.", {
2289
- key: key.canonical
2290
- });
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
+ }
2291
2306
  }
2307
+ return {
2308
+ action,
2309
+ keys: normalizedKeys,
2310
+ applied: true,
2311
+ held: true,
2312
+ mode: "buffered_text_repeat"
2313
+ };
2292
2314
  }
2293
- for (const heldKey of normalizedKeys) {
2294
- const interval = this.#heldKeyboardIntervals.get(heldKey);
2295
- if (interval) {
2296
- clearInterval(interval);
2297
- this.#heldKeyboardIntervals.delete(heldKey);
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
+ }
2298
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
+ };
2299
2337
  }
2300
- return {
2301
- action,
2302
- keys: normalizedKeys,
2303
- applied: true,
2304
- held: false,
2305
- mode: "buffered_text_repeat"
2306
- };
2307
2338
  }
2308
- }
2339
+ });
2309
2340
  }
2310
2341
  async joystickInput(port2, action, control, durationMs) {
2311
- await this.#ensureRunning("joystick_input");
2312
- const previousExecutionState = this.#executionState;
2313
- const bit = JOYSTICK_CONTROL_BITS[control];
2314
- if (bit == null) {
2315
- validationError("Unsupported joystick control", { control });
2316
- }
2317
- this.#writeProcessLogLine(
2318
- `[tx] joystick_input port=${port2} action=${action} control=${control}${durationMs == null ? "" : ` durationMs=${durationMs}`}`
2319
- );
2320
- switch (action) {
2321
- case "tap": {
2322
- const duration = clampTapDuration(durationMs);
2323
- await this.#applyJoystickMask(port2, this.#getJoystickMask(port2) & ~bit);
2324
- await sleep(duration);
2325
- await this.#applyJoystickMask(port2, this.#getJoystickMask(port2) | bit);
2326
- 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 });
2327
2347
  }
2328
- case "press":
2329
- await this.#applyJoystickMask(port2, this.#getJoystickMask(port2) & ~bit);
2330
- break;
2331
- case "release":
2332
- await this.#applyJoystickMask(port2, this.#getJoystickMask(port2) | bit);
2333
- break;
2334
- }
2335
- await this.#settleInputState("joystick_input", previousExecutionState);
2336
- return {
2337
- port: port2,
2338
- action,
2339
- control,
2340
- applied: true,
2341
- state: this.#describeJoystickState(port2)
2342
- };
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
+ });
2343
2375
  }
2344
2376
  async waitForState(targetState, timeoutMs = 5e3, stableMs = targetState === "running" ? INPUT_RUNNING_STABLE_MS : 0) {
2345
2377
  await this.#ensureReady();
@@ -2402,6 +2434,27 @@ var ViceSession = class {
2402
2434
  });
2403
2435
  }
2404
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
+ }
2405
2458
  async #ensureHealthyConnection() {
2406
2459
  if (this.#recoveryPromise) {
2407
2460
  await this.#recoveryPromise;
@@ -2450,6 +2503,16 @@ var ViceSession = class {
2450
2503
  const port2 = await this.#portAllocator.allocate();
2451
2504
  await this.#portAllocator.ensureFree(port2, host2);
2452
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
+ }
2453
2516
  const args = ["-autostartprgmode", "1", "-binarymonitor", "-binarymonitoraddress", `${host2}:${port2}`];
2454
2517
  if (config.arguments) {
2455
2518
  args.push(...splitCommandLine(config.arguments));
@@ -2467,11 +2530,15 @@ var ViceSession = class {
2467
2530
  this.#lastRuntimeEventType = "unknown";
2468
2531
  this.#lastRuntimeProgramCounter = null;
2469
2532
  const env = await buildViceLaunchEnv();
2533
+ let spawnError = void 0;
2470
2534
  const child = (0, import_node_child_process.spawn)(binary, args, {
2471
2535
  cwd: config.workingDirectory ? import_node_path.default.resolve(config.workingDirectory) : void 0,
2472
2536
  env,
2473
2537
  stdio: ["ignore", "pipe", "pipe"]
2474
2538
  });
2539
+ child.once("error", (err) => {
2540
+ spawnError = err;
2541
+ });
2475
2542
  this.#process = child;
2476
2543
  this.#attachProcessLogging(child, binary, args);
2477
2544
  this.#bindProcessLifecycle(child);
@@ -2479,6 +2546,16 @@ var ViceSession = class {
2479
2546
  this.#transportState = "waiting_for_monitor";
2480
2547
  try {
2481
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
+ }
2482
2559
  await this.#client.connect(host2, port2);
2483
2560
  this.#transportState = "connected";
2484
2561
  this.#connectedSince = nowIso();
@@ -2491,6 +2568,19 @@ var ViceSession = class {
2491
2568
  } catch (error) {
2492
2569
  this.#processState = "crashed";
2493
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
+ }
2494
2584
  this.#warnings = [...this.#warnings.filter((warning) => warning.code !== "launch_failed"), makeWarning(String(error.message ?? error), "launch_failed")];
2495
2585
  await this.#stopManagedProcess(true);
2496
2586
  throw error;
@@ -3160,6 +3250,45 @@ var ViceSession = class {
3160
3250
  }
3161
3251
  this.#syncMonitorRuntimeState();
3162
3252
  }
3253
+ async #cleanupOldScreenshots() {
3254
+ if (!CLEANUP_ENABLED) {
3255
+ return;
3256
+ }
3257
+ try {
3258
+ const maxAgeMinutes = Math.max(1, Math.min(525600, CLEANUP_MAX_AGE_MINUTES));
3259
+ const maxAgeMs = maxAgeMinutes * 60 * 1e3;
3260
+ const cutoffTime = Date.now() - maxAgeMs;
3261
+ this.#writeProcessLogLine(`[cleanup] scanning ${DISPLAY_CAPTURE_DIR} for screenshots older than ${maxAgeMinutes}m`);
3262
+ let entries;
3263
+ try {
3264
+ entries = await import_promises.default.readdir(DISPLAY_CAPTURE_DIR);
3265
+ } catch (error) {
3266
+ if (error.code === "ENOENT") {
3267
+ return;
3268
+ }
3269
+ throw error;
3270
+ }
3271
+ const pngFiles = entries.filter((name) => name.endsWith(".png") && name.startsWith("capture-"));
3272
+ let deletedCount = 0;
3273
+ let errorCount = 0;
3274
+ for (const filename of pngFiles) {
3275
+ try {
3276
+ const filePath = import_node_path.default.join(DISPLAY_CAPTURE_DIR, filename);
3277
+ const stats = await import_promises.default.stat(filePath);
3278
+ if (stats.mtime.getTime() < cutoffTime) {
3279
+ await import_promises.default.unlink(filePath);
3280
+ deletedCount++;
3281
+ }
3282
+ } catch (error) {
3283
+ errorCount++;
3284
+ this.#writeProcessLogLine(`[cleanup] failed to delete ${filename}: ${error instanceof Error ? error.message : String(error)}`);
3285
+ }
3286
+ }
3287
+ this.#writeProcessLogLine(`[cleanup] completed: ${deletedCount} deleted, ${errorCount} errors, ${pngFiles.length - deletedCount - errorCount} retained`);
3288
+ } catch (error) {
3289
+ this.#writeProcessLogLine(`[cleanup] failed: ${error instanceof Error ? error.message : String(error)}`);
3290
+ }
3291
+ }
3163
3292
  };
3164
3293
  function splitCommandLine(input) {
3165
3294
  const result = [];
@@ -3192,6 +3321,27 @@ function splitCommandLine(input) {
3192
3321
  }
3193
3322
  return result;
3194
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
+ }
3195
3345
  async function waitForMonitor(host2, port2, timeoutMs) {
3196
3346
  const deadline = Date.now() + timeoutMs;
3197
3347
  while (Date.now() < deadline) {
@@ -3200,7 +3350,7 @@ async function waitForMonitor(host2, port2, timeoutMs) {
3200
3350
  }
3201
3351
  await sleep(100);
3202
3352
  }
3203
- throw new ViceMcpError("monitor_timeout", `Debugger monitor did not open on ${host2}:${port2}`, "timeout", true, {
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, {
3204
3354
  host: host2,
3205
3355
  port: port2
3206
3356
  });
@@ -3265,7 +3415,7 @@ var getSessionStateTool = createViceTool({
3265
3415
  description: "Returns emulator session state including transport/process status, auto-resume state, and the most recent hit checkpoint.",
3266
3416
  inputSchema: noInputSchema,
3267
3417
  dataSchema: sessionStateResultSchema,
3268
- execute: async () => c64Session.snapshot()
3418
+ execute: async () => await c64Session.getSessionState()
3269
3419
  });
3270
3420
  var getRegistersTool = createViceTool({
3271
3421
  id: "get_registers",
@@ -3331,7 +3481,7 @@ var writeMemoryTool = createViceTool({
3331
3481
  });
3332
3482
  var executeTool = createViceTool({
3333
3483
  id: "execute",
3334
- description: "Controls execution with pause, resume, step, step_over, step_out, or reset. Resume can optionally wait until running becomes stable.",
3484
+ description: "Controls execution with pause, resume, step, step_over, step_out, or reset. Pause and resume are idempotent (safe to call multiple times).",
3335
3485
  inputSchema: import_zod4.z.object({
3336
3486
  action: import_zod4.z.enum(["pause", "resume", "step", "step_over", "step_out", "reset"]),
3337
3487
  count: import_zod4.z.number().int().positive().default(1).describe("Instruction count for step and step_over actions"),
@@ -3453,7 +3603,7 @@ var getDisplayTextTool = createViceTool({
3453
3603
  });
3454
3604
  var writeTextTool = createViceTool({
3455
3605
  id: "write_text",
3456
- description: 'Types text into the C64. Requires emulator to be running - call execute(action="resume") first if stopped. Supports escaped characters and PETSCII brace tokens like {RETURN}, {CLR}, {HOME}, {PI}, and color names. Limit 64 bytes per request.',
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.",
3457
3607
  inputSchema: import_zod4.z.object({
3458
3608
  text: import_zod4.z.string()
3459
3609
  }),
@@ -3465,7 +3615,7 @@ var writeTextTool = createViceTool({
3465
3615
  });
3466
3616
  var keyboardInputTool = createViceTool({
3467
3617
  id: "keyboard_input",
3468
- description: 'Sends one to four keys or PETSCII tokens to the C64. Requires emulator to be running - call execute(action="resume") first if stopped. Use for key presses, releases, and taps.',
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.",
3469
3619
  inputSchema: import_zod4.z.object({
3470
3620
  action: inputActionSchema.describe("Use tap for a single key event or press/release for repeated buffered input"),
3471
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"),
@@ -3476,7 +3626,7 @@ var keyboardInputTool = createViceTool({
3476
3626
  });
3477
3627
  var joystickInputTool = createViceTool({
3478
3628
  id: "joystick_input",
3479
- description: 'Sends joystick input to C64 joystick port 1 or 2. Requires emulator to be running - call execute(action="resume") first if stopped.',
3629
+ description: "Sends joystick input to C64 joystick port 1 or 2. Automatically resumes if stopped and restores pause state after.",
3480
3630
  inputSchema: import_zod4.z.object({
3481
3631
  port: joystickPortSchema.describe("Joystick port number"),
3482
3632
  action: inputActionSchema.describe("Joystick action to apply"),