@adhdev/daemon-core 0.6.75 → 0.6.77

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.d.ts CHANGED
@@ -285,7 +285,7 @@ interface ManagedCliEntry {
285
285
  cliType: string;
286
286
  cliName: string;
287
287
  status: string;
288
- mode: 'terminal' | 'chat';
288
+ mode: 'terminal';
289
289
  workspace: string;
290
290
  activeChat: ActiveChatData | null;
291
291
  }
@@ -1075,6 +1075,7 @@ declare class ProviderLoader {
1075
1075
  icon: string;
1076
1076
  command: string;
1077
1077
  category: string;
1078
+ versionCommand?: string;
1078
1079
  }[];
1079
1080
  /**
1080
1081
  * List providers by category
@@ -1245,6 +1246,7 @@ interface CLIInfo {
1245
1246
  displayName: string;
1246
1247
  icon: string;
1247
1248
  command: string;
1249
+ versionCommand?: string;
1248
1250
  installed: boolean;
1249
1251
  version?: string;
1250
1252
  path?: string;
@@ -2710,6 +2712,9 @@ declare class ProviderCliAdapter implements CliAdapter {
2710
2712
  private lastApprovalResolvedAt;
2711
2713
  private approvalTransitionBuffer;
2712
2714
  private approvalExitTimeout;
2715
+ private pendingScriptStatus;
2716
+ private pendingScriptStatusSince;
2717
+ private pendingScriptStatusTimer;
2713
2718
  private settleTimer;
2714
2719
  private settledBuffer;
2715
2720
  private submitPendingUntil;
@@ -2741,6 +2746,7 @@ declare class ProviderCliAdapter implements CliAdapter {
2741
2746
  private readonly sendDelayMs;
2742
2747
  private readonly sendKey;
2743
2748
  private readonly submitStrategy;
2749
+ private static readonly SCRIPT_STATUS_DEBOUNCE_MS;
2744
2750
  constructor(provider: CliProviderModule, workingDir: string, extraArgs?: string[]);
2745
2751
  /** Inject CLI scripts after construction (e.g. when resolved by ProviderLoader) */
2746
2752
  setCliScripts(scripts: CliScripts): void;
package/dist/index.js CHANGED
@@ -692,6 +692,12 @@ __export(provider_cli_adapter_exports, {
692
692
  function stripAnsi(str) {
693
693
  return str.replace(/\x1B\[\d*[A-HJKSTfG]/g, " ").replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "").replace(/\x1B\][^\x07]*\x07/g, "").replace(/\x1B\][^\x1B]*\x1B\\/g, "").replace(/ +/g, " ");
694
694
  }
695
+ function stripTerminalNoise(str) {
696
+ return String(str || "").replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]/g, "").replace(/(^|[\s([])(?:\??\d{1,4}(?:;\d{1,4})*[A-Za-z])(?=$|[\s)\]])/g, "$1").replace(/(^|[\s([])(?:\[\??\d{1,4}(?:;\d{1,4})*[A-Za-z])(?=$|[\s)\]])/g, "$1").replace(/(^|[\s([])(?:\d{1,4};\?)(?=$|[\s)\]])/g, "$1").replace(/\r+/g, "\n").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").replace(/ {2,}/g, " ");
697
+ }
698
+ function sanitizeTerminalText(str) {
699
+ return stripTerminalNoise(stripAnsi(str));
700
+ }
695
701
  function findBinary(name) {
696
702
  const isWin = os11.platform() === "win32";
697
703
  try {
@@ -924,6 +930,9 @@ var init_provider_cli_adapter = __esm({
924
930
  // Approval state machine
925
931
  approvalTransitionBuffer = "";
926
932
  approvalExitTimeout = null;
933
+ pendingScriptStatus = null;
934
+ pendingScriptStatusSince = 0;
935
+ pendingScriptStatusTimer = null;
927
936
  // Output settle debounce — fires after PTY output goes quiet
928
937
  settleTimer = null;
929
938
  settledBuffer = "";
@@ -989,6 +998,7 @@ var init_provider_cli_adapter = __esm({
989
998
  sendDelayMs;
990
999
  sendKey;
991
1000
  submitStrategy;
1001
+ static SCRIPT_STATUS_DEBOUNCE_MS = 1e3;
992
1002
  /** Inject CLI scripts after construction (e.g. when resolved by ProviderLoader) */
993
1003
  setCliScripts(scripts) {
994
1004
  this.cliScripts = scripts;
@@ -1115,7 +1125,7 @@ var init_provider_cli_adapter = __esm({
1115
1125
  handleOutput(rawData) {
1116
1126
  this.terminalScreen.write(rawData);
1117
1127
  this.terminalHistory = mergeTerminalHistory(this.terminalHistory, this.terminalScreen.getText());
1118
- const cleanData = stripAnsi(rawData);
1128
+ const cleanData = sanitizeTerminalText(rawData);
1119
1129
  if (this.isWaitingForResponse && cleanData) {
1120
1130
  this.responseBuffer = (this.responseBuffer + cleanData).slice(-8e3);
1121
1131
  }
@@ -1212,24 +1222,39 @@ var init_provider_cli_adapter = __esm({
1212
1222
  const scriptStatus = rawScriptStatus;
1213
1223
  if (!scriptStatus) return;
1214
1224
  const prevStatus = this.currentStatus;
1215
- if (scriptStatus === "waiting_approval") {
1216
- const modalMessage = modal?.message || "";
1217
- const screenText = this.terminalScreen.getText() || this.accumulatedBuffer;
1218
- const autoAcceptPatterns = [
1219
- /be able to read, edit, and execute/i,
1220
- /Security guide/i,
1221
- /Enter to confirm/i,
1222
- /Quick safety check/i,
1223
- /Do you trust the files/i,
1224
- /Is this a project/i
1225
- ];
1226
- if (autoAcceptPatterns.some((p) => p.test(modalMessage) || p.test(screenText))) {
1227
- LOG.info("CLI", `[${this.cliType}] Auto-accepting startup dialog: ${modalMessage.slice(0, 80)}`);
1228
- setTimeout(() => this.ptyProcess?.write("\r"), 200);
1229
- this.lastApprovalResolvedAt = Date.now();
1230
- this.activeModal = null;
1225
+ const clearPendingScriptStatus = () => {
1226
+ this.pendingScriptStatus = null;
1227
+ this.pendingScriptStatusSince = 0;
1228
+ if (this.pendingScriptStatusTimer) {
1229
+ clearTimeout(this.pendingScriptStatusTimer);
1230
+ this.pendingScriptStatusTimer = null;
1231
+ }
1232
+ };
1233
+ const armPendingScriptStatus = (delayMs) => {
1234
+ if (this.pendingScriptStatusTimer) clearTimeout(this.pendingScriptStatusTimer);
1235
+ this.pendingScriptStatusTimer = setTimeout(() => {
1236
+ this.pendingScriptStatusTimer = null;
1237
+ this.settledBuffer = this.recentOutputBuffer;
1238
+ this.evaluateSettled();
1239
+ }, delayMs);
1240
+ };
1241
+ const shouldDebouncePromotion = (status) => prevStatus === "idle" && !this.isWaitingForResponse && !this.currentTurnScope && (status === "generating" || status === "waiting_approval");
1242
+ if (shouldDebouncePromotion(scriptStatus)) {
1243
+ if (this.pendingScriptStatus !== scriptStatus) {
1244
+ this.pendingScriptStatus = scriptStatus;
1245
+ this.pendingScriptStatusSince = now;
1246
+ armPendingScriptStatus(_ProviderCliAdapter.SCRIPT_STATUS_DEBOUNCE_MS);
1247
+ return;
1248
+ }
1249
+ const elapsed = now - this.pendingScriptStatusSince;
1250
+ if (elapsed < _ProviderCliAdapter.SCRIPT_STATUS_DEBOUNCE_MS) {
1251
+ armPendingScriptStatus(_ProviderCliAdapter.SCRIPT_STATUS_DEBOUNCE_MS - elapsed);
1231
1252
  return;
1232
1253
  }
1254
+ } else {
1255
+ clearPendingScriptStatus();
1256
+ }
1257
+ if (scriptStatus === "waiting_approval") {
1233
1258
  const inCooldown = this.lastApprovalResolvedAt && Date.now() - this.lastApprovalResolvedAt < this.timeouts.approvalCooldown;
1234
1259
  if (!inCooldown) {
1235
1260
  this.isWaitingForResponse = true;
@@ -1242,6 +1267,12 @@ var init_provider_cli_adapter = __esm({
1242
1267
  }
1243
1268
  }
1244
1269
  if (scriptStatus === "generating") {
1270
+ const screenText = this.terminalScreen.getText() || this.accumulatedBuffer;
1271
+ const noActiveTurn = !this.currentTurnScope;
1272
+ const looksIdleChrome = /(^|\n)\s*[❯›>]\s*(?:\n|$)/m.test(screenText) || /accept edits on/i.test(screenText) && (/Update available!/i.test(screenText) || /\/effort/i.test(screenText) || /^.*➜\s+\S+/m.test(screenText));
1273
+ if (prevStatus === "idle" && !this.isWaitingForResponse && noActiveTurn && !modal && looksIdleChrome) {
1274
+ return;
1275
+ }
1245
1276
  if (prevStatus === "waiting_approval") {
1246
1277
  if (this.approvalExitTimeout) {
1247
1278
  clearTimeout(this.approvalExitTimeout);
@@ -1699,7 +1730,7 @@ ${data.message || ""}`.trim();
1699
1730
  committedMessages: this.committedMessages.slice(-20),
1700
1731
  structuredMessages: this.structuredMessages.slice(-20),
1701
1732
  messageCount: this.committedMessages.length,
1702
- screenText: this.terminalScreen.getText().slice(-4e3),
1733
+ screenText: sanitizeTerminalText(this.terminalScreen.getText()).slice(-4e3),
1703
1734
  terminalHistory: this.terminalHistory.slice(-8e3),
1704
1735
  currentTurnScope: this.currentTurnScope,
1705
1736
  startupBuffer: this.startupBuffer.slice(-4e3),
@@ -1708,6 +1739,7 @@ ${data.message || ""}`.trim();
1708
1739
  accumulatedBufferLength: this.accumulatedBuffer.length,
1709
1740
  accumulatedRawBufferLength: this.accumulatedRawBuffer.length,
1710
1741
  rawBufferPreview: this.accumulatedRawBuffer.slice(-1e3),
1742
+ sanitizedRawPreview: sanitizeTerminalText(this.accumulatedRawBuffer).slice(-1e3),
1711
1743
  responseBuffer: this.responseBuffer.slice(-1e3),
1712
1744
  isWaitingForResponse: this.isWaitingForResponse,
1713
1745
  activeModal: this.activeModal,
@@ -1946,6 +1978,10 @@ async function detectIDEs() {
1946
1978
  // src/detection/cli-detector.ts
1947
1979
  var import_child_process2 = require("child_process");
1948
1980
  var os2 = __toESM(require("os"));
1981
+ function parseVersion(raw) {
1982
+ const match = raw.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?)/);
1983
+ return match ? match[1] : raw.split("\n")[0].slice(0, 100);
1984
+ }
1949
1985
  function execAsync(cmd, timeoutMs = 5e3) {
1950
1986
  return new Promise((resolve8) => {
1951
1987
  const child = (0, import_child_process2.exec)(cmd, { encoding: "utf-8", timeout: timeoutMs }, (err, stdout) => {
@@ -1970,10 +2006,18 @@ async function detectCLIs(providerLoader) {
1970
2006
  const firstPath = pathResult.split("\n")[0];
1971
2007
  let version;
1972
2008
  try {
1973
- const versionResult = await execAsync(`${cli.command} --version 2>/dev/null`, 3e3);
1974
- if (versionResult) {
1975
- const match = versionResult.match(/(\d+\.\d+[\.\d]*)/);
1976
- version = match ? match[1] : versionResult.split("\n")[0].slice(0, 30);
2009
+ const versionCommands = [
2010
+ cli.versionCommand,
2011
+ `${cli.command} --version 2>/dev/null`,
2012
+ `${cli.command} -V 2>/dev/null`,
2013
+ `${cli.command} -v 2>/dev/null`
2014
+ ].filter((v) => !!v);
2015
+ for (const versionCommand of versionCommands) {
2016
+ const versionResult = await execAsync(versionCommand, 3e3);
2017
+ if (versionResult) {
2018
+ version = parseVersion(versionResult);
2019
+ break;
2020
+ }
1977
2021
  }
1978
2022
  } catch {
1979
2023
  }
@@ -4479,7 +4523,7 @@ function buildManagedClis(cliStates) {
4479
4523
  cliType: s.type,
4480
4524
  cliName: s.name,
4481
4525
  status: s.status,
4482
- mode: s.mode,
4526
+ mode: "terminal",
4483
4527
  workspace: s.workspace || "",
4484
4528
  activeChat: s.activeChat
4485
4529
  }));
@@ -6035,8 +6079,13 @@ var DaemonCommandHandler = class {
6035
6079
  getCliAdapter(type) {
6036
6080
  const target = type || this._currentIdeType;
6037
6081
  if (!target || !this._ctx.adapters) return null;
6082
+ let normalizedTarget = target;
6083
+ const colonIdx = normalizedTarget.lastIndexOf(":");
6084
+ if (colonIdx >= 0) normalizedTarget = normalizedTarget.substring(colonIdx + 1);
6085
+ const direct = this._ctx.adapters.get(normalizedTarget);
6086
+ if (direct) return direct;
6038
6087
  for (const [key, adapter] of this._ctx.adapters.entries()) {
6039
- if (adapter.cliType === target || key.startsWith(target)) {
6088
+ if (adapter.cliType === target || adapter.cliType === normalizedTarget || key === normalizedTarget || key.startsWith(target) || key.startsWith(normalizedTarget)) {
6040
6089
  return adapter;
6041
6090
  }
6042
6091
  }
@@ -6549,12 +6598,15 @@ var ProviderLoader = class _ProviderLoader {
6549
6598
  const result = [];
6550
6599
  for (const p of this.providers.values()) {
6551
6600
  if ((p.category === "cli" || p.category === "acp") && p.spawn?.command) {
6601
+ const verCmdConfig = p.versionCommand;
6602
+ const versionCommand = typeof verCmdConfig === "object" && verCmdConfig !== null ? verCmdConfig[process.platform] : verCmdConfig;
6552
6603
  result.push({
6553
6604
  id: p.type,
6554
6605
  displayName: p.displayName || p.name,
6555
6606
  icon: p.icon || "\u{1F527}",
6556
6607
  command: p.spawn.command,
6557
- category: p.category
6608
+ category: p.category,
6609
+ ...typeof versionCommand === "string" && versionCommand.trim() ? { versionCommand: versionCommand.trim() } : {}
6558
6610
  });
6559
6611
  }
6560
6612
  }
@@ -8472,19 +8524,6 @@ var CliProviderInstance = class {
8472
8524
  getState() {
8473
8525
  const adapterStatus = this.adapter.getStatus();
8474
8526
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
8475
- const recentMessages = adapterStatus.messages.slice(-50).map((m) => {
8476
- const content = typeof m.content === "string" && m.content.length > 8e3 ? m.content.slice(0, 8e3) + "\n... (truncated)" : m.content;
8477
- return { ...m, content };
8478
- });
8479
- if (recentMessages.length > 0) {
8480
- const dirName2 = this.workingDir.split("/").filter(Boolean).pop() || "session";
8481
- this.historyWriter.appendNewMessages(
8482
- this.type,
8483
- recentMessages,
8484
- `${this.provider.name} \xB7 ${dirName2}`,
8485
- this.instanceId
8486
- );
8487
- }
8488
8527
  if (adapterStatus.terminalHistory?.trim()) {
8489
8528
  this.historyWriter.appendTerminalHistory(
8490
8529
  this.type,
@@ -8498,12 +8537,12 @@ var CliProviderInstance = class {
8498
8537
  name: this.provider.name,
8499
8538
  category: "cli",
8500
8539
  status: adapterStatus.status,
8501
- mode: this.settings.mode || "terminal",
8540
+ mode: "terminal",
8502
8541
  activeChat: {
8503
8542
  id: `${this.type}_${this.workingDir}`,
8504
8543
  title: `${this.provider.name} \xB7 ${dirName}`,
8505
8544
  status: adapterStatus.status,
8506
- messages: recentMessages,
8545
+ messages: [],
8507
8546
  activeModal: adapterStatus.activeModal,
8508
8547
  terminalHistory: adapterStatus.terminalHistory,
8509
8548
  inputContent: ""
@@ -8517,11 +8556,15 @@ var CliProviderInstance = class {
8517
8556
  }
8518
8557
  onEvent(event, data) {
8519
8558
  if (event === "send_message" && data?.text) {
8520
- this.adapter.sendMessage(data.text);
8559
+ void this.adapter.sendMessage(data.text).catch((e) => {
8560
+ LOG.warn("CLI", `[${this.type}] send_message failed: ${e?.message || e}`);
8561
+ });
8521
8562
  } else if (event === "server_connected" && data?.serverConn) {
8522
8563
  this.adapter.setServerConn(data.serverConn);
8523
8564
  } else if (event === "resolve_action" && data) {
8524
- this.adapter.resolveAction(data);
8565
+ void this.adapter.resolveAction(data).catch((e) => {
8566
+ LOG.warn("CLI", `[${this.type}] resolve_action failed: ${e?.message || e}`);
8567
+ });
8525
8568
  }
8526
8569
  }
8527
8570
  dispose() {
@@ -10635,18 +10678,18 @@ function findBinary2(name) {
10635
10678
  const result = runCommand(cmd, 5e3);
10636
10679
  return result ? result.split("\n")[0] : null;
10637
10680
  }
10638
- function parseVersion(raw) {
10681
+ function parseVersion2(raw) {
10639
10682
  const match = raw.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?)/);
10640
10683
  return match ? match[1] : raw.split("\n")[0].substring(0, 100);
10641
10684
  }
10642
10685
  function getVersion(binary, versionCommand) {
10643
10686
  if (versionCommand) {
10644
10687
  const raw = runCommand(versionCommand);
10645
- return raw ? parseVersion(raw) : null;
10688
+ return raw ? parseVersion2(raw) : null;
10646
10689
  }
10647
10690
  for (const flag of ["--version", "-V", "-v"]) {
10648
10691
  const raw = runCommand(`"${binary}" ${flag}`);
10649
- if (raw && raw.length < 500) return parseVersion(raw);
10692
+ if (raw && raw.length < 500) return parseVersion2(raw);
10650
10693
  }
10651
10694
  return null;
10652
10695
  }
@@ -12907,16 +12950,16 @@ var DevServer = class _DevServer {
12907
12950
  resolveAutoImplWritableProviderDir(category, type, requestedDir) {
12908
12951
  const canonicalUserDir = path12.resolve(this.providerLoader.getUserProviderDir(category, type));
12909
12952
  const desiredDir = requestedDir ? path12.resolve(requestedDir) : canonicalUserDir;
12910
- if (desiredDir !== canonicalUserDir) {
12911
- return null;
12953
+ const upstreamRoot = path12.resolve(this.providerLoader.getUpstreamDir());
12954
+ if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${path12.sep}`)) {
12955
+ return { dir: null, reason: `Refusing to write into upstream provider directory: ${desiredDir}` };
12912
12956
  }
12913
- const userRoot = path12.resolve(this.providerLoader.getUserDir());
12914
- if (desiredDir !== userRoot && !desiredDir.startsWith(`${userRoot}${path12.sep}`)) {
12915
- return null;
12957
+ if (path12.basename(desiredDir) !== type) {
12958
+ return { dir: null, reason: `Requested writable provider directory must end with '${type}': ${desiredDir}` };
12916
12959
  }
12917
12960
  const sourceDir = this.findProviderDir(type);
12918
12961
  if (!sourceDir) {
12919
- return null;
12962
+ return { dir: null, reason: `Provider source directory not found for '${type}'` };
12920
12963
  }
12921
12964
  if (!fs9.existsSync(desiredDir)) {
12922
12965
  fs9.mkdirSync(path12.dirname(desiredDir), { recursive: true });
@@ -12925,7 +12968,7 @@ var DevServer = class _DevServer {
12925
12968
  }
12926
12969
  const providerJson = path12.join(desiredDir, "provider.json");
12927
12970
  if (!fs9.existsSync(providerJson)) {
12928
- return null;
12971
+ return { dir: null, reason: `provider.json not found in writable provider directory: ${desiredDir}` };
12929
12972
  }
12930
12973
  try {
12931
12974
  const providerData = JSON.parse(fs9.readFileSync(providerJson, "utf-8"));
@@ -12933,10 +12976,13 @@ var DevServer = class _DevServer {
12933
12976
  providerData.disableUpstream = true;
12934
12977
  fs9.writeFileSync(providerJson, JSON.stringify(providerData, null, 2));
12935
12978
  }
12936
- } catch {
12937
- return null;
12979
+ } catch (error) {
12980
+ return {
12981
+ dir: null,
12982
+ reason: `Failed to update provider.json in writable provider directory: ${error.message}`
12983
+ };
12938
12984
  }
12939
- return desiredDir;
12985
+ return { dir: desiredDir };
12940
12986
  }
12941
12987
  loadAutoImplReferenceScripts(referenceType) {
12942
12988
  if (!referenceType) return {};
@@ -12971,13 +13017,14 @@ var DevServer = class _DevServer {
12971
13017
  this.json(res, 404, { error: `Provider not found: ${type}` });
12972
13018
  return;
12973
13019
  }
12974
- const providerDir = this.resolveAutoImplWritableProviderDir(provider.category, type, requestedProviderDir);
12975
- if (!providerDir) {
13020
+ const writableProvider = this.resolveAutoImplWritableProviderDir(provider.category, type, requestedProviderDir);
13021
+ if (!writableProvider.dir) {
12976
13022
  this.json(res, 409, {
12977
- error: `Auto-implement only writes to the canonical user provider directory for '${type}'.`
13023
+ error: writableProvider.reason || `Auto-implement only writes to the canonical user provider directory for '${type}'.`
12978
13024
  });
12979
13025
  return;
12980
13026
  }
13027
+ const providerDir = writableProvider.dir;
12981
13028
  try {
12982
13029
  const resolvedReference = this.resolveAutoImplReference(provider.category, reference, type);
12983
13030
  this.sendAutoImplSSE({
@@ -13478,14 +13525,16 @@ var DevServer = class _DevServer {
13478
13525
  lines.push("| Status | When to use | How to detect |");
13479
13526
  lines.push("|---|---|---|");
13480
13527
  lines.push("| `idle` | AI is NOT generating, no approval needed | Default state. No stop button, no spinners, no approval pills/buttons |");
13481
- lines.push("| `generating` | AI is actively streaming/thinking | ANY of: (1) Stop/Cancel button visible, (2) CSS animation (animate-spin/pulse/bounce), (3) floating state text like Thinking/Generating/Sailing, (4) streaming indicator class |");
13528
+ lines.push('| `generating` | AI is actively streaming/thinking | ANY of: (1) Submit button icon SVG changes (e.g. arrow\u2192stop square, fill="none"\u2192fill="currentColor"), (2) Stop/Cancel button visible, (3) CSS animation, (4) Structural markers (aria-labels that only appear during generation) |');
13482
13529
  lines.push("| `waiting_approval` | AI stopped and needs user action | Actionable buttons like Run/Skip/Accept/Reject are visible AND clickable |");
13483
13530
  lines.push("");
13484
13531
  lines.push("### \u26A0\uFE0F Status Detection Gotchas (MUST READ!)");
13485
- lines.push('1. **FALSE POSITIVES from old messages**: Chat history may contain text like "Command Awaiting Approval" from PAST turns. If you search the entire chat panel for this text, you will get false matches from parent divs whose innerText includes ALL child text. ONLY match small leaf elements (under 80 chars) or use explicit button/pill selectors.');
13486
- lines.push('2. **Awaiting Approval pill without actions**: Some IDEs show a floating pill/banner saying "Awaiting Approval" that is just a scroll-to indicator (not an actual approval dialog). If this pill exists but NO actionable buttons (Run/Skip/Accept/Reject) exist anywhere in the panel, the status should be `idle`, NOT `waiting_approval`.');
13487
- lines.push("3. **generating detection must be multi-signal**: Do NOT rely on just one indicator. Check ALL of: stop buttons, CSS animations, floating state labels, streaming classes. IDEs differ widely.");
13488
- lines.push("4. **activeModal must include actions**: When `status` is `waiting_approval`, the `activeModal` object MUST include a non-empty `actions` array listing the button labels. If you cannot find any action buttons, the status is NOT `waiting_approval`.");
13532
+ lines.push(`1. **DO NOT rely on button text/labels in the user's language.** OS locale may be Korean, Japanese, etc. Button text like "Cancel" or "Stop" will be localized. Instead, detect STRUCTURAL indicators: SVG icon changes, CSS classes, aria-labels from the extension's own React/Radix UI (which stay in English regardless of OS locale).`);
13533
+ lines.push('2. **Use sendMessage to CREATE a generating state, then CAPTURE the DOM.** Send a LONG prompt (e.g. "Write an extremely detailed 5000-word essay...") so the AI takes 10+ seconds. Then periodically capture the DOM during generation to find which elements appear/change. Compare idle vs generating DOM snapshots to find reliable structural markers.');
13534
+ lines.push("3. **Look for SVG icon changes in the submit button.** Many IDEs change the submit button icon from an arrow (send) to a square (stop) during generation. Check the SVG `fill` attribute or path data.");
13535
+ lines.push('4. **FALSE POSITIVES from old messages**: Chat history may contain text like "Command Awaiting Approval" from PAST turns. ONLY match small leaf elements (under 80 chars) or use explicit button/pill selectors.');
13536
+ lines.push("5. **Awaiting Approval pill without actions**: Some IDEs show a floating pill/banner that is just a scroll-to indicator. If NO actionable buttons exist, the status should be `idle`, NOT `waiting_approval`.");
13537
+ lines.push("6. **activeModal must include actions**: When `status` is `waiting_approval`, the `activeModal` object MUST include a non-empty `actions` array.");
13489
13538
  lines.push("");
13490
13539
  lines.push("## Action");
13491
13540
  lines.push("1. Edit the script files to implement working code");
@@ -13543,10 +13592,10 @@ var DevServer = class _DevServer {
13543
13592
  lines.push(`echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); r=d.get('result',d); r=json.loads(r) if isinstance(r,str) else r; assert r.get('status')=='idle', f'Expected idle, got {r.get(chr(34)+chr(115)+chr(116)+chr(97)+chr(116)+chr(117)+chr(115)+chr(34))}'; print('Step 1 PASS: status=idle')"`);
13544
13593
  lines.push("```");
13545
13594
  lines.push("");
13546
- lines.push("### Step 2: Send a message that triggers generation");
13595
+ lines.push("### Step 2: Send a LONG message that triggers extended generation (10+ seconds)");
13547
13596
  lines.push("```bash");
13548
- lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/scripts/run -H "Content-Type: application/json" -d '{"script": "sendMessage", "type": "${type}", "ideType": "${type}", "args": {"message": "Say hello in one word"}}'`);
13549
- lines.push("sleep 2");
13597
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/scripts/run -H "Content-Type: application/json" -d '{"script": "sendMessage", "type": "${type}", "ideType": "${type}", "args": {"message": "Write an extremely detailed 5000-word essay about the history of artificial intelligence from Alan Turing to 2025. Be very thorough and verbose."}}'`);
13598
+ lines.push("sleep 3");
13550
13599
  lines.push("```");
13551
13600
  lines.push("");
13552
13601
  lines.push("### Step 3: Check generating OR completed");
@@ -13670,6 +13719,9 @@ var DevServer = class _DevServer {
13670
13719
  lines.push("8. Keep exports compatible with the existing `scripts.js` router (`module.exports = function ...`).");
13671
13720
  lines.push("9. Do not rewrite unrelated provider config. Only touch the scripts needed for this task unless a tiny supporting change is required.");
13672
13721
  lines.push("10. When the verification API returns `instanceId`, keep using that exact instance for follow-up `send`, `resolve`, `raw`, and `stop` calls. Do not assume type-only routing is safe if multiple sessions exist.");
13722
+ lines.push("11. Do NOT repeatedly dump the same target files. Read the target scripts once, reproduce the bug, then move directly to patching.");
13723
+ lines.push("12. If the user instructions include concrete screen text, raw PTY snippets, or a specific repro, treat that as the primary acceptance criteria.");
13724
+ lines.push("13. After the first successful live repro, stop broad diagnosis. Edit the scripts, reload, and verify. Do not burn tokens on repeated re-inspection without code changes.");
13673
13725
  lines.push("");
13674
13726
  lines.push("## Task");
13675
13727
  lines.push(`Edit files in \`${providerDir}\` to implement: **${functions.join(", ")}**`);
@@ -13711,6 +13763,9 @@ var DevServer = class _DevServer {
13711
13763
  lines.push("");
13712
13764
  lines.push("Use `resolve` when the parsed modal buttons are correct. Use `raw` when the CLI expects a literal keystroke like `1`, `y`, or Enter. Repeat until idle.");
13713
13765
  lines.push("");
13766
+ lines.push("### Patch Discipline");
13767
+ lines.push("Once the repro is confirmed, immediately edit the target files. Avoid loops where you keep re-reading long files or re-running the same debug commands without changing code.");
13768
+ lines.push("");
13714
13769
  lines.push("### 5. Verify the side effects outside the CLI");
13715
13770
  lines.push("```bash");
13716
13771
  lines.push("test -f tmp/adhdev_provider_fix_test.py");