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 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) {
@@ -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.#ensureRunning("write_text");
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
- };
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.#ensureRunning("keyboard_input");
2236
- if (!Array.isArray(keys) || keys.length === 0 || keys.length > 4) {
2237
- validationError("keyboard_input requires between 1 and 4 keys", { keys });
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
- case "press": {
2260
- const singleByteKeys = resolvedKeys.map((key) => {
2261
- if (key.bytes.length !== 1) {
2262
- unsupportedError("keyboard_input press/release only supports keys that map to a single PETSCII byte.", {
2263
- key: key.canonical
2264
- });
2265
- }
2266
- return key.bytes[0];
2267
- });
2268
- for (let index = 0; index < normalizedKeys.length; index += 1) {
2269
- const heldKey = normalizedKeys[index];
2270
- const byte = singleByteKeys[index];
2271
- if (!this.#heldKeyboardIntervals.has(heldKey)) {
2272
- await this.#client.sendKeys(Buffer.from([byte]).toString("binary"));
2273
- await this.#settleInputState("keyboard_input", "running");
2274
- const interval = setInterval(() => {
2275
- void this.#client.sendKeys(Buffer.from([byte]).toString("binary")).then(() => this.#settleInputState("keyboard_input", "running")).catch(() => void 0);
2276
- }, DEFAULT_KEYBOARD_REPEAT_MS);
2277
- this.#heldKeyboardIntervals.set(heldKey, interval);
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
- return {
2281
- action,
2282
- keys: normalizedKeys,
2283
- applied: true,
2284
- held: true,
2285
- mode: "buffered_text_repeat"
2286
- };
2287
- }
2288
- case "release": {
2289
- for (const key of resolvedKeys) {
2290
- if (key.bytes.length !== 1) {
2291
- unsupportedError("keyboard_input press/release only supports keys that map to a single PETSCII byte.", {
2292
- key: key.canonical
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
- for (const heldKey of normalizedKeys) {
2297
- const interval = this.#heldKeyboardIntervals.get(heldKey);
2298
- if (interval) {
2299
- clearInterval(interval);
2300
- 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
+ }
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.#ensureRunning("joystick_input");
2315
- const previousExecutionState = this.#executionState;
2316
- const bit = JOYSTICK_CONTROL_BITS[control];
2317
- if (bit == null) {
2318
- validationError("Unsupported joystick control", { control });
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
- case "press":
2332
- await this.#applyJoystickMask(port2, this.#getJoystickMask(port2) & ~bit);
2333
- break;
2334
- case "release":
2335
- await this.#applyJoystickMask(port2, this.#getJoystickMask(port2) | bit);
2336
- break;
2337
- }
2338
- await this.#settleInputState("joystick_input", previousExecutionState);
2339
- return {
2340
- port: port2,
2341
- action,
2342
- control,
2343
- applied: true,
2344
- state: this.#describeJoystickState(port2)
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}`, "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, {
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.snapshot()
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. 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).",
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: '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.",
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: '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.",
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: '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.",
3522
3630
  inputSchema: import_zod4.z.object({
3523
3631
  port: joystickPortSchema.describe("Joystick port number"),
3524
3632
  action: inputActionSchema.describe("Joystick action to apply"),