@adhdev/daemon-core 0.5.7 → 0.5.16

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/index.js CHANGED
@@ -396,7 +396,9 @@ __export(index_exports, {
396
396
  installExtensions: () => installExtensions,
397
397
  installGlobalInterceptor: () => installGlobalInterceptor,
398
398
  isExtensionInstalled: () => isExtensionInstalled,
399
+ isIdeRunning: () => isIdeRunning,
399
400
  isSetupComplete: () => isSetupComplete,
401
+ killIdeProcess: () => killIdeProcess,
400
402
  launchIDE: () => launchIDE,
401
403
  launchWithCdp: () => launchWithCdp,
402
404
  loadConfig: () => loadConfig,
@@ -499,13 +501,11 @@ function getIdeVersion(cliCommand) {
499
501
  }
500
502
  }
501
503
  function checkPathExists(paths) {
504
+ const home = (0, import_os2.homedir)();
502
505
  for (const p of paths) {
503
506
  if (p.includes("*")) {
504
- const home = (0, import_os2.homedir)();
505
- const starIdx = p.indexOf("*");
506
- const suffix = p.substring(starIdx + 1);
507
- const homeNormalized = home.replace(/\//g, "\\\\");
508
- const resolved = homeNormalized + suffix;
507
+ const username = home.split(/[\\/]/).pop() || "";
508
+ const resolved = p.replace("*", username);
509
509
  if ((0, import_fs2.existsSync)(resolved)) return resolved;
510
510
  } else {
511
511
  if ((0, import_fs2.existsSync)(p)) return p;
@@ -2340,8 +2340,7 @@ var IdeProviderInstance = class {
2340
2340
  // IDE meta
2341
2341
  ideVersion = "";
2342
2342
  instanceId;
2343
- workspaceFolders = [];
2344
- activeFile = null;
2343
+ workspace = "";
2345
2344
  // ─── Child Extension Instances ────────────────────
2346
2345
  extensions = /* @__PURE__ */ new Map();
2347
2346
  constructor(provider, instanceKey) {
@@ -2402,8 +2401,7 @@ var IdeProviderInstance = class {
2402
2401
  activeModal: this.cachedChat.activeModal || null,
2403
2402
  inputContent: this.cachedChat.inputContent || ""
2404
2403
  } : null,
2405
- workspaceFolders: this.workspaceFolders,
2406
- activeFile: this.activeFile,
2404
+ workspace: this.workspace || null,
2407
2405
  extensions: extensionStates,
2408
2406
  cdpConnected: cdp?.isConnected || false,
2409
2407
  currentModel: this.cachedChat?.model || void 0,
@@ -2420,11 +2418,6 @@ var IdeProviderInstance = class {
2420
2418
  } else if (event === "cdp_disconnected") {
2421
2419
  this.cachedChat = null;
2422
2420
  this.currentStatus = "idle";
2423
- } else if (event === "extension_data") {
2424
- if (data?.workspaceFolders) this.workspaceFolders = data.workspaceFolders;
2425
- if (data?.activeFile) this.activeFile = data.activeFile;
2426
- if (data?.ideVersion) this.ideVersion = data.ideVersion;
2427
- if (data?.instanceId) this.instanceId = data.instanceId;
2428
2421
  } else if (event === "stream_update") {
2429
2422
  const extType = data?.extensionType;
2430
2423
  if (extType && this.extensions.has(extType)) {
@@ -2480,6 +2473,10 @@ var IdeProviderInstance = class {
2480
2473
  getExtensionInstances() {
2481
2474
  return [...this.extensions.values()];
2482
2475
  }
2476
+ /** Set workspace from daemon launch context */
2477
+ setWorkspace(workspace) {
2478
+ this.workspace = workspace;
2479
+ }
2483
2480
  // ─── CDP readChat ───────────────────────────────
2484
2481
  async readChat() {
2485
2482
  const { cdp } = this.context;
@@ -6033,8 +6030,8 @@ var DaemonCommandRouter = class {
6033
6030
  if (!targetType) throw new Error("cliType or ideType required");
6034
6031
  const isIde = this.deps.cdpManagers.has(targetType) || this.deps.providerLoader.getMeta(targetType)?.category === "ide";
6035
6032
  if (isIde) {
6036
- await this.stopIde(targetType);
6037
- const launchResult = await this.executeDaemonCommand("launch_ide", { ideType: targetType, enableCdp: true });
6033
+ await this.stopIde(targetType, true);
6034
+ const launchResult = await this.executeDaemonCommand("launch_ide", { ideType: targetType, enableCdp: true, workspace: args?.workspace });
6038
6035
  return { success: true, restarted: true, ideType: targetType, launch: launchResult };
6039
6036
  }
6040
6037
  return this.deps.cliManager.handleCliCommand(cmd, args);
@@ -6043,15 +6040,16 @@ var DaemonCommandRouter = class {
6043
6040
  case "stop_ide": {
6044
6041
  const ideType = args?.ideType;
6045
6042
  if (!ideType) throw new Error("ideType required");
6046
- await this.stopIde(ideType);
6047
- return { success: true, ideType, stopped: true };
6043
+ const killProcess = args?.killProcess !== false;
6044
+ await this.stopIde(ideType, killProcess);
6045
+ return { success: true, ideType, stopped: true, processKilled: killProcess };
6048
6046
  }
6049
6047
  // ─── IDE restart ───
6050
6048
  case "restart_ide": {
6051
6049
  const ideType = args?.ideType;
6052
6050
  if (!ideType) throw new Error("ideType required");
6053
- await this.stopIde(ideType);
6054
- const launchResult = await this.executeDaemonCommand("launch_ide", { ideType, enableCdp: true });
6051
+ await this.stopIde(ideType, true);
6052
+ const launchResult = await this.executeDaemonCommand("launch_ide", { ideType, enableCdp: true, workspace: args?.workspace });
6055
6053
  return { success: true, ideType, restarted: true, launch: launchResult };
6056
6054
  }
6057
6055
  // ─── IDE launch + CDP connect ───
@@ -6097,10 +6095,48 @@ var DaemonCommandRouter = class {
6097
6095
  }
6098
6096
  return { success: result.success, ...result };
6099
6097
  }
6100
- // ─── IDE detection ───
6098
+ // ─── Detect IDEs ───
6101
6099
  case "detect_ides": {
6102
- this.deps.detectedIdes.value = await detectIDEs();
6103
- return { success: true, ides: this.deps.detectedIdes.value };
6100
+ const results = await detectIDEs();
6101
+ this.deps.detectedIdes.value = results;
6102
+ return { success: true, detectedInfo: results };
6103
+ }
6104
+ // ─── Set User Name ───
6105
+ case "set_user_name": {
6106
+ const name = args?.userName;
6107
+ if (!name || typeof name !== "string") throw new Error("userName required");
6108
+ updateConfig({ userName: name });
6109
+ return { success: true, userName: name };
6110
+ }
6111
+ // ─── Daemon Self-Upgrade ───
6112
+ case "daemon_upgrade": {
6113
+ LOG.info("Upgrade", "Remote upgrade requested from dashboard");
6114
+ try {
6115
+ const { execSync: execSync7 } = await import("child_process");
6116
+ const latest = execSync7("npm view adhdev version", { encoding: "utf-8", timeout: 1e4 }).trim();
6117
+ LOG.info("Upgrade", `Latest available: v${latest}`);
6118
+ execSync7("npm install -g adhdev@latest", {
6119
+ encoding: "utf-8",
6120
+ timeout: 6e4,
6121
+ stdio: ["pipe", "pipe", "pipe"]
6122
+ });
6123
+ LOG.info("Upgrade", `\u2705 Upgraded to v${latest}`);
6124
+ setTimeout(() => {
6125
+ LOG.info("Upgrade", "Restarting daemon with new version...");
6126
+ const { spawn: spawn3 } = require("child_process");
6127
+ const child = spawn3(process.execPath, [process.argv[1], "daemon", "-p", "19222"], {
6128
+ detached: true,
6129
+ stdio: "ignore",
6130
+ env: { ...process.env }
6131
+ });
6132
+ child.unref();
6133
+ process.exit(0);
6134
+ }, 3e3);
6135
+ return { success: true, upgraded: true, version: latest };
6136
+ } catch (e) {
6137
+ LOG.error("Upgrade", `Failed: ${e.message}`);
6138
+ return { success: false, error: e.message };
6139
+ }
6104
6140
  }
6105
6141
  // ─── Machine Settings ───
6106
6142
  case "set_machine_nickname": {
@@ -6108,38 +6144,85 @@ var DaemonCommandRouter = class {
6108
6144
  updateConfig({ machineNickname: nickname || null });
6109
6145
  return { success: true };
6110
6146
  }
6147
+ default:
6148
+ break;
6111
6149
  }
6112
6150
  return null;
6113
6151
  }
6114
6152
  /**
6115
- * IDE stop: CDP disconnect + remove from InstanceManager + clean instanceIdMap
6153
+ * IDE stop: CDP disconnect + InstanceManager cleanup + optionally kill OS process
6116
6154
  */
6117
- async stopIde(ideType) {
6118
- const cdp = this.deps.cdpManagers.get(ideType);
6119
- if (cdp) {
6120
- try {
6121
- cdp.disconnect();
6122
- } catch {
6155
+ async stopIde(ideType, killProcess = false) {
6156
+ const cdpKeysToRemove = [];
6157
+ for (const key of this.deps.cdpManagers.keys()) {
6158
+ if (key === ideType || key.startsWith(`${ideType}_`)) {
6159
+ cdpKeysToRemove.push(key);
6160
+ }
6161
+ }
6162
+ for (const key of cdpKeysToRemove) {
6163
+ const cdp = this.deps.cdpManagers.get(key);
6164
+ if (cdp) {
6165
+ try {
6166
+ cdp.disconnect();
6167
+ } catch {
6168
+ }
6169
+ this.deps.cdpManagers.delete(key);
6170
+ LOG.info("StopIDE", `CDP disconnected: ${key}`);
6171
+ }
6172
+ }
6173
+ const keysToRemove = [];
6174
+ for (const key of this.deps.instanceManager.instances?.keys?.() || []) {
6175
+ if (key === `ide:${ideType}` || typeof key === "string" && key.startsWith(`ide:${ideType}_`)) {
6176
+ keysToRemove.push(key);
6177
+ }
6178
+ }
6179
+ for (const instanceKey of keysToRemove) {
6180
+ const ideInstance = this.deps.instanceManager.getInstance(instanceKey);
6181
+ if (ideInstance) {
6182
+ if (ideInstance.getInstanceId) {
6183
+ this.deps.instanceIdMap.delete(ideInstance.getInstanceId());
6184
+ }
6185
+ if (ideInstance.getExtensionInstances) {
6186
+ for (const ext of ideInstance.getExtensionInstances()) {
6187
+ if (ext.getInstanceId) this.deps.instanceIdMap.delete(ext.getInstanceId());
6188
+ }
6189
+ }
6190
+ this.deps.instanceManager.removeInstance(instanceKey);
6191
+ LOG.info("StopIDE", `Instance removed: ${instanceKey}`);
6123
6192
  }
6124
- this.deps.cdpManagers.delete(ideType);
6125
- LOG.info("StopIDE", `CDP disconnected: ${ideType}`);
6126
6193
  }
6127
- const instanceKey = `ide:${ideType}`;
6128
- const ideInstance = this.deps.instanceManager.getInstance(instanceKey);
6129
- if (ideInstance) {
6130
- if (ideInstance.getInstanceId) {
6131
- this.deps.instanceIdMap.delete(ideInstance.getInstanceId());
6194
+ if (keysToRemove.length === 0) {
6195
+ const instanceKey = `ide:${ideType}`;
6196
+ const ideInstance = this.deps.instanceManager.getInstance(instanceKey);
6197
+ if (ideInstance) {
6198
+ if (ideInstance.getInstanceId) {
6199
+ this.deps.instanceIdMap.delete(ideInstance.getInstanceId());
6200
+ }
6201
+ if (ideInstance.getExtensionInstances) {
6202
+ for (const ext of ideInstance.getExtensionInstances()) {
6203
+ if (ext.getInstanceId) this.deps.instanceIdMap.delete(ext.getInstanceId());
6204
+ }
6205
+ }
6206
+ this.deps.instanceManager.removeInstance(instanceKey);
6207
+ LOG.info("StopIDE", `Instance removed: ${instanceKey}`);
6132
6208
  }
6133
- if (ideInstance.getExtensionInstances) {
6134
- for (const ext of ideInstance.getExtensionInstances()) {
6135
- if (ext.getInstanceId) this.deps.instanceIdMap.delete(ext.getInstanceId());
6209
+ }
6210
+ if (killProcess) {
6211
+ const running = isIdeRunning(ideType);
6212
+ if (running) {
6213
+ LOG.info("StopIDE", `Killing IDE process: ${ideType}`);
6214
+ const killed = await killIdeProcess(ideType);
6215
+ if (killed) {
6216
+ LOG.info("StopIDE", `\u2705 Process killed: ${ideType}`);
6217
+ } else {
6218
+ LOG.warn("StopIDE", `\u26A0 Could not kill process: ${ideType} (may need manual intervention)`);
6136
6219
  }
6220
+ } else {
6221
+ LOG.info("StopIDE", `Process not running: ${ideType}`);
6137
6222
  }
6138
- this.deps.instanceManager.removeInstance(instanceKey);
6139
- LOG.info("StopIDE", `Instance removed: ${instanceKey}`);
6140
6223
  }
6141
6224
  this.deps.onStatusChange?.();
6142
- LOG.info("StopIDE", `IDE stopped: ${ideType}`);
6225
+ LOG.info("StopIDE", `IDE stopped: ${ideType} (processKill=${killProcess})`);
6143
6226
  }
6144
6227
  };
6145
6228
 
@@ -6233,7 +6316,7 @@ var DaemonStatusReporter = class {
6233
6316
  const acpStates = allStates.filter((s) => s.category === "acp");
6234
6317
  const ideSummary = ideStates.map((s) => {
6235
6318
  const msgs = s.activeChat?.messages?.length || 0;
6236
- const exts = (s.extensions || []).length;
6319
+ const exts = s.extensions.length;
6237
6320
  return `${s.type}(${s.status},${msgs}msg,${exts}ext${s.currentModel ? ",model=" + s.currentModel : ""})`;
6238
6321
  }).join(", ");
6239
6322
  const cliSummary = cliStates.map((s) => `${s.type}(${s.status})`).join(", ");
@@ -6253,13 +6336,12 @@ var DaemonStatusReporter = class {
6253
6336
  ideType: s.type,
6254
6337
  ideVersion: "",
6255
6338
  instanceId: s.instanceId,
6256
- workspaceFolders: s.workspaceFolders || [],
6257
- activeFile: s.activeFile || null,
6339
+ workspace: s.workspace || null,
6258
6340
  terminals: 0,
6259
6341
  aiAgents: [],
6260
6342
  activeChat: s.activeChat,
6261
6343
  chats: [],
6262
- agentStreams: (s.extensions || []).map((ext) => ({
6344
+ agentStreams: s.extensions.map((ext) => ({
6263
6345
  agentType: ext.type,
6264
6346
  agentName: ext.name,
6265
6347
  extensionId: ext.type,
@@ -6268,7 +6350,7 @@ var DaemonStatusReporter = class {
6268
6350
  inputContent: ext.activeChat?.inputContent || "",
6269
6351
  activeModal: ext.activeChat?.activeModal || null
6270
6352
  })),
6271
- cdpConnected: s.cdpConnected || false,
6353
+ cdpConnected: s.cdpConnected,
6272
6354
  currentModel: s.currentModel,
6273
6355
  currentPlan: s.currentPlan,
6274
6356
  currentAutoApprove: s.currentAutoApprove
@@ -6279,8 +6361,8 @@ var DaemonStatusReporter = class {
6279
6361
  cliType: s.type,
6280
6362
  cliName: s.name,
6281
6363
  status: s.status,
6282
- mode: s.mode || "terminal",
6283
- workingDir: s.workingDir || "",
6364
+ mode: s.mode,
6365
+ workspace: s.workspace || "",
6284
6366
  activeChat: s.activeChat
6285
6367
  }));
6286
6368
  const managedAcps = acpStates.map((s) => ({
@@ -6288,19 +6370,22 @@ var DaemonStatusReporter = class {
6288
6370
  acpType: s.type,
6289
6371
  acpName: s.name,
6290
6372
  status: s.status,
6291
- mode: "chat",
6292
- workingDir: s.workingDir || "",
6373
+ mode: s.mode,
6374
+ workspace: s.workspace || "",
6293
6375
  activeChat: s.activeChat,
6294
6376
  currentModel: s.currentModel,
6295
6377
  currentPlan: s.currentPlan,
6296
6378
  acpConfigOptions: s.acpConfigOptions,
6297
- acpModes: s.acpModes
6379
+ acpModes: s.acpModes,
6380
+ errorMessage: s.errorMessage,
6381
+ errorReason: s.errorReason
6298
6382
  }));
6299
6383
  const cfg = loadConfig();
6300
6384
  const wsState = getWorkspaceState(cfg);
6301
6385
  const memSnap = getHostMemorySnapshot();
6302
6386
  const payload = {
6303
6387
  daemonMode: true,
6388
+ version: this.deps.daemonVersion || "unknown",
6304
6389
  machineNickname: cfg.machineNickname || null,
6305
6390
  workspaces: wsState.workspaces,
6306
6391
  defaultWorkspaceId: wsState.defaultWorkspaceId,
@@ -6326,7 +6411,6 @@ var DaemonStatusReporter = class {
6326
6411
  peers: p2p?.connectedPeerCount || 0,
6327
6412
  screenshotActive: p2p?.screenshotActive || false
6328
6413
  },
6329
- cdpConnected: [...cdpManagers.values()].some((c) => c.isConnected),
6330
6414
  connectedExtensions: [],
6331
6415
  detectedIdes: this.deps.detectedIdes || [],
6332
6416
  availableProviders: this.deps.providerLoader.getAll().map((p) => ({
@@ -6368,7 +6452,6 @@ var DaemonStatusReporter = class {
6368
6452
  acpName: a.acpName
6369
6453
  })),
6370
6454
  p2p: payload.p2p,
6371
- cdpConnected: payload.cdpConnected,
6372
6455
  timestamp: now
6373
6456
  };
6374
6457
  serverConn.sendMessage("status_report", wsPayload);
@@ -6507,12 +6590,14 @@ var FALLBACK_PROMPT = [
6507
6590
  /Type your message/i,
6508
6591
  /^>\s*$/m,
6509
6592
  // '>' alone on its own line
6510
- /[›➤]\s*$/,
6511
- /for shortcuts/i,
6512
- /\?\s*for help/i,
6593
+ /[›❯]\s*[\r\n]/,
6594
+ // prompt char followed by line ending (ANSI-stripped may not have $ at end)
6595
+ /[›❯]\s*$/m,
6596
+ // prompt char at end of line (multiline)
6597
+ /for\s*shortcuts/i,
6598
+ // Claude Code prompt (ANSI strip may remove spaces → 'forshortcuts')
6599
+ /\?\s*for\s*help/i,
6513
6600
  /Press enter/i
6514
- // NOTE: removed /^[\s\u2500-\u257f]*>\s*$/m — the box-drawing char range is too wide and
6515
- // can match dialog-clearing ANSI output, causing false prompt detection in approval state.
6516
6601
  ];
6517
6602
  var FALLBACK_GENERATING = [
6518
6603
  /[\u2800-\u28ff]/,
@@ -6524,13 +6609,18 @@ var FALLBACK_GENERATING = [
6524
6609
  // Specific Claude Code status
6525
6610
  ];
6526
6611
  var FALLBACK_APPROVAL = [
6527
- /Allow\s+once/i,
6528
- /Always\s+allow/i,
6612
+ /Allow\s*once/i,
6613
+ // ANSI strip may remove spaces
6614
+ /Always\s*allow/i,
6529
6615
  /\(y\/n\)/i,
6530
6616
  /\[Y\/n\]/i,
6531
- /Run\s+this\s+command/i
6532
- // NOTE: removed /Do you want to (?:run|execute|allow)/i — too broad, matches AI explanation
6533
- // text like "Do you want to allow this feature?" causing false approval notifications.
6617
+ /Run\s*this\s*command/i,
6618
+ /Allow\s*tool/i,
6619
+ // Claude Code v2 approval
6620
+ /Yes,?\s*don'?t\s*ask/i,
6621
+ // "Yes, don't ask again" (Claude Code)
6622
+ /Deny/i
6623
+ // Deny button presence = approval dialog
6534
6624
  ];
6535
6625
  function defaultCleanOutput(raw, _lastUserInput) {
6536
6626
  return stripAnsi(raw).trim();
@@ -6565,6 +6655,8 @@ var ProviderCliAdapter = class {
6565
6655
  maxResponse: t.maxResponse ?? 3e5,
6566
6656
  shutdownGrace: t.shutdownGrace ?? 1e3
6567
6657
  };
6658
+ const rawKeys = provider.approvalKeys;
6659
+ this.approvalKeys = rawKeys && typeof rawKeys === "object" ? rawKeys : {};
6568
6660
  }
6569
6661
  cliType;
6570
6662
  cliName;
@@ -6596,8 +6688,20 @@ var ProviderCliAdapter = class {
6596
6688
  approvalExitTimeout = null;
6597
6689
  // Resize redraw suppression
6598
6690
  resizeSuppressUntil = 0;
6691
+ // Debug: status transition history
6692
+ statusHistory = [];
6693
+ setStatus(status, trigger) {
6694
+ const prev = this.currentStatus;
6695
+ if (prev === status) return;
6696
+ this.currentStatus = status;
6697
+ this.statusHistory.push({ status, at: Date.now(), trigger });
6698
+ if (this.statusHistory.length > 50) this.statusHistory.shift();
6699
+ LOG.info("CLI", `[${this.cliType}] status: ${prev} \u2192 ${status}${trigger ? ` (${trigger})` : ""}`);
6700
+ }
6599
6701
  // Resolved timeouts (provider defaults + overrides)
6600
6702
  timeouts;
6703
+ // Provider approval key mapping (e.g. { 0: '1', 1: '2', 2: '3' }) — loaded from provider.json
6704
+ approvalKeys;
6601
6705
  // ─── Lifecycle ─────────────────────────────────
6602
6706
  setServerConn(serverConn) {
6603
6707
  this.serverConn = serverConn;
@@ -6677,11 +6781,11 @@ var ProviderCliAdapter = class {
6677
6781
  this.ptyProcess.onExit(({ exitCode }) => {
6678
6782
  LOG.info("CLI", `[${this.cliType}] Exit code ${exitCode}`);
6679
6783
  this.ptyProcess = null;
6680
- this.currentStatus = "stopped";
6784
+ this.setStatus("stopped", "pty_exit");
6681
6785
  this.ready = false;
6682
6786
  this.onStatusChange?.();
6683
6787
  });
6684
- this.currentStatus = "starting";
6788
+ this.setStatus("starting", "spawn");
6685
6789
  this.onStatusChange?.();
6686
6790
  }
6687
6791
  // ─── Output state machine ────────────────────────────
@@ -6699,9 +6803,13 @@ var ProviderCliAdapter = class {
6699
6803
  this.recentOutputBuffer = (this.recentOutputBuffer + cleanData).slice(-1e3);
6700
6804
  if (!this.ready) {
6701
6805
  this.startupBuffer += cleanData;
6806
+ LOG.info("CLI", `[${this.cliType}] startup chunk (${cleanData.length} chars): ${cleanData.slice(0, 200).replace(/\n/g, "\\n")}`);
6702
6807
  const dialogPatterns = [
6703
6808
  /Do you want to connect/i,
6704
- /Do you trust the files/i
6809
+ /Do you trust the files/i,
6810
+ /Quick safety check/i,
6811
+ /Is this a project/i,
6812
+ /Enter to confirm/i
6705
6813
  ];
6706
6814
  if (dialogPatterns.some((p) => p.test(this.startupBuffer))) {
6707
6815
  setTimeout(() => this.ptyProcess?.write("\r"), this.timeouts.dialogAccept);
@@ -6710,7 +6818,7 @@ var ProviderCliAdapter = class {
6710
6818
  }
6711
6819
  if (patterns.prompt.some((p) => p.test(this.startupBuffer))) {
6712
6820
  this.ready = true;
6713
- this.currentStatus = "idle";
6821
+ this.setStatus("idle", "prompt_matched");
6714
6822
  LOG.info("CLI", `[${this.cliType}] \u2713 Ready`);
6715
6823
  this.onStatusChange?.();
6716
6824
  }
@@ -6721,12 +6829,12 @@ var ProviderCliAdapter = class {
6721
6829
  if (this.lastApprovalResolvedAt && Date.now() - this.lastApprovalResolvedAt < this.timeouts.approvalCooldown) return;
6722
6830
  const ctxLines = this.recentOutputBuffer.split("\n").map((l) => l.trim()).filter((l) => l && !/^[─═╭╮╰╯│]+$/.test(l));
6723
6831
  this.isWaitingForResponse = true;
6724
- this.currentStatus = "waiting_approval";
6832
+ this.setStatus("waiting_approval", "approval_pattern");
6725
6833
  this.recentOutputBuffer = "";
6726
6834
  this.approvalTransitionBuffer = "";
6727
6835
  this.activeModal = {
6728
6836
  message: ctxLines.slice(-5).join(" ").slice(0, 200) || "Approval required",
6729
- buttons: ["Allow once", "Always allow", "Deny"]
6837
+ buttons: this.cliType === "claude-cli" ? ["Yes (y)", "Always allow (a)", "Deny (Esc)"] : ["Allow once", "Always allow", "Deny"]
6730
6838
  };
6731
6839
  if (this.idleTimeout) clearTimeout(this.idleTimeout);
6732
6840
  if (this.approvalExitTimeout) clearTimeout(this.approvalExitTimeout);
@@ -6738,7 +6846,7 @@ var ProviderCliAdapter = class {
6738
6846
  this.recentOutputBuffer = "";
6739
6847
  this.approvalTransitionBuffer = "";
6740
6848
  this.approvalExitTimeout = null;
6741
- this.currentStatus = this.isWaitingForResponse ? "generating" : "idle";
6849
+ this.setStatus(this.isWaitingForResponse ? "generating" : "idle", "approval_cleared");
6742
6850
  this.onStatusChange?.();
6743
6851
  }
6744
6852
  }, 6e4);
@@ -6754,7 +6862,7 @@ var ProviderCliAdapter = class {
6754
6862
  clearTimeout(this.approvalExitTimeout);
6755
6863
  this.approvalExitTimeout = null;
6756
6864
  }
6757
- this.currentStatus = "generating";
6865
+ this.setStatus("generating", "approval_gen_resume");
6758
6866
  this.activeModal = null;
6759
6867
  this.recentOutputBuffer = "";
6760
6868
  this.approvalTransitionBuffer = "";
@@ -6777,7 +6885,7 @@ var ProviderCliAdapter = class {
6777
6885
  if (patterns.generating.some((p) => p.test(cleanData))) {
6778
6886
  this.isWaitingForResponse = true;
6779
6887
  this.responseBuffer = "";
6780
- this.currentStatus = "generating";
6888
+ this.setStatus("generating", "autonomous_gen");
6781
6889
  this.onStatusChange?.();
6782
6890
  }
6783
6891
  }
@@ -6786,7 +6894,7 @@ var ProviderCliAdapter = class {
6786
6894
  if (this.idleTimeout) clearTimeout(this.idleTimeout);
6787
6895
  const stillGenerating = patterns.generating.some((p) => p.test(cleanData));
6788
6896
  if (stillGenerating) {
6789
- this.currentStatus = "generating";
6897
+ this.setStatus("generating", "still_generating");
6790
6898
  this.idleTimeout = setTimeout(() => {
6791
6899
  if (this.isWaitingForResponse) this.finishResponse();
6792
6900
  }, this.timeouts.generatingIdle);
@@ -6833,7 +6941,7 @@ var ProviderCliAdapter = class {
6833
6941
  this.responseBuffer = "";
6834
6942
  this.isWaitingForResponse = false;
6835
6943
  this.activeModal = null;
6836
- this.currentStatus = "idle";
6944
+ this.setStatus("idle", "response_finished");
6837
6945
  this.onStatusChange?.();
6838
6946
  }
6839
6947
  // ─── Public API (CliAdapter interface) ──────────
@@ -6852,7 +6960,7 @@ var ProviderCliAdapter = class {
6852
6960
  this.messages.push({ role: "user", content: text, timestamp: Date.now() });
6853
6961
  this.isWaitingForResponse = true;
6854
6962
  this.responseBuffer = "";
6855
- this.currentStatus = "generating";
6963
+ this.setStatus("generating", "sendMessage");
6856
6964
  this.onStatusChange?.();
6857
6965
  this.ptyProcess.write(text + "\r");
6858
6966
  this.responseTimeout = setTimeout(() => {
@@ -6880,7 +6988,7 @@ var ProviderCliAdapter = class {
6880
6988
  } catch {
6881
6989
  }
6882
6990
  this.ptyProcess = null;
6883
- this.currentStatus = "stopped";
6991
+ this.setStatus("stopped", "stop_cmd");
6884
6992
  this.ready = false;
6885
6993
  this.onStatusChange?.();
6886
6994
  }, this.timeouts.shutdownGrace);
@@ -6906,9 +7014,13 @@ var ProviderCliAdapter = class {
6906
7014
  */
6907
7015
  resolveModal(buttonIndex) {
6908
7016
  if (!this.ptyProcess || this.currentStatus !== "waiting_approval") return;
6909
- const DOWN = "\x1B[B";
6910
- const keys = DOWN.repeat(Math.max(0, buttonIndex)) + "\r";
6911
- this.ptyProcess.write(keys);
7017
+ if (buttonIndex in this.approvalKeys) {
7018
+ this.ptyProcess.write(this.approvalKeys[buttonIndex]);
7019
+ } else {
7020
+ const DOWN = "\x1B[B";
7021
+ const keys = DOWN.repeat(Math.max(0, buttonIndex)) + "\r";
7022
+ this.ptyProcess.write(keys);
7023
+ }
6912
7024
  }
6913
7025
  resize(cols, rows) {
6914
7026
  if (this.ptyProcess) {
@@ -6919,6 +7031,44 @@ var ProviderCliAdapter = class {
6919
7031
  }
6920
7032
  }
6921
7033
  }
7034
+ /**
7035
+ * Full debug state — exposes all internal buffers, status, and patterns for debugging.
7036
+ * Used by DevServer /api/cli/debug endpoint.
7037
+ */
7038
+ getDebugState() {
7039
+ return {
7040
+ type: this.cliType,
7041
+ name: this.cliName,
7042
+ status: this.currentStatus,
7043
+ ready: this.ready,
7044
+ workingDir: this.workingDir,
7045
+ messages: this.messages.slice(-20),
7046
+ messageCount: this.messages.length,
7047
+ // Buffers
7048
+ startupBuffer: this.startupBuffer.slice(-500),
7049
+ recentOutputBuffer: this.recentOutputBuffer.slice(-500),
7050
+ responseBuffer: this.responseBuffer.slice(-500),
7051
+ approvalTransitionBuffer: this.approvalTransitionBuffer.slice(-500),
7052
+ // State
7053
+ isWaitingForResponse: this.isWaitingForResponse,
7054
+ activeModal: this.activeModal,
7055
+ lastApprovalResolvedAt: this.lastApprovalResolvedAt,
7056
+ resizeSuppressUntil: this.resizeSuppressUntil,
7057
+ // Provider patterns (serialized)
7058
+ patterns: {
7059
+ prompt: this.provider.patterns.prompt.map((p) => p.toString()),
7060
+ generating: this.provider.patterns.generating.map((p) => p.toString()),
7061
+ approval: this.provider.patterns.approval.map((p) => p.toString()),
7062
+ ready: this.provider.patterns.ready.map((p) => p.toString())
7063
+ },
7064
+ // Status history
7065
+ statusHistory: this.statusHistory.slice(-30),
7066
+ // Timeouts config
7067
+ timeouts: this.timeouts,
7068
+ // PTY alive
7069
+ ptyAlive: !!this.ptyProcess
7070
+ };
7071
+ }
6922
7072
  };
6923
7073
 
6924
7074
  // src/commands/cli-manager.ts
@@ -6947,6 +7097,8 @@ var CliProviderInstance = class {
6947
7097
  generatingStartedAt = 0;
6948
7098
  settings = {};
6949
7099
  monitor;
7100
+ generatingDebounceTimer = null;
7101
+ generatingDebouncePending = null;
6950
7102
  historyWriter;
6951
7103
  instanceId;
6952
7104
  // ─── Lifecycle ─────────────────────────────────
@@ -7013,7 +7165,7 @@ var CliProviderInstance = class {
7013
7165
  activeModal: adapterStatus.activeModal,
7014
7166
  inputContent: ""
7015
7167
  },
7016
- workingDir: this.workingDir,
7168
+ workspace: this.workingDir,
7017
7169
  instanceId: this.instanceId,
7018
7170
  lastUpdated: Date.now(),
7019
7171
  settings: this.settings,
@@ -7042,8 +7194,24 @@ var CliProviderInstance = class {
7042
7194
  LOG.info("CLI", `[${this.type}] status: ${this.lastStatus} \u2192 ${newStatus}`);
7043
7195
  if (this.lastStatus === "idle" && newStatus === "generating") {
7044
7196
  this.generatingStartedAt = now;
7045
- this.pushEvent({ event: "agent:generating_started", chatTitle, timestamp: now });
7197
+ if (this.generatingDebounceTimer) clearTimeout(this.generatingDebounceTimer);
7198
+ this.generatingDebouncePending = { chatTitle, timestamp: now };
7199
+ this.generatingDebounceTimer = setTimeout(() => {
7200
+ if (this.generatingDebouncePending) {
7201
+ this.pushEvent({ event: "agent:generating_started", ...this.generatingDebouncePending });
7202
+ this.generatingDebouncePending = null;
7203
+ }
7204
+ this.generatingDebounceTimer = null;
7205
+ }, 1e3);
7046
7206
  } else if (newStatus === "waiting_approval") {
7207
+ if (this.generatingDebouncePending) {
7208
+ if (this.generatingDebounceTimer) {
7209
+ clearTimeout(this.generatingDebounceTimer);
7210
+ this.generatingDebounceTimer = null;
7211
+ }
7212
+ this.pushEvent({ event: "agent:generating_started", ...this.generatingDebouncePending });
7213
+ this.generatingDebouncePending = null;
7214
+ }
7047
7215
  if (!this.generatingStartedAt) this.generatingStartedAt = now;
7048
7216
  const modal = adapterStatus.activeModal;
7049
7217
  LOG.info("CLI", `[${this.type}] approval modal: "${modal?.message?.slice(0, 80) ?? "none"}"`);
@@ -7056,10 +7224,24 @@ var CliProviderInstance = class {
7056
7224
  });
7057
7225
  } else if (newStatus === "idle" && (this.lastStatus === "generating" || this.lastStatus === "waiting_approval")) {
7058
7226
  const duration = this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1e3) : 0;
7059
- LOG.info("CLI", `[${this.type}] completed in ${duration}s`);
7060
- this.pushEvent({ event: "agent:generating_completed", chatTitle, duration, timestamp: now });
7227
+ if (this.generatingDebouncePending) {
7228
+ LOG.info("CLI", `[${this.type}] suppressed short generating (${now - this.generatingStartedAt}ms)`);
7229
+ if (this.generatingDebounceTimer) {
7230
+ clearTimeout(this.generatingDebounceTimer);
7231
+ this.generatingDebounceTimer = null;
7232
+ }
7233
+ this.generatingDebouncePending = null;
7234
+ } else {
7235
+ LOG.info("CLI", `[${this.type}] completed in ${duration}s`);
7236
+ this.pushEvent({ event: "agent:generating_completed", chatTitle, duration, timestamp: now });
7237
+ }
7061
7238
  this.generatingStartedAt = 0;
7062
7239
  } else if (newStatus === "stopped") {
7240
+ if (this.generatingDebounceTimer) {
7241
+ clearTimeout(this.generatingDebounceTimer);
7242
+ this.generatingDebounceTimer = null;
7243
+ }
7244
+ this.generatingDebouncePending = null;
7063
7245
  this.pushEvent({ event: "agent:stopped", chatTitle, timestamp: now });
7064
7246
  }
7065
7247
  this.lastStatus = newStatus;
@@ -7210,7 +7392,7 @@ var AcpProviderInstance = class {
7210
7392
  } : null,
7211
7393
  inputContent: ""
7212
7394
  },
7213
- workingDir: this.workingDir,
7395
+ workspace: this.workingDir,
7214
7396
  currentModel: this.currentModel,
7215
7397
  currentPlan: this.currentMode,
7216
7398
  instanceId: this.instanceId,
@@ -7221,8 +7403,8 @@ var AcpProviderInstance = class {
7221
7403
  acpConfigOptions: this.configOptions,
7222
7404
  acpModes: this.availableModes,
7223
7405
  // Error details for dashboard display
7224
- errorMessage: this.errorMessage,
7225
- errorReason: this.errorReason
7406
+ errorMessage: this.errorMessage || void 0,
7407
+ errorReason: this.errorReason || void 0
7226
7408
  };
7227
7409
  }
7228
7410
  onEvent(event, data) {
@@ -7705,29 +7887,27 @@ var AcpProviderInstance = class {
7705
7887
  handleSessionUpdate(params) {
7706
7888
  if (!params) return;
7707
7889
  const update = params.update;
7708
- this.log.debug(`[${this.type}] sessionUpdate: ${update.sessionUpdate} | keys=${Object.keys(update).join(",")}`);
7890
+ this.log.debug(`[${this.type}] sessionUpdate: ${update.sessionUpdate}`);
7709
7891
  switch (update.sessionUpdate) {
7710
7892
  case "agent_message_chunk": {
7711
7893
  const content = update.content;
7712
- if (content?.type === "text" && content.text) {
7894
+ if (content.type === "text") {
7713
7895
  this.partialContent += content.text;
7714
- } else if (content?.type === "image") {
7896
+ } else if (content.type === "image") {
7715
7897
  this.partialBlocks.push({
7716
7898
  type: "image",
7717
- data: content.data || "",
7718
- mimeType: content.mimeType || "image/png",
7719
- uri: content.uri
7899
+ data: content.data,
7900
+ mimeType: content.mimeType
7720
7901
  });
7721
- } else if (content?.type === "resource_link") {
7902
+ } else if (content.type === "resource_link") {
7722
7903
  this.partialBlocks.push({
7723
7904
  type: "resource_link",
7724
- uri: content.uri || "",
7905
+ uri: content.uri,
7725
7906
  name: content.name || "resource",
7726
- title: content.title,
7727
- mimeType: content.mimeType,
7728
- size: content.size
7907
+ title: content.title ?? void 0,
7908
+ mimeType: content.mimeType ?? void 0
7729
7909
  });
7730
- } else if (content?.type === "resource") {
7910
+ } else if (content.type === "resource") {
7731
7911
  this.partialBlocks.push({
7732
7912
  type: "resource",
7733
7913
  resource: content.resource
@@ -7899,7 +8079,7 @@ var AcpProviderInstance = class {
7899
8079
  return { ...b, text: b.text.slice(0, -3) };
7900
8080
  }
7901
8081
  return b;
7902
- }).filter((b) => b.type !== "text" || b.text.trim());
8082
+ }).filter((b) => b.type !== "text" || b.type === "text" && b.text.trim());
7903
8083
  if (finalBlocks.length > 0) {
7904
8084
  this.messages.push({
7905
8085
  role: "assistant",
@@ -9460,6 +9640,8 @@ var DevServer = class _DevServer {
9460
9640
  server = null;
9461
9641
  providerLoader;
9462
9642
  cdpManagers;
9643
+ instanceManager;
9644
+ cliManager;
9463
9645
  logFn;
9464
9646
  sseClients = [];
9465
9647
  watchScriptPath = null;
@@ -9469,9 +9651,13 @@ var DevServer = class _DevServer {
9469
9651
  autoImplProcess = null;
9470
9652
  autoImplSSEClients = [];
9471
9653
  autoImplStatus = { running: false, type: null, progress: [] };
9654
+ // CLI debug SSE
9655
+ cliSSEClients = [];
9472
9656
  constructor(options) {
9473
9657
  this.providerLoader = options.providerLoader;
9474
9658
  this.cdpManagers = options.cdpManagers;
9659
+ this.instanceManager = options.instanceManager || null;
9660
+ this.cliManager = options.cliManager || null;
9475
9661
  this.logFn = options.logFn || LOG.forComponent("DevServer").asLogFn();
9476
9662
  }
9477
9663
  log(msg) {
@@ -9498,6 +9684,15 @@ var DevServer = class _DevServer {
9498
9684
  { method: "POST", pattern: "/api/watch/stop", handler: (q, s) => this.handleWatchStop(q, s) },
9499
9685
  { method: "GET", pattern: "/api/watch/events", handler: (q, s) => this.handleSSE(q, s) },
9500
9686
  { method: "POST", pattern: "/api/scaffold", handler: (q, s) => this.handleScaffold(q, s) },
9687
+ // CLI Debug routes
9688
+ { method: "GET", pattern: "/api/cli/status", handler: (q, s) => this.handleCliStatus(q, s) },
9689
+ { method: "POST", pattern: "/api/cli/launch", handler: (q, s) => this.handleCliLaunch(q, s) },
9690
+ { method: "POST", pattern: "/api/cli/send", handler: (q, s) => this.handleCliSend(q, s) },
9691
+ { method: "POST", pattern: "/api/cli/resolve", handler: (q, s) => this.handleCliResolve(q, s) },
9692
+ { method: "POST", pattern: "/api/cli/raw", handler: (q, s) => this.handleCliRaw(q, s) },
9693
+ { method: "POST", pattern: "/api/cli/stop", handler: (q, s) => this.handleCliStop(q, s) },
9694
+ { method: "GET", pattern: "/api/cli/events", handler: (q, s) => this.handleCliSSE(q, s) },
9695
+ { method: "GET", pattern: /^\/api\/cli\/debug\/([^/]+)$/, handler: (q, s, p) => this.handleCliDebug(p[0], q, s) },
9501
9696
  // Dynamic routes (provider :type param)
9502
9697
  { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/script$/, handler: (q, s, p) => this.handleRunScript(p[0], q, s) },
9503
9698
  { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/files$/, handler: (q, s, p) => this.handleListFiles(p[0], q, s) },
@@ -11764,6 +11959,251 @@ data: ${JSON.stringify(msg.data)}
11764
11959
  });
11765
11960
  });
11766
11961
  }
11962
+ // ─── CLI Debug Handlers ──────────────────────────────
11963
+ /** GET /api/cli/status — list all running CLI/ACP instances with state */
11964
+ async handleCliStatus(_req, res) {
11965
+ if (!this.instanceManager) {
11966
+ this.json(res, 503, { error: "InstanceManager not available (daemon not fully initialized)" });
11967
+ return;
11968
+ }
11969
+ const allStates = this.instanceManager.collectAllStates();
11970
+ const cliStates = allStates.filter((s) => s.category === "cli" || s.category === "acp");
11971
+ const result = cliStates.map((s) => ({
11972
+ instanceId: s.instanceId,
11973
+ type: s.type,
11974
+ name: s.name,
11975
+ category: s.category,
11976
+ status: s.status,
11977
+ mode: s.mode,
11978
+ workspace: s.workspace,
11979
+ messageCount: s.activeChat?.messages?.length || 0,
11980
+ lastMessage: s.activeChat?.messages?.slice(-1)[0] || null,
11981
+ activeModal: s.activeChat?.activeModal || null,
11982
+ pendingEvents: s.pendingEvents || [],
11983
+ currentModel: s.currentModel,
11984
+ settings: s.settings
11985
+ }));
11986
+ this.json(res, 200, { instances: result, count: result.length });
11987
+ }
11988
+ /** POST /api/cli/launch — launch a CLI agent { type, workingDir?, args? } */
11989
+ async handleCliLaunch(req, res) {
11990
+ if (!this.cliManager) {
11991
+ this.json(res, 503, { error: "CliManager not available" });
11992
+ return;
11993
+ }
11994
+ const body = await this.readBody(req);
11995
+ const { type, workingDir, args } = body;
11996
+ if (!type) {
11997
+ this.json(res, 400, { error: "type required (e.g. claude-cli, gemini-cli)" });
11998
+ return;
11999
+ }
12000
+ try {
12001
+ await this.cliManager.startSession(type, workingDir || process.cwd(), args || []);
12002
+ this.json(res, 200, { launched: true, type, workspace: workingDir || process.cwd() });
12003
+ } catch (e) {
12004
+ this.json(res, 500, { error: `Launch failed: ${e.message}` });
12005
+ }
12006
+ }
12007
+ /** POST /api/cli/send — send message to a running CLI { type, text } */
12008
+ async handleCliSend(req, res) {
12009
+ if (!this.instanceManager) {
12010
+ this.json(res, 503, { error: "InstanceManager not available" });
12011
+ return;
12012
+ }
12013
+ const body = await this.readBody(req);
12014
+ const { type, text, instanceId } = body;
12015
+ if (!text) {
12016
+ this.json(res, 400, { error: "text required" });
12017
+ return;
12018
+ }
12019
+ const allStates = this.instanceManager.collectAllStates();
12020
+ const target = allStates.find(
12021
+ (s) => (s.category === "cli" || s.category === "acp") && (instanceId ? s.instanceId === instanceId : s.type === type)
12022
+ );
12023
+ if (!target) {
12024
+ this.json(res, 404, { error: `No running instance found for: ${type || instanceId}` });
12025
+ return;
12026
+ }
12027
+ try {
12028
+ this.instanceManager.sendEvent(target.instanceId, "send_message", { text });
12029
+ this.json(res, 200, { sent: true, type: target.type, instanceId: target.instanceId });
12030
+ } catch (e) {
12031
+ this.json(res, 500, { error: `Send failed: ${e.message}` });
12032
+ }
12033
+ }
12034
+ /** POST /api/cli/stop — stop a running CLI { type } */
12035
+ async handleCliStop(req, res) {
12036
+ if (!this.instanceManager) {
12037
+ this.json(res, 503, { error: "InstanceManager not available" });
12038
+ return;
12039
+ }
12040
+ const body = await this.readBody(req);
12041
+ const { type, instanceId } = body;
12042
+ const allStates = this.instanceManager.collectAllStates();
12043
+ const target = allStates.find(
12044
+ (s) => (s.category === "cli" || s.category === "acp") && (instanceId ? s.instanceId === instanceId : s.type === type)
12045
+ );
12046
+ if (!target) {
12047
+ this.json(res, 404, { error: `No running instance found for: ${type || instanceId}` });
12048
+ return;
12049
+ }
12050
+ try {
12051
+ this.instanceManager.removeInstance(target.instanceId);
12052
+ this.json(res, 200, { stopped: true, type: target.type, instanceId: target.instanceId });
12053
+ } catch (e) {
12054
+ this.json(res, 500, { error: `Stop failed: ${e.message}` });
12055
+ }
12056
+ }
12057
+ /** GET /api/cli/events — SSE stream of CLI status events */
12058
+ handleCliSSE(_req, res) {
12059
+ res.writeHead(200, {
12060
+ "Content-Type": "text/event-stream",
12061
+ "Cache-Control": "no-cache",
12062
+ "Connection": "keep-alive",
12063
+ "Access-Control-Allow-Origin": "*"
12064
+ });
12065
+ res.write('data: {"type":"connected"}\n\n');
12066
+ this.cliSSEClients.push(res);
12067
+ if (this.cliSSEClients.length === 1 && this.instanceManager) {
12068
+ this.instanceManager.onEvent((event) => {
12069
+ this.sendCliSSE(event);
12070
+ });
12071
+ }
12072
+ if (this.instanceManager) {
12073
+ const allStates = this.instanceManager.collectAllStates();
12074
+ const cliStates = allStates.filter((s) => s.category === "cli" || s.category === "acp");
12075
+ for (const s of cliStates) {
12076
+ this.sendCliSSE({ event: "snapshot", providerType: s.type, status: s.status, instanceId: s.instanceId });
12077
+ }
12078
+ }
12079
+ _req.on("close", () => {
12080
+ this.cliSSEClients = this.cliSSEClients.filter((c) => c !== res);
12081
+ });
12082
+ }
12083
+ sendCliSSE(data) {
12084
+ const msg = `data: ${JSON.stringify({ ...data, timestamp: Date.now() })}
12085
+
12086
+ `;
12087
+ for (const client of this.cliSSEClients) {
12088
+ try {
12089
+ client.write(msg);
12090
+ } catch {
12091
+ }
12092
+ }
12093
+ }
12094
+ /** GET /api/cli/debug/:type — full internal debug state of a CLI adapter */
12095
+ async handleCliDebug(type, _req, res) {
12096
+ if (!this.instanceManager) {
12097
+ this.json(res, 503, { error: "InstanceManager not available" });
12098
+ return;
12099
+ }
12100
+ const allStates = this.instanceManager.collectAllStates();
12101
+ const target = allStates.find(
12102
+ (s) => (s.category === "cli" || s.category === "acp") && s.type === type
12103
+ );
12104
+ if (!target) {
12105
+ this.json(res, 404, { error: `No running instance for: ${type}`, available: allStates.filter((s) => s.category === "cli" || s.category === "acp").map((s) => s.type) });
12106
+ return;
12107
+ }
12108
+ const instance = this.instanceManager.getInstance(target.instanceId);
12109
+ if (!instance) {
12110
+ this.json(res, 404, { error: `Instance not found: ${target.instanceId}` });
12111
+ return;
12112
+ }
12113
+ try {
12114
+ const adapter = instance.getAdapter?.() || instance.adapter;
12115
+ if (adapter && typeof adapter.getDebugState === "function") {
12116
+ const debugState = adapter.getDebugState();
12117
+ this.json(res, 200, {
12118
+ instanceId: target.instanceId,
12119
+ providerState: {
12120
+ type: target.type,
12121
+ name: target.name,
12122
+ status: target.status,
12123
+ mode: "mode" in target ? target.mode : void 0
12124
+ },
12125
+ debug: debugState
12126
+ });
12127
+ } else {
12128
+ this.json(res, 200, {
12129
+ instanceId: target.instanceId,
12130
+ providerState: target,
12131
+ debug: null,
12132
+ message: "No debug state available (adapter.getDebugState not found)"
12133
+ });
12134
+ }
12135
+ } catch (e) {
12136
+ this.json(res, 500, { error: `Debug state failed: ${e.message}` });
12137
+ }
12138
+ }
12139
+ /** POST /api/cli/resolve — resolve an approval modal { type, buttonIndex } */
12140
+ async handleCliResolve(req, res) {
12141
+ const body = await this.readBody(req);
12142
+ const { type, buttonIndex, instanceId } = body;
12143
+ if (buttonIndex === void 0 || buttonIndex === null) {
12144
+ this.json(res, 400, { error: "buttonIndex required (0=Yes, 1=Always, 2=Deny)" });
12145
+ return;
12146
+ }
12147
+ if (!this.cliManager) {
12148
+ this.json(res, 503, { error: "CliManager not available" });
12149
+ return;
12150
+ }
12151
+ let adapter = null;
12152
+ for (const [, a] of this.cliManager.adapters) {
12153
+ if (type && a.cliType === type) {
12154
+ adapter = a;
12155
+ break;
12156
+ }
12157
+ }
12158
+ if (!adapter) {
12159
+ this.json(res, 404, { error: `No running adapter for: ${type || instanceId}` });
12160
+ return;
12161
+ }
12162
+ try {
12163
+ if (typeof adapter.resolveModal === "function") {
12164
+ adapter.resolveModal(buttonIndex);
12165
+ this.json(res, 200, { resolved: true, type, buttonIndex });
12166
+ } else {
12167
+ this.json(res, 400, { error: "resolveModal not available on this adapter" });
12168
+ }
12169
+ } catch (e) {
12170
+ this.json(res, 500, { error: `Resolve failed: ${e.message}` });
12171
+ }
12172
+ }
12173
+ /** POST /api/cli/raw — send raw keystrokes to PTY { type, keys } */
12174
+ async handleCliRaw(req, res) {
12175
+ const body = await this.readBody(req);
12176
+ const { type, keys, instanceId } = body;
12177
+ if (!keys) {
12178
+ this.json(res, 400, { error: "keys required (raw string to send to PTY)" });
12179
+ return;
12180
+ }
12181
+ if (!this.cliManager) {
12182
+ this.json(res, 503, { error: "CliManager not available" });
12183
+ return;
12184
+ }
12185
+ let adapter = null;
12186
+ for (const [, a] of this.cliManager.adapters) {
12187
+ if (type && a.cliType === type) {
12188
+ adapter = a;
12189
+ break;
12190
+ }
12191
+ }
12192
+ if (!adapter) {
12193
+ this.json(res, 404, { error: `No running adapter for: ${type || instanceId}` });
12194
+ return;
12195
+ }
12196
+ try {
12197
+ if (typeof adapter.writeRaw === "function") {
12198
+ adapter.writeRaw(keys);
12199
+ this.json(res, 200, { sent: true, type, keysLength: keys.length });
12200
+ } else {
12201
+ this.json(res, 400, { error: "writeRaw not available on this adapter" });
12202
+ }
12203
+ } catch (e) {
12204
+ this.json(res, 500, { error: `Raw send failed: ${e.message}` });
12205
+ }
12206
+ }
11767
12207
  };
11768
12208
 
11769
12209
  // src/installer.ts
@@ -12113,7 +12553,9 @@ async function shutdownDaemonComponents(components) {
12113
12553
  installExtensions,
12114
12554
  installGlobalInterceptor,
12115
12555
  isExtensionInstalled,
12556
+ isIdeRunning,
12116
12557
  isSetupComplete,
12558
+ killIdeProcess,
12117
12559
  launchIDE,
12118
12560
  launchWithCdp,
12119
12561
  loadConfig,