@adhdev/daemon-core 0.9.6 → 0.9.8

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
@@ -1512,8 +1512,35 @@ function promptLikelyVisible(screenText, promptSnippet) {
1512
1512
  function normalizeScreenSnapshot(text) {
1513
1513
  return sanitizeTerminalText(String(text || "")).replace(/\s+/g, " ").trim();
1514
1514
  }
1515
+ function shouldReflowComparableMessageLines(lines) {
1516
+ return Array.isArray(lines) && lines.length > 1 && lines.slice(0, -1).every((line) => String(line || "").trim().length >= 48) && !lines.some((line) => /^```/.test(line)) && !lines.some((line) => /^\|/.test(line)) && !lines.some((line) => /^\s*(?:[-*+] |\d+\.\s)/.test(line));
1517
+ }
1518
+ function joinComparableMessageLines(lines) {
1519
+ return lines.reduce((acc, line) => {
1520
+ const next = String(line || "").trim();
1521
+ if (!next) return acc;
1522
+ if (!acc) return next;
1523
+ if (/[,\d]$/.test(acc) && /^\d/.test(next)) {
1524
+ return `${acc}${next}`;
1525
+ }
1526
+ if (/[A-Za-z]$/.test(acc) && /^\d/.test(next)) {
1527
+ return `${acc}${next}`;
1528
+ }
1529
+ const fragmentMatch = acc.match(/([A-Za-z]{1,4})$/);
1530
+ const fragment = fragmentMatch ? fragmentMatch[1].toLowerCase() : "";
1531
+ if (/^[a-z]/.test(next) && fragment && !COMMON_COMPARABLE_WRAP_WORDS.has(fragment)) {
1532
+ return `${acc}${next}`;
1533
+ }
1534
+ return `${acc} ${next}`;
1535
+ }, "").replace(/\s+([,.;:!?])/g, "$1").replace(/(\d)\s+,/g, "$1,").replace(/\s+/g, " ").trim();
1536
+ }
1515
1537
  function normalizeComparableMessageContent(text) {
1516
- return String(text || "").replace(/\s+/g, " ").trim();
1538
+ const lines = String(text || "").split(/\r\n|\n|\r/g).map((line) => line.trim()).filter(Boolean);
1539
+ if (lines.length === 0) return "";
1540
+ if (shouldReflowComparableMessageLines(lines)) {
1541
+ return joinComparableMessageLines(lines);
1542
+ }
1543
+ return lines.join(" ").replace(/\s+/g, " ").trim();
1517
1544
  }
1518
1545
  function trimPromptEchoPrefix(text, promptText) {
1519
1546
  const prompt = normalizeComparableMessageContent(String(promptText || ""));
@@ -1546,9 +1573,6 @@ function getLastUserPromptText(messages) {
1546
1573
  }
1547
1574
  return "";
1548
1575
  }
1549
- function looksLikeConfirmOnlyLabel(label) {
1550
- return /^(?:continue|confirm|ok|yes|trust|proceed|enter)$/i.test(String(label || "").trim());
1551
- }
1552
1576
  function parsePatternEntry(x) {
1553
1577
  if (x instanceof RegExp) return x;
1554
1578
  if (x && typeof x === "object" && typeof x.source === "string") {
@@ -1575,7 +1599,7 @@ function normalizeCliProviderForRuntime(raw) {
1575
1599
  }
1576
1600
  };
1577
1601
  }
1578
- var os8, path9, import_child_process4, buildCliSpawnEnv;
1602
+ var os8, path9, import_child_process4, buildCliSpawnEnv, COMMON_COMPARABLE_WRAP_WORDS;
1579
1603
  var init_provider_cli_shared = __esm({
1580
1604
  "src/cli-adapters/provider-cli-shared.ts"() {
1581
1605
  "use strict";
@@ -1584,6 +1608,32 @@ var init_provider_cli_shared = __esm({
1584
1608
  import_child_process4 = require("child_process");
1585
1609
  init_spawn_env();
1586
1610
  buildCliSpawnEnv = import_session_host_core.sanitizeSpawnEnv;
1611
+ COMMON_COMPARABLE_WRAP_WORDS = /* @__PURE__ */ new Set([
1612
+ "a",
1613
+ "an",
1614
+ "and",
1615
+ "as",
1616
+ "at",
1617
+ "but",
1618
+ "by",
1619
+ "for",
1620
+ "from",
1621
+ "in",
1622
+ "into",
1623
+ "is",
1624
+ "it",
1625
+ "of",
1626
+ "on",
1627
+ "or",
1628
+ "that",
1629
+ "the",
1630
+ "their",
1631
+ "then",
1632
+ "this",
1633
+ "to",
1634
+ "was",
1635
+ "with"
1636
+ ]);
1587
1637
  }
1588
1638
  });
1589
1639
 
@@ -1639,8 +1689,44 @@ function hydrateCliParsedMessages(parsedMessages, options) {
1639
1689
  };
1640
1690
  });
1641
1691
  }
1692
+ function chooseMoreComparableCliMessage(left, right) {
1693
+ const leftComparable = normalizeComparableMessageContent(left.content || "");
1694
+ const rightComparable = normalizeComparableMessageContent(right.content || "");
1695
+ if (leftComparable && leftComparable === rightComparable) {
1696
+ const leftNewlines = String(left.content || "").split(/\r\n|\n|\r/g).length - 1;
1697
+ const rightNewlines = String(right.content || "").split(/\r\n|\n|\r/g).length - 1;
1698
+ return rightNewlines < leftNewlines ? right : left;
1699
+ }
1700
+ return rightComparable.length > leftComparable.length ? right : left;
1701
+ }
1702
+ function dedupeConsecutiveComparableCliMessages(messages) {
1703
+ const deduped = [];
1704
+ for (const message of messages) {
1705
+ const current = {
1706
+ ...message,
1707
+ content: typeof message.content === "string" ? message.content : String(message.content || "")
1708
+ };
1709
+ const previous = deduped[deduped.length - 1];
1710
+ if (!previous) {
1711
+ deduped.push(current);
1712
+ continue;
1713
+ }
1714
+ const previousComparable = normalizeComparableMessageContent(previous.content || "");
1715
+ const currentComparable = normalizeComparableMessageContent(current.content || "");
1716
+ const sameRole = previous.role === current.role;
1717
+ const sameKind = (previous.kind || "standard") === (current.kind || "standard");
1718
+ const sameSender = (previous.senderName || "") === (current.senderName || "");
1719
+ const comparableMatch = previousComparable && previousComparable === currentComparable;
1720
+ if (sameRole && sameKind && sameSender && comparableMatch) {
1721
+ deduped[deduped.length - 1] = chooseMoreComparableCliMessage(previous, current);
1722
+ continue;
1723
+ }
1724
+ deduped.push(current);
1725
+ }
1726
+ return deduped;
1727
+ }
1642
1728
  function normalizeCliParsedMessages(parsedMessages, options) {
1643
- return hydrateCliParsedMessages(parsedMessages, options).map((message) => ({
1729
+ return dedupeConsecutiveComparableCliMessages(hydrateCliParsedMessages(parsedMessages, options).map((message) => ({
1644
1730
  role: message.role,
1645
1731
  content: message.content,
1646
1732
  timestamp: message.timestamp,
@@ -1650,7 +1736,7 @@ function normalizeCliParsedMessages(parsedMessages, options) {
1650
1736
  index: message.index,
1651
1737
  meta: message.meta,
1652
1738
  senderName: message.senderName
1653
- }));
1739
+ })));
1654
1740
  }
1655
1741
  function buildCliParseInput(options) {
1656
1742
  const {
@@ -2313,7 +2399,7 @@ var init_provider_cli_adapter = __esm({
2313
2399
  if (!hasStartupOutput) return;
2314
2400
  const stableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
2315
2401
  if (stableMs < 2e3) return;
2316
- const startupModal = this.getStartupConfirmationModal(screenText);
2402
+ const startupModal = this.runParseApproval(this.recentOutputBuffer);
2317
2403
  this.startupParseGate = false;
2318
2404
  if (this.startupSettleTimer) {
2319
2405
  clearTimeout(this.startupSettleTimer);
@@ -2370,11 +2456,17 @@ var init_provider_cli_adapter = __esm({
2370
2456
  if (this.currentStatus !== "waiting_approval") return;
2371
2457
  const tail = this.recentOutputBuffer;
2372
2458
  const screenText = this.terminalScreen.getText() || "";
2373
- const startupModal = this.getStartupConfirmationModal(screenText);
2374
- const modal = this.runParseApproval(tail) || startupModal;
2459
+ const modal = this.runParseApproval(tail);
2375
2460
  const stillWaiting = this.runDetectStatus(tail) === "waiting_approval" || !!modal;
2376
2461
  if (stillWaiting) {
2377
- this.activeModal = modal || this.activeModal || { message: "Approval required", buttons: ["Allow", "Deny"] };
2462
+ if (!modal) {
2463
+ LOG.warn("CLI", `[${this.cliType}] approval timeout check found no actionable modal; keeping approval state fail-closed`);
2464
+ this.activeModal = null;
2465
+ this.onStatusChange?.();
2466
+ this.armApprovalExitTimeout();
2467
+ return;
2468
+ }
2469
+ this.activeModal = modal;
2378
2470
  this.onStatusChange?.();
2379
2471
  this.armApprovalExitTimeout();
2380
2472
  return;
@@ -2386,81 +2478,12 @@ var init_provider_cli_adapter = __esm({
2386
2478
  this.onStatusChange?.();
2387
2479
  }, 6e4);
2388
2480
  }
2389
- looksLikeVisibleIdlePrompt(screenText) {
2390
- const text = String(screenText || "");
2391
- if (!text.trim()) return false;
2392
- if (this.cliType === "codex-cli" && /(^|\n)\s*[❯›>]\s+(?:Find and fix a bug in @filename|Improve documentation in @filename|Use \/skills|Write tests for @filename|Explain this codebase|Summarize recent commits|Implement \{feature\}|Run \/review on my current changes)(?:\n|$)/im.test(text)) {
2393
- return true;
2394
- }
2395
- return /(^|\n)\s*[❯›>]\s*(?:\n|$)/m.test(text) || /⏎\s+send/i.test(text) || /\?\s*for\s*shortcuts/i.test(text) || /Type your message(?:\s+or\s+@path\/to\/file)?/i.test(text) || /workspace\s*\(\/directory\)/i.test(text) || /for\s*shortcuts/i.test(text);
2396
- }
2397
- findLastMatchingLineIndex(lines, predicate) {
2398
- for (let index = lines.length - 1; index >= 0; index -= 1) {
2399
- if (predicate(lines[index])) return index;
2400
- }
2401
- return -1;
2402
- }
2403
- looksLikeClaudeGeneratingLine(line) {
2404
- const trimmed = String(line || "").trim();
2405
- if (!trimmed) return false;
2406
- if (/^⏵⏵\s+accept edits on/i.test(trimmed)) return false;
2407
- if (/esc to (cancel|interrupt|stop)/i.test(trimmed)) return true;
2408
- if (/^[✻✶✳✢✽⠂⠐⠒⠓⠦⠴⠶⠷⠿]+\s+\S+.*\b(?:thinking|thought for \d+s?)\b/i.test(trimmed)) return true;
2409
- if (/^[✻✶✳✢✽⠂⠐⠒⠓⠦⠴⠶⠷⠿]+\s+[A-Z][A-Za-z-]{3,}ing\b.*(?:…|\.{3})/u.test(trimmed)) return true;
2410
- if (/^[⏺•]\s+(?:Reading|Writing|Editing|Searching|Inspecting|Planning|Analyzing|Synthesizing|Drafting|Running|Listing|Scanning|Matching)\b.*(?:…|\.{3})/i.test(trimmed)) {
2411
- return /ctrl\+o to expand/i.test(trimmed) || /\b\d+\s+(?:file|files|pattern|patterns|director(?:y|ies)|match|matches|result|results)\b/i.test(trimmed);
2412
- }
2413
- return false;
2414
- }
2415
- detectClaudeGeneratingOverride(screenText, tail) {
2416
- if (this.cliType !== "claude-cli") return false;
2417
- const source = sanitizeTerminalText(screenText || tail || "");
2418
- if (!source.trim()) return false;
2419
- const allLines = source.split(/\r\n|\n|\r/g).map((line) => line.trim()).filter(Boolean);
2420
- if (allLines.length === 0) return false;
2421
- const recentLines = allLines.slice(-12);
2422
- const promptIndex = this.findLastMatchingLineIndex(recentLines, (line) => /^[❯›>]\s*$/.test(line));
2423
- const activeRegion = promptIndex >= 0 ? recentLines.slice(Math.max(0, promptIndex - 2), promptIndex) : recentLines;
2424
- if (activeRegion.length === 0) return false;
2425
- return activeRegion.some((line) => this.looksLikeClaudeGeneratingLine(line));
2426
- }
2427
- refineDetectedStatus(status, tail, screenText) {
2428
- if (this.startupParseGate) {
2429
- return this.getStartupConfirmationModal(screenText || "") ? "waiting_approval" : "starting";
2430
- }
2431
- if (status === "waiting_approval") return status;
2432
- if (this.detectClaudeGeneratingOverride(screenText || "", tail)) return "generating";
2433
- return status;
2434
- }
2435
- looksLikeVisibleAssistantCandidate(screenText) {
2436
- const lines = sanitizeTerminalText(String(screenText || "")).split(/\r\n|\n|\r/g);
2437
- for (const line of lines) {
2438
- const trimmed = String(line || "").trim();
2439
- if (!trimmed) continue;
2440
- if (/^➜\s+\S+/.test(trimmed)) continue;
2441
- if (/^Update available!/i.test(trimmed)) continue;
2442
- if (/Claude Code v\d/i.test(trimmed)) continue;
2443
- if (/^⏵⏵\s+accept edits on/i.test(trimmed)) continue;
2444
- if (/^[◐◑◒◓◴◵◶◷◸◹◺◿].*\/effort/i.test(trimmed)) continue;
2445
- if (/^[✻✶✳✢✽⠂⠐⠒⠓⠦⠴⠶⠷⠿]+$/.test(trimmed)) continue;
2446
- if (/esc to (cancel|interrupt|stop)/i.test(trimmed)) continue;
2447
- const assistantMatch = trimmed.match(/^⏺\s+(.+)$/);
2448
- if (!assistantMatch) continue;
2449
- const content = assistantMatch[1].trim();
2450
- if (!content) continue;
2451
- if (/^(?:Bash|Read|Write|Edit|MultiEdit|Task|Glob|Grep|LS|NotebookEdit)\(/.test(content)) continue;
2452
- if (/This command requires approval|Do you want to proceed|Allow once|Always allow/i.test(content)) continue;
2453
- return true;
2454
- }
2455
- return false;
2456
- }
2457
2481
  shouldRetryFinishResponse(commitResult) {
2458
2482
  if (!this.currentTurnScope) return false;
2459
2483
  if (this.currentStatus === "waiting_approval" || this.activeModal) return false;
2460
2484
  if (this.finishRetryCount >= _ProviderCliAdapter.MAX_FINISH_RETRIES) return false;
2461
2485
  if (commitResult.hasAssistant && commitResult.assistantContent.trim()) return false;
2462
- const screenText = this.terminalScreen.getText() || "";
2463
- if (!this.looksLikeVisibleAssistantCandidate(screenText)) return false;
2486
+ if (this.runDetectStatus(this.recentOutputBuffer) !== "idle") return false;
2464
2487
  const now = Date.now();
2465
2488
  const quietForMs = this.lastNonEmptyOutputAt ? now - this.lastNonEmptyOutputAt : Number.MAX_SAFE_INTEGER;
2466
2489
  const screenStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
@@ -2484,45 +2507,21 @@ var init_provider_cli_adapter = __esm({
2484
2507
  }
2485
2508
  return false;
2486
2509
  }
2487
- getStartupConfirmationModal(screenText) {
2488
- const text = sanitizeTerminalText(String(screenText || ""));
2489
- if (!text.trim()) return null;
2490
- if (this.cliType === "claude-cli") {
2491
- const hasTrustPrompt = /Quick safety check/i.test(text) || /Is this a project you trust/i.test(text) || /Do you trust (?:this project|the contents of this directory|the files in this folder)/i.test(text);
2492
- const hasConfirmFooter = /Press Enter to (?:continue|confirm)/i.test(text) || /Enter to confirm/i.test(text) || /Esc to (?:cancel|exit)/i.test(text);
2493
- if (hasTrustPrompt || hasConfirmFooter && /trust/i.test(text)) {
2494
- return {
2495
- message: "Confirm Claude Code project trust",
2496
- buttons: ["Continue"]
2497
- };
2498
- }
2499
- }
2500
- return null;
2501
- }
2502
- shouldResolveModalWithEnter(modal, buttonIndex) {
2503
- if (!modal || buttonIndex !== 0) return false;
2504
- const buttons = Array.isArray(modal.buttons) ? modal.buttons : [];
2505
- if (buttons.length !== 1) return false;
2506
- const buttonLabel = String(buttons[0] || "").trim();
2507
- return looksLikeConfirmOnlyLabel(buttonLabel);
2508
- }
2509
2510
  async waitForInteractivePrompt(maxWaitMs = 5e3) {
2510
2511
  const startedAt = Date.now();
2511
2512
  let loggedWait = false;
2512
2513
  while (Date.now() - startedAt < maxWaitMs) {
2513
2514
  this.resolveStartupState("interactive_wait");
2514
2515
  const screenText = this.terminalScreen.getText() || "";
2515
- const hasPrompt = this.looksLikeVisibleIdlePrompt(screenText);
2516
2516
  const stableMs = this.lastScreenChangeAt ? Date.now() - this.lastScreenChangeAt : 0;
2517
2517
  const recentlyOutput = this.lastNonEmptyOutputAt ? Date.now() - this.lastNonEmptyOutputAt : Number.MAX_SAFE_INTEGER;
2518
2518
  const status = this.runDetectStatus(this.recentOutputBuffer) || this.currentStatus;
2519
- const startupLikelyActive = /Welcome back|Tips for getting|Recent activity|Claude Code v\d/i.test(screenText);
2520
- const interactiveReady = hasPrompt && stableMs >= 700 && recentlyOutput >= 350 && status !== "generating";
2519
+ const interactiveReady = status === "idle" && stableMs >= 700 && recentlyOutput >= 350;
2521
2520
  if (interactiveReady) {
2522
2521
  if (loggedWait) {
2523
2522
  LOG.info(
2524
2523
  "CLI",
2525
- `[${this.cliType}] Interactive prompt ready after ${Date.now() - startedAt}ms (stableMs=${stableMs}, recentOutputMs=${recentlyOutput}, startup=${startupLikelyActive})`
2524
+ `[${this.cliType}] Interactive prompt ready after ${Date.now() - startedAt}ms (stableMs=${stableMs}, recentOutputMs=${recentlyOutput})`
2526
2525
  );
2527
2526
  }
2528
2527
  return;
@@ -2531,7 +2530,7 @@ var init_provider_cli_adapter = __esm({
2531
2530
  loggedWait = true;
2532
2531
  LOG.info(
2533
2532
  "CLI",
2534
- `[${this.cliType}] Waiting for interactive prompt: hasPrompt=${hasPrompt} stableMs=${stableMs} recentOutputMs=${recentlyOutput} status=${status} startup=${startupLikelyActive} screen=${JSON.stringify(summarizeCliTraceText(screenText, 220)).slice(0, 260)}`
2533
+ `[${this.cliType}] Waiting for interactive prompt: status=${status} stableMs=${stableMs} recentOutputMs=${recentlyOutput} screen=${JSON.stringify(summarizeCliTraceText(screenText, 220)).slice(0, 260)}`
2535
2534
  );
2536
2535
  }
2537
2536
  await new Promise((resolve12) => setTimeout(resolve12, 50));
@@ -2542,13 +2541,12 @@ var init_provider_cli_adapter = __esm({
2542
2541
  `[${this.cliType}] Interactive prompt wait timed out after ${maxWaitMs}ms; proceeding with screen=${JSON.stringify(summarizeCliTraceText(finalScreenText, 240)).slice(0, 280)}`
2543
2542
  );
2544
2543
  }
2545
- clearStaleIdleResponseGuard(reason) {
2546
- const screenText = this.terminalScreen.getText() || "";
2547
- const visibleIdlePrompt = this.looksLikeVisibleIdlePrompt(screenText);
2548
- const blockingModal = this.activeModal || this.getStartupConfirmationModal(screenText);
2549
- if (!this.isWaitingForResponse || this.currentStatus !== "idle" || !visibleIdlePrompt || !!blockingModal) {
2550
- return false;
2551
- }
2544
+ trimLastAssistantEcho(messages, prompt) {
2545
+ if (!prompt) return;
2546
+ const last = [...messages].reverse().find((m) => m.role === "assistant" && typeof m.content === "string");
2547
+ if (last) last.content = trimPromptEchoPrefix(last.content, prompt);
2548
+ }
2549
+ clearAllTimers() {
2552
2550
  if (this.responseTimeout) {
2553
2551
  clearTimeout(this.responseTimeout);
2554
2552
  this.responseTimeout = null;
@@ -2561,10 +2559,38 @@ var init_provider_cli_adapter = __esm({
2561
2559
  clearTimeout(this.approvalExitTimeout);
2562
2560
  this.approvalExitTimeout = null;
2563
2561
  }
2562
+ if (this.submitRetryTimer) {
2563
+ clearTimeout(this.submitRetryTimer);
2564
+ this.submitRetryTimer = null;
2565
+ }
2564
2566
  if (this.finishRetryTimer) {
2565
2567
  clearTimeout(this.finishRetryTimer);
2566
2568
  this.finishRetryTimer = null;
2567
2569
  }
2570
+ if (this.settleTimer) {
2571
+ clearTimeout(this.settleTimer);
2572
+ this.settleTimer = null;
2573
+ }
2574
+ if (this.pendingScriptStatusTimer) {
2575
+ clearTimeout(this.pendingScriptStatusTimer);
2576
+ this.pendingScriptStatusTimer = null;
2577
+ }
2578
+ if (this.pendingOutputParseTimer) {
2579
+ clearTimeout(this.pendingOutputParseTimer);
2580
+ this.pendingOutputParseTimer = null;
2581
+ }
2582
+ if (this.ptyOutputFlushTimer) {
2583
+ clearTimeout(this.ptyOutputFlushTimer);
2584
+ this.ptyOutputFlushTimer = null;
2585
+ }
2586
+ }
2587
+ clearStaleIdleResponseGuard(reason) {
2588
+ const blockingModal = this.activeModal || this.runParseApproval(this.recentOutputBuffer);
2589
+ const isIdle = this.runDetectStatus(this.recentOutputBuffer) === "idle";
2590
+ if (!this.isWaitingForResponse || this.currentStatus !== "idle" || !isIdle || !!blockingModal) {
2591
+ return false;
2592
+ }
2593
+ this.clearAllTimers();
2568
2594
  this.clearIdleFinishCandidate(reason);
2569
2595
  this.responseBuffer = "";
2570
2596
  this.isWaitingForResponse = false;
@@ -2574,10 +2600,7 @@ var init_provider_cli_adapter = __esm({
2574
2600
  this.finishRetryCount = 0;
2575
2601
  this.currentTurnScope = null;
2576
2602
  this.activeModal = null;
2577
- this.recordTrace("stale_idle_response_cleared", {
2578
- reason,
2579
- screenText: summarizeCliTraceText(screenText, 240)
2580
- });
2603
+ this.recordTrace("stale_idle_response_cleared", { reason });
2581
2604
  return true;
2582
2605
  }
2583
2606
  hasMeaningfulResponseBuffer(promptSnippet) {
@@ -2612,16 +2635,15 @@ var init_provider_cli_adapter = __esm({
2612
2635
  if (this.startupParseGate) {
2613
2636
  return;
2614
2637
  }
2615
- const startupModal = this.getStartupConfirmationModal(screenText);
2616
2638
  const parsedTranscript = this.parseCurrentTranscript(
2617
2639
  this.committedMessages,
2618
2640
  this.responseBuffer,
2619
2641
  this.currentTurnScope
2620
2642
  );
2621
2643
  const parsedModal = parsedTranscript?.activeModal && Array.isArray(parsedTranscript.activeModal.buttons) && parsedTranscript.activeModal.buttons.some((button) => typeof button === "string" && button.trim()) ? parsedTranscript.activeModal : null;
2622
- const modal = this.runParseApproval(tail) || parsedModal || startupModal;
2644
+ const modal = this.runParseApproval(tail) || parsedModal;
2623
2645
  const rawScriptStatus = this.runDetectStatus(tail);
2624
- const scriptStatus = startupModal ? "waiting_approval" : parsedModal && parsedTranscript?.status === "waiting_approval" ? "waiting_approval" : rawScriptStatus;
2646
+ const scriptStatus = parsedTranscript?.status === "waiting_approval" && modal ? "waiting_approval" : rawScriptStatus;
2625
2647
  const parsedMessages = Array.isArray(parsedTranscript?.messages) ? normalizeCliParsedMessages(parsedTranscript.messages, {
2626
2648
  committedMessages: this.committedMessages,
2627
2649
  scope: this.currentTurnScope,
@@ -2676,15 +2698,44 @@ var init_provider_cli_adapter = __esm({
2676
2698
  }
2677
2699
  if (!scriptStatus) return;
2678
2700
  const prevStatus = this.currentStatus;
2679
- const clearPendingScriptStatus = () => {
2701
+ const ctx = { now, screenText, modal, scriptStatus, parsedTranscript, parsedMessages, lastParsedAssistant, parsedShowsLiveAssistantProgress, prevStatus };
2702
+ if (!this.applyPendingScriptStatusDebounce(ctx)) return;
2703
+ const recentInteractiveActivity = this.hasRecentInteractiveActivity(now);
2704
+ LOG.info(
2705
+ "CLI",
2706
+ `[${this.cliType}] settled diagnostics prompt=${JSON.stringify(this.currentTurnScope?.prompt || "").slice(0, 140)} scriptStatus=${String(scriptStatus || "")} parsedStatus=${String(parsedTranscript?.status || "")} parsedMsgCount=${parsedMessages.length} lastParsedAssistant=${JSON.stringify(summarizeCliTraceText(lastParsedAssistant?.content || "", 120)).slice(0, 160)} responseBuffer=${JSON.stringify(summarizeCliTraceText(this.responseBuffer, 160)).slice(0, 220)} screen=${JSON.stringify(summarizeCliTraceText(screenText, 160)).slice(0, 220)}`
2707
+ );
2708
+ const shouldHoldGenerating = scriptStatus === "idle" && this.isWaitingForResponse && !modal && recentInteractiveActivity && !(parsedTranscript?.status === "idle" && !!lastParsedAssistant);
2709
+ if (shouldHoldGenerating) {
2710
+ this.applyHoldGenerating(ctx, recentInteractiveActivity);
2711
+ return;
2712
+ }
2713
+ if (scriptStatus === "waiting_approval") {
2714
+ this.applyWaitingApproval(ctx);
2715
+ return;
2716
+ }
2717
+ if (scriptStatus === "generating") {
2718
+ this.applyGenerating(ctx);
2719
+ return;
2720
+ }
2721
+ if (scriptStatus === "idle") {
2722
+ this.applyIdle(ctx, now);
2723
+ }
2724
+ }
2725
+ // Returns false if the caller should bail out (debounce pending).
2726
+ applyPendingScriptStatusDebounce(ctx) {
2727
+ const { now, scriptStatus, prevStatus } = ctx;
2728
+ const shouldDebounce = prevStatus === "idle" && !this.isWaitingForResponse && !this.currentTurnScope && (scriptStatus === "generating" || scriptStatus === "waiting_approval");
2729
+ if (!shouldDebounce) {
2680
2730
  this.pendingScriptStatus = null;
2681
2731
  this.pendingScriptStatusSince = 0;
2682
2732
  if (this.pendingScriptStatusTimer) {
2683
2733
  clearTimeout(this.pendingScriptStatusTimer);
2684
2734
  this.pendingScriptStatusTimer = null;
2685
2735
  }
2686
- };
2687
- const armPendingScriptStatus = (delayMs) => {
2736
+ return true;
2737
+ }
2738
+ const armPending = (delayMs) => {
2688
2739
  if (this.pendingScriptStatusTimer) clearTimeout(this.pendingScriptStatusTimer);
2689
2740
  this.pendingScriptStatusTimer = setTimeout(() => {
2690
2741
  this.pendingScriptStatusTimer = null;
@@ -2692,200 +2743,187 @@ var init_provider_cli_adapter = __esm({
2692
2743
  this.evaluateSettled();
2693
2744
  }, delayMs);
2694
2745
  };
2695
- const shouldDebouncePromotion = (status) => prevStatus === "idle" && !this.isWaitingForResponse && !this.currentTurnScope && (status === "generating" || status === "waiting_approval");
2696
- if (shouldDebouncePromotion(scriptStatus)) {
2697
- if (this.pendingScriptStatus !== scriptStatus) {
2698
- this.pendingScriptStatus = scriptStatus;
2699
- this.pendingScriptStatusSince = now;
2700
- armPendingScriptStatus(_ProviderCliAdapter.SCRIPT_STATUS_DEBOUNCE_MS);
2701
- return;
2702
- }
2703
- const elapsed = now - this.pendingScriptStatusSince;
2704
- if (elapsed < _ProviderCliAdapter.SCRIPT_STATUS_DEBOUNCE_MS) {
2705
- armPendingScriptStatus(_ProviderCliAdapter.SCRIPT_STATUS_DEBOUNCE_MS - elapsed);
2706
- return;
2707
- }
2708
- } else {
2709
- clearPendingScriptStatus();
2710
- }
2711
- const recentInteractiveActivity = this.hasRecentInteractiveActivity(now);
2712
- const statusActivityHoldMs = this.getStatusActivityHoldMs();
2713
- const visibleIdlePrompt = this.looksLikeVisibleIdlePrompt(screenText);
2714
- const visibleAssistantCandidate = this.looksLikeVisibleAssistantCandidate(screenText);
2715
- if (this.currentTurnScope && this.cliType === "claude-cli") {
2716
- LOG.info(
2717
- "CLI",
2718
- `[${this.cliType}] settled diagnostics prompt=${JSON.stringify(this.currentTurnScope.prompt).slice(0, 140)} scriptStatus=${String(scriptStatus || "")} parsedStatus=${String(parsedTranscript?.status || "")} parsedMsgCount=${parsedMessages.length} lastParsedAssistant=${JSON.stringify(summarizeCliTraceText(lastParsedAssistant?.content || "", 120)).slice(0, 160)} visibleIdlePrompt=${String(visibleIdlePrompt)} visibleAssistantCandidate=${String(visibleAssistantCandidate)} responseBuffer=${JSON.stringify(summarizeCliTraceText(this.responseBuffer, 160)).slice(0, 220)} screen=${JSON.stringify(summarizeCliTraceText(screenText, 160)).slice(0, 220)}`
2719
- );
2746
+ if (this.pendingScriptStatus !== scriptStatus) {
2747
+ this.pendingScriptStatus = scriptStatus;
2748
+ this.pendingScriptStatusSince = now;
2749
+ armPending(_ProviderCliAdapter.SCRIPT_STATUS_DEBOUNCE_MS);
2750
+ return false;
2720
2751
  }
2721
- const shouldHoldGenerating = scriptStatus === "idle" && this.isWaitingForResponse && !modal && recentInteractiveActivity && !(visibleIdlePrompt && visibleAssistantCandidate) && !(parsedTranscript?.status === "idle" && !!lastParsedAssistant);
2722
- if (shouldHoldGenerating) {
2723
- this.clearIdleFinishCandidate("hold_generating_recent_activity");
2724
- this.setStatus("generating", "recent_activity_hold");
2725
- if (this.idleTimeout) clearTimeout(this.idleTimeout);
2726
- this.idleTimeout = setTimeout(() => {
2727
- if (this.isWaitingForResponse && this.currentStatus !== "waiting_approval") {
2728
- if (this.shouldDeferIdleTimeoutFinish()) return;
2729
- this.finishResponse();
2730
- }
2731
- }, this.timeouts.generatingIdle);
2732
- this.recordTrace("hold_generating_recent_activity", {
2733
- scriptStatus,
2734
- recentInteractiveActivity,
2735
- lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
2736
- lastScreenChangeAt: this.lastScreenChangeAt,
2737
- holdMs: statusActivityHoldMs,
2738
- ...buildCliTraceParseSnapshot({
2739
- accumulatedBuffer: this.accumulatedBuffer,
2740
- accumulatedRawBuffer: this.accumulatedRawBuffer,
2741
- responseBuffer: this.responseBuffer,
2742
- partialResponse: this.responseBuffer,
2743
- scope: this.currentTurnScope
2744
- })
2745
- });
2746
- this.onStatusChange?.();
2747
- return;
2752
+ const elapsed = now - this.pendingScriptStatusSince;
2753
+ if (elapsed < _ProviderCliAdapter.SCRIPT_STATUS_DEBOUNCE_MS) {
2754
+ armPending(_ProviderCliAdapter.SCRIPT_STATUS_DEBOUNCE_MS - elapsed);
2755
+ return false;
2748
2756
  }
2749
- if (scriptStatus === "waiting_approval") {
2750
- this.clearIdleFinishCandidate("waiting_approval");
2751
- const inCooldown = this.lastApprovalResolvedAt && Date.now() - this.lastApprovalResolvedAt < this.timeouts.approvalCooldown;
2752
- const visibleIdlePrompt2 = this.looksLikeVisibleIdlePrompt(screenText);
2753
- if ((inCooldown || visibleIdlePrompt2) && !modal) {
2754
- if (this.approvalExitTimeout) {
2755
- clearTimeout(this.approvalExitTimeout);
2756
- this.approvalExitTimeout = null;
2757
- }
2758
- this.activeModal = null;
2759
- if (this.isWaitingForResponse) {
2760
- this.setStatus("generating", inCooldown ? "approval_cooldown_ignore" : "approval_prompt_gone");
2761
- if (this.idleTimeout) clearTimeout(this.idleTimeout);
2762
- this.idleTimeout = setTimeout(() => {
2763
- if (this.isWaitingForResponse && this.currentStatus !== "waiting_approval") {
2764
- if (this.shouldDeferIdleTimeoutFinish()) return;
2765
- this.finishResponse();
2766
- }
2767
- }, this.timeouts.generatingIdle);
2768
- } else {
2769
- this.setStatus("idle", inCooldown ? "approval_cooldown_ignore" : "approval_prompt_gone");
2770
- }
2771
- this.onStatusChange?.();
2772
- return;
2757
+ return true;
2758
+ }
2759
+ applyHoldGenerating(ctx, recentInteractiveActivity) {
2760
+ const { scriptStatus } = ctx;
2761
+ this.clearIdleFinishCandidate("hold_generating_recent_activity");
2762
+ this.setStatus("generating", "recent_activity_hold");
2763
+ if (this.idleTimeout) clearTimeout(this.idleTimeout);
2764
+ this.idleTimeout = setTimeout(() => {
2765
+ if (this.isWaitingForResponse && this.currentStatus !== "waiting_approval") {
2766
+ if (this.shouldDeferIdleTimeoutFinish()) return;
2767
+ this.finishResponse();
2768
+ }
2769
+ }, this.timeouts.generatingIdle);
2770
+ this.recordTrace("hold_generating_recent_activity", {
2771
+ scriptStatus,
2772
+ recentInteractiveActivity,
2773
+ lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
2774
+ lastScreenChangeAt: this.lastScreenChangeAt,
2775
+ holdMs: this.getStatusActivityHoldMs(),
2776
+ ...buildCliTraceParseSnapshot({
2777
+ accumulatedBuffer: this.accumulatedBuffer,
2778
+ accumulatedRawBuffer: this.accumulatedRawBuffer,
2779
+ responseBuffer: this.responseBuffer,
2780
+ partialResponse: this.responseBuffer,
2781
+ scope: this.currentTurnScope
2782
+ })
2783
+ });
2784
+ this.onStatusChange?.();
2785
+ }
2786
+ applyWaitingApproval(ctx) {
2787
+ const { modal } = ctx;
2788
+ this.clearIdleFinishCandidate("waiting_approval");
2789
+ const inCooldown = this.lastApprovalResolvedAt && Date.now() - this.lastApprovalResolvedAt < this.timeouts.approvalCooldown;
2790
+ if (inCooldown && !modal) {
2791
+ if (this.approvalExitTimeout) {
2792
+ clearTimeout(this.approvalExitTimeout);
2793
+ this.approvalExitTimeout = null;
2773
2794
  }
2774
- if (!inCooldown) {
2775
- this.isWaitingForResponse = true;
2776
- this.setStatus("waiting_approval", "script_detect");
2777
- this.activeModal = modal || { message: "Approval required", buttons: ["Allow", "Deny"] };
2795
+ this.activeModal = null;
2796
+ if (this.isWaitingForResponse) {
2797
+ this.setStatus("generating", inCooldown ? "approval_cooldown_ignore" : "approval_prompt_gone");
2778
2798
  if (this.idleTimeout) clearTimeout(this.idleTimeout);
2779
- this.armApprovalExitTimeout();
2780
- this.onStatusChange?.();
2781
- return;
2799
+ this.idleTimeout = setTimeout(() => {
2800
+ if (this.isWaitingForResponse && this.currentStatus !== "waiting_approval") {
2801
+ if (this.shouldDeferIdleTimeoutFinish()) return;
2802
+ this.finishResponse();
2803
+ }
2804
+ }, this.timeouts.generatingIdle);
2805
+ } else {
2806
+ this.setStatus("idle", inCooldown ? "approval_cooldown_ignore" : "approval_prompt_gone");
2782
2807
  }
2808
+ this.onStatusChange?.();
2809
+ return;
2783
2810
  }
2784
- if (scriptStatus === "generating") {
2785
- this.clearIdleFinishCandidate("generating");
2786
- const effectiveScreenText = screenText || this.accumulatedBuffer;
2787
- const noActiveTurn = !this.currentTurnScope;
2788
- const looksIdleChrome = /(^|\n)\s*[❯›>]\s*(?:\n|$)/m.test(effectiveScreenText) || /accept edits on/i.test(effectiveScreenText) && (/Update available!/i.test(screenText) || /\/effort/i.test(screenText) || /^.*➜\s+\S+/m.test(effectiveScreenText));
2789
- if (prevStatus === "idle" && !this.isWaitingForResponse && noActiveTurn && !modal && looksIdleChrome && !parsedShowsLiveAssistantProgress) {
2811
+ if (!inCooldown) {
2812
+ if (!modal) {
2813
+ LOG.warn("CLI", `[${this.cliType}] detectStatus reported waiting_approval without parseApproval modal; ignoring non-actionable approval state`);
2790
2814
  return;
2791
2815
  }
2792
- if (prevStatus === "waiting_approval") {
2793
- if (this.approvalExitTimeout) {
2794
- clearTimeout(this.approvalExitTimeout);
2795
- this.approvalExitTimeout = null;
2796
- }
2797
- this.activeModal = null;
2798
- this.lastApprovalResolvedAt = Date.now();
2799
- }
2800
- if (!this.isWaitingForResponse) {
2801
- this.isWaitingForResponse = true;
2802
- this.responseBuffer = "";
2803
- }
2804
- this.setStatus("generating", "script_detect");
2816
+ this.isWaitingForResponse = true;
2817
+ this.setStatus("waiting_approval", "script_detect");
2818
+ this.activeModal = modal;
2805
2819
  if (this.idleTimeout) clearTimeout(this.idleTimeout);
2806
- this.idleTimeout = setTimeout(() => {
2807
- if (this.isWaitingForResponse) {
2808
- if (this.shouldDeferIdleTimeoutFinish()) return;
2809
- this.finishResponse();
2810
- }
2811
- }, this.timeouts.generatingIdle);
2820
+ this.armApprovalExitTimeout();
2812
2821
  this.onStatusChange?.();
2822
+ }
2823
+ }
2824
+ applyGenerating(ctx) {
2825
+ const { screenText, modal, parsedShowsLiveAssistantProgress, prevStatus } = ctx;
2826
+ this.clearIdleFinishCandidate("generating");
2827
+ const effectiveScreenText = screenText || this.accumulatedBuffer;
2828
+ const noActiveTurn = !this.currentTurnScope;
2829
+ const looksIdleChrome = /(^|\n)\s*[❯›>]\s*(?:\n|$)/m.test(effectiveScreenText) || /accept edits on/i.test(effectiveScreenText) && (/Update available!/i.test(screenText) || /\/effort/i.test(screenText) || /^.*➜\s+\S+/m.test(effectiveScreenText));
2830
+ if (prevStatus === "idle" && !this.isWaitingForResponse && noActiveTurn && !modal && looksIdleChrome && !parsedShowsLiveAssistantProgress) {
2813
2831
  return;
2814
2832
  }
2815
- if (scriptStatus === "idle") {
2816
- if (prevStatus === "waiting_approval") {
2817
- if (this.approvalExitTimeout) {
2818
- clearTimeout(this.approvalExitTimeout);
2819
- this.approvalExitTimeout = null;
2820
- }
2821
- this.activeModal = null;
2822
- this.lastApprovalResolvedAt = Date.now();
2833
+ if (prevStatus === "waiting_approval") {
2834
+ if (this.approvalExitTimeout) {
2835
+ clearTimeout(this.approvalExitTimeout);
2836
+ this.approvalExitTimeout = null;
2823
2837
  }
2838
+ this.activeModal = null;
2839
+ this.lastApprovalResolvedAt = Date.now();
2840
+ }
2841
+ if (!this.isWaitingForResponse) {
2842
+ this.isWaitingForResponse = true;
2843
+ this.responseBuffer = "";
2844
+ }
2845
+ this.setStatus("generating", "script_detect");
2846
+ if (this.idleTimeout) clearTimeout(this.idleTimeout);
2847
+ this.idleTimeout = setTimeout(() => {
2824
2848
  if (this.isWaitingForResponse) {
2825
- const visibleIdlePrompt2 = this.looksLikeVisibleIdlePrompt(screenText);
2826
- const quietForMs = this.lastNonEmptyOutputAt ? now - this.lastNonEmptyOutputAt : Number.MAX_SAFE_INTEGER;
2827
- const screenStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
2828
- const hasAssistantTurn = !!lastParsedAssistant;
2829
- const assistantLength = lastParsedAssistant?.content?.length || 0;
2830
- const idleFinishConfirmMs = this.getIdleFinishConfirmMs();
2831
- const idleQuietThresholdMs = Math.max(idleFinishConfirmMs, this.timeouts.outputSettle);
2832
- const idleStableThresholdMs = idleFinishConfirmMs;
2833
- const idleReady = visibleIdlePrompt2 && !modal && hasAssistantTurn && quietForMs >= idleQuietThresholdMs && screenStableMs >= idleStableThresholdMs;
2834
- const candidate = this.idleFinishCandidate;
2835
- const candidateQuiet = !!candidate && candidate.responseEpoch === this.responseEpoch && candidate.lastOutputAt === this.lastOutputAt && candidate.lastScreenChangeAt === this.lastScreenChangeAt && assistantLength >= candidate.assistantLength && now - candidate.armedAt >= idleFinishConfirmMs;
2836
- const canFinishImmediately = idleReady && candidateQuiet;
2837
- this.recordTrace("idle_decision", {
2838
- visibleIdlePrompt: visibleIdlePrompt2,
2839
- quietForMs,
2840
- screenStableMs,
2841
- hasAssistantTurn,
2842
- assistantLength,
2843
- hasModal: !!modal,
2844
- idleQuietThresholdMs,
2845
- idleStableThresholdMs,
2846
- idleReady,
2847
- idleFinishConfirmMs,
2848
- idleFinishCandidate: candidate,
2849
- candidateQuiet,
2850
- canFinishImmediately,
2851
- submitPendingUntil: this.submitPendingUntil,
2852
- responseSettleIgnoreUntil: this.responseSettleIgnoreUntil,
2853
- ...buildCliTraceParseSnapshot({
2854
- accumulatedBuffer: this.accumulatedBuffer,
2855
- accumulatedRawBuffer: this.accumulatedRawBuffer,
2856
- responseBuffer: this.responseBuffer,
2857
- partialResponse: this.responseBuffer,
2858
- scope: this.currentTurnScope
2859
- })
2860
- });
2861
- if (canFinishImmediately) {
2862
- this.clearIdleFinishCandidate("finish_response");
2863
- if (this.idleTimeout) clearTimeout(this.idleTimeout);
2864
- this.finishResponse();
2865
- return;
2866
- }
2867
- if (idleReady) {
2868
- if (!candidate) {
2869
- this.armIdleFinishCandidate(assistantLength);
2870
- return;
2871
- }
2872
- } else {
2873
- this.clearIdleFinishCandidate("idle_not_ready");
2874
- }
2875
- if (this.idleTimeout) clearTimeout(this.idleTimeout);
2876
- this.idleTimeout = setTimeout(() => {
2877
- if (this.isWaitingForResponse && this.currentStatus !== "waiting_approval") {
2878
- if (this.shouldDeferIdleTimeoutFinish()) return;
2879
- this.clearIdleFinishCandidate("idle_timeout_finish");
2880
- this.finishResponse();
2881
- }
2882
- }, this.timeouts.idleFinish);
2883
- } else if (prevStatus !== "idle") {
2849
+ if (this.shouldDeferIdleTimeoutFinish()) return;
2850
+ this.finishResponse();
2851
+ }
2852
+ }, this.timeouts.generatingIdle);
2853
+ this.onStatusChange?.();
2854
+ }
2855
+ applyIdle(ctx, now) {
2856
+ const { screenText, modal, lastParsedAssistant, prevStatus } = ctx;
2857
+ if (prevStatus === "waiting_approval") {
2858
+ if (this.approvalExitTimeout) {
2859
+ clearTimeout(this.approvalExitTimeout);
2860
+ this.approvalExitTimeout = null;
2861
+ }
2862
+ this.activeModal = null;
2863
+ this.lastApprovalResolvedAt = Date.now();
2864
+ }
2865
+ if (!this.isWaitingForResponse) {
2866
+ if (prevStatus !== "idle") {
2884
2867
  this.clearIdleFinishCandidate("idle_without_response");
2885
2868
  this.setStatus("idle", "script_detect");
2886
2869
  this.onStatusChange?.();
2887
2870
  }
2871
+ return;
2872
+ }
2873
+ const quietForMs = this.lastNonEmptyOutputAt ? now - this.lastNonEmptyOutputAt : Number.MAX_SAFE_INTEGER;
2874
+ const screenStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
2875
+ const hasAssistantTurn = !!lastParsedAssistant;
2876
+ const assistantLength = lastParsedAssistant?.content?.length || 0;
2877
+ const idleFinishConfirmMs = this.getIdleFinishConfirmMs();
2878
+ const idleQuietThresholdMs = Math.max(idleFinishConfirmMs, this.timeouts.outputSettle);
2879
+ const idleReady = !modal && hasAssistantTurn && quietForMs >= idleQuietThresholdMs && screenStableMs >= idleFinishConfirmMs;
2880
+ const candidate = this.idleFinishCandidate;
2881
+ const candidateQuiet = !!candidate && candidate.responseEpoch === this.responseEpoch && candidate.lastOutputAt === this.lastOutputAt && candidate.lastScreenChangeAt === this.lastScreenChangeAt && assistantLength >= candidate.assistantLength && now - candidate.armedAt >= idleFinishConfirmMs;
2882
+ this.recordTrace("idle_decision", {
2883
+ quietForMs,
2884
+ screenStableMs,
2885
+ hasAssistantTurn,
2886
+ assistantLength,
2887
+ hasModal: !!modal,
2888
+ idleQuietThresholdMs,
2889
+ idleStableThresholdMs: idleFinishConfirmMs,
2890
+ idleReady,
2891
+ idleFinishConfirmMs,
2892
+ idleFinishCandidate: candidate,
2893
+ candidateQuiet,
2894
+ canFinishImmediately: idleReady && candidateQuiet,
2895
+ submitPendingUntil: this.submitPendingUntil,
2896
+ responseSettleIgnoreUntil: this.responseSettleIgnoreUntil,
2897
+ ...buildCliTraceParseSnapshot({
2898
+ accumulatedBuffer: this.accumulatedBuffer,
2899
+ accumulatedRawBuffer: this.accumulatedRawBuffer,
2900
+ responseBuffer: this.responseBuffer,
2901
+ partialResponse: this.responseBuffer,
2902
+ scope: this.currentTurnScope
2903
+ })
2904
+ });
2905
+ if (idleReady && candidateQuiet) {
2906
+ this.clearIdleFinishCandidate("finish_response");
2907
+ if (this.idleTimeout) clearTimeout(this.idleTimeout);
2908
+ this.finishResponse();
2909
+ return;
2888
2910
  }
2911
+ if (idleReady) {
2912
+ if (!candidate) {
2913
+ this.armIdleFinishCandidate(assistantLength);
2914
+ return;
2915
+ }
2916
+ } else {
2917
+ this.clearIdleFinishCandidate("idle_not_ready");
2918
+ }
2919
+ if (this.idleTimeout) clearTimeout(this.idleTimeout);
2920
+ this.idleTimeout = setTimeout(() => {
2921
+ if (this.isWaitingForResponse && this.currentStatus !== "waiting_approval") {
2922
+ if (this.shouldDeferIdleTimeoutFinish()) return;
2923
+ this.clearIdleFinishCandidate("idle_timeout_finish");
2924
+ this.finishResponse();
2925
+ }
2926
+ }, this.timeouts.idleFinish);
2889
2927
  }
2890
2928
  finishResponse() {
2891
2929
  if (this.submitPendingUntil > Date.now()) return;
@@ -2924,26 +2962,7 @@ var init_provider_cli_adapter = __esm({
2924
2962
  }, _ProviderCliAdapter.FINISH_RETRY_DELAY_MS);
2925
2963
  return;
2926
2964
  }
2927
- if (this.responseTimeout) {
2928
- clearTimeout(this.responseTimeout);
2929
- this.responseTimeout = null;
2930
- }
2931
- if (this.idleTimeout) {
2932
- clearTimeout(this.idleTimeout);
2933
- this.idleTimeout = null;
2934
- }
2935
- if (this.approvalExitTimeout) {
2936
- clearTimeout(this.approvalExitTimeout);
2937
- this.approvalExitTimeout = null;
2938
- }
2939
- if (this.submitRetryTimer) {
2940
- clearTimeout(this.submitRetryTimer);
2941
- this.submitRetryTimer = null;
2942
- }
2943
- if (this.finishRetryTimer) {
2944
- clearTimeout(this.finishRetryTimer);
2945
- this.finishRetryTimer = null;
2946
- }
2965
+ this.clearAllTimers();
2947
2966
  this.responseBuffer = "";
2948
2967
  this.isWaitingForResponse = false;
2949
2968
  this.responseSettleIgnoreUntil = 0;
@@ -2955,18 +2974,12 @@ var init_provider_cli_adapter = __esm({
2955
2974
  this.setStatus("idle", "response_finished");
2956
2975
  this.onStatusChange?.();
2957
2976
  }
2958
- maybeCommitVisibleIdleTranscript(parsed, options) {
2977
+ maybeCommitVisibleIdleTranscript(parsed) {
2959
2978
  const allowImmediateScriptIdleCommit = this.provider.allowInputDuringGeneration === true;
2960
2979
  if (!allowImmediateScriptIdleCommit) return false;
2961
2980
  if (!parsed || !Array.isArray(parsed.messages) || parsed.status !== "idle" || !this.isWaitingForResponse || !this.currentTurnScope || this.activeModal || parsed.activeModal) {
2962
2981
  return false;
2963
2982
  }
2964
- if (options?.requireVisibleAssistantCandidate) {
2965
- const candidateText = options.screenText || this.terminalScreen.getText() || "";
2966
- if (!this.looksLikeVisibleAssistantCandidate(candidateText)) {
2967
- return false;
2968
- }
2969
- }
2970
2983
  const hydratedForIdleCommit = normalizeCliParsedMessages(parsed.messages, {
2971
2984
  committedMessages: this.committedMessages,
2972
2985
  scope: this.currentTurnScope,
@@ -2975,33 +2988,8 @@ var init_provider_cli_adapter = __esm({
2975
2988
  const visibleAssistant = [...hydratedForIdleCommit].reverse().find((message) => message.role === "assistant" && message.content.trim());
2976
2989
  if (!visibleAssistant) return false;
2977
2990
  this.committedMessages = hydratedForIdleCommit;
2978
- const promptForTrim = this.currentTurnScope?.prompt || getLastUserPromptText(this.committedMessages);
2979
- if (promptForTrim) {
2980
- const lastAssistantForTrim = [...this.committedMessages].reverse().find((message) => message.role === "assistant");
2981
- if (lastAssistantForTrim) {
2982
- lastAssistantForTrim.content = trimPromptEchoPrefix(lastAssistantForTrim.content, promptForTrim);
2983
- }
2984
- }
2985
- if (this.responseTimeout) {
2986
- clearTimeout(this.responseTimeout);
2987
- this.responseTimeout = null;
2988
- }
2989
- if (this.idleTimeout) {
2990
- clearTimeout(this.idleTimeout);
2991
- this.idleTimeout = null;
2992
- }
2993
- if (this.approvalExitTimeout) {
2994
- clearTimeout(this.approvalExitTimeout);
2995
- this.approvalExitTimeout = null;
2996
- }
2997
- if (this.submitRetryTimer) {
2998
- clearTimeout(this.submitRetryTimer);
2999
- this.submitRetryTimer = null;
3000
- }
3001
- if (this.finishRetryTimer) {
3002
- clearTimeout(this.finishRetryTimer);
3003
- this.finishRetryTimer = null;
3004
- }
2991
+ this.trimLastAssistantEcho(this.committedMessages, this.currentTurnScope?.prompt || getLastUserPromptText(this.committedMessages));
2992
+ this.clearAllTimers();
3005
2993
  this.syncMessageViews();
3006
2994
  this.responseBuffer = "";
3007
2995
  this.isWaitingForResponse = false;
@@ -3031,13 +3019,7 @@ var init_provider_cli_adapter = __esm({
3031
3019
  scope: this.currentTurnScope,
3032
3020
  lastOutputAt: this.lastOutputAt
3033
3021
  });
3034
- const promptForTrim = this.currentTurnScope?.prompt || getLastUserPromptText(this.committedMessages);
3035
- if (promptForTrim) {
3036
- const lastAssistantForTrim = [...this.committedMessages].reverse().find((message) => message.role === "assistant");
3037
- if (lastAssistantForTrim) {
3038
- lastAssistantForTrim.content = trimPromptEchoPrefix(lastAssistantForTrim.content, promptForTrim);
3039
- }
3040
- }
3022
+ this.trimLastAssistantEcho(this.committedMessages, this.currentTurnScope?.prompt || getLastUserPromptText(this.committedMessages));
3041
3023
  this.syncMessageViews();
3042
3024
  const lastAssistant = [...this.committedMessages].reverse().find((message) => message.role === "assistant");
3043
3025
  if (this.currentTurnScope) {
@@ -3095,7 +3077,7 @@ var init_provider_cli_adapter = __esm({
3095
3077
  screen: buildCliScreenSnapshot(screenText),
3096
3078
  tailScreen: buildCliScreenSnapshot(text.slice(-500))
3097
3079
  });
3098
- return this.refineDetectedStatus(status, text, screenText || "");
3080
+ return status;
3099
3081
  } catch (e) {
3100
3082
  LOG.warn("CLI", `[${this.cliType}] detectStatus error: ${e.message}`);
3101
3083
  return null;
@@ -3135,23 +3117,21 @@ var init_provider_cli_adapter = __esm({
3135
3117
  if (!inApprovalCooldown) {
3136
3118
  return parsed;
3137
3119
  }
3138
- const startupModal = this.getStartupConfirmationModal(screenText || "");
3139
- const visibleModal = this.runParseApproval(recentBuffer) || startupModal;
3120
+ const visibleModal = this.runParseApproval(recentBuffer);
3140
3121
  if (visibleModal) {
3141
3122
  return parsed;
3142
3123
  }
3143
3124
  const detectedStatus = this.runDetectStatus(recentBuffer);
3144
- const fallbackStatus = detectedStatus && detectedStatus !== "waiting_approval" ? detectedStatus : this.isWaitingForResponse || this.currentTurnScope ? "generating" : this.currentStatus === "waiting_approval" ? "idle" : this.currentStatus;
3125
+ const resolvedStatus = detectedStatus && detectedStatus !== "waiting_approval" ? detectedStatus : this.isWaitingForResponse || this.currentTurnScope ? "generating" : this.currentStatus === "waiting_approval" ? "idle" : this.currentStatus;
3145
3126
  return {
3146
3127
  ...parsed,
3147
- status: fallbackStatus,
3128
+ status: resolvedStatus,
3148
3129
  activeModal: null
3149
3130
  };
3150
3131
  }
3151
3132
  // ─── Public API (CliAdapter) ───────────────────
3152
3133
  getStatus() {
3153
- const screenText = this.terminalScreen.getText() || "";
3154
- const startupModal = this.startupParseGate ? this.getStartupConfirmationModal(screenText) : null;
3134
+ const startupModal = this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
3155
3135
  let effectiveStatus = this.projectEffectiveStatus(startupModal);
3156
3136
  let effectiveModal = startupModal || this.activeModal;
3157
3137
  if (!startupModal && !effectiveModal && typeof this.cliScripts?.parseOutput === "function") {
@@ -3232,8 +3212,7 @@ var init_provider_cli_adapter = __esm({
3232
3212
  receivedAt: typeof message.receivedAt === "number" ? message.receivedAt : message.timestamp
3233
3213
  }));
3234
3214
  const parsedLastAssistant = [...parsedHydratedMessages].reverse().find((message) => message.role === "assistant" && typeof message.content === "string" && message.content.trim());
3235
- const visibleIdlePrompt = this.looksLikeVisibleIdlePrompt(screenText);
3236
- const shouldAdoptParsedIdleReplay = !this.currentTurnScope && !this.activeModal && !!parsedLastAssistant && parsedTranscriptIsRicherThanCommitted(parsedHydratedMessages, committedHydratedMessages) && (this.currentStatus === "idle" || this.currentStatus === "generating" && this.isWaitingForResponse && parsed.status === "idle" && visibleIdlePrompt);
3215
+ const shouldAdoptParsedIdleReplay = !this.currentTurnScope && !this.activeModal && !!parsedLastAssistant && parsedTranscriptIsRicherThanCommitted(parsedHydratedMessages, committedHydratedMessages) && (this.currentStatus === "idle" || this.currentStatus === "generating" && this.isWaitingForResponse && parsed.status === "idle" && this.runDetectStatus(this.recentOutputBuffer) === "idle");
3237
3216
  if (shouldAdoptParsedIdleReplay) {
3238
3217
  this.committedMessages = normalizeCliParsedMessages(parsed.messages, {
3239
3218
  committedMessages: this.committedMessages,
@@ -3362,17 +3341,9 @@ var init_provider_cli_adapter = __esm({
3362
3341
  if (parsed && typeof parsed === "object") {
3363
3342
  Object.assign(parsed, validateReadChatResultPayload(parsed, `${this.cliType} parseOutput`));
3364
3343
  }
3365
- const refinedStatus = this.refineDetectedStatus(typeof parsed?.status === "string" ? parsed.status : null, input.recentBuffer, input.screenText);
3366
- if (parsed && refinedStatus && parsed.status !== refinedStatus) {
3367
- parsed.status = refinedStatus;
3368
- }
3369
3344
  const normalizedParsed = this.suppressStaleParsedApproval(parsed, input.recentBuffer, input.screenText);
3370
- const promptForTrim = scope?.prompt || getLastUserPromptText(baseMessages);
3371
- if (normalizedParsed && Array.isArray(normalizedParsed.messages) && promptForTrim) {
3372
- const lastAssistant = [...normalizedParsed.messages].reverse().find((message) => message?.role === "assistant" && typeof message.content === "string");
3373
- if (lastAssistant) {
3374
- lastAssistant.content = trimPromptEchoPrefix(lastAssistant.content, promptForTrim);
3375
- }
3345
+ if (normalizedParsed && Array.isArray(normalizedParsed.messages)) {
3346
+ this.trimLastAssistantEcho(normalizedParsed.messages, scope?.prompt || getLastUserPromptText(baseMessages));
3376
3347
  }
3377
3348
  this.parseErrorMessage = null;
3378
3349
  return normalizedParsed;
@@ -3400,16 +3371,11 @@ var init_provider_cli_adapter = __esm({
3400
3371
  LOG.warn("CLI", `[${this.cliType}] resolveAction error: ${e.message}`);
3401
3372
  }
3402
3373
  }
3403
- if (!promptText && data) {
3404
- promptText = `Please fix the following issue:
3405
- ${data.title || ""}
3406
- ${data.explanation || ""}
3407
-
3408
- ${data.message || ""}`.trim();
3409
- }
3410
- if (promptText) {
3411
- await this.sendMessage(promptText);
3374
+ if (!promptText) {
3375
+ LOG.warn("CLI", `[${this.cliType}] resolveAction skipped: provider script did not supply a prompt`);
3376
+ return;
3412
3377
  }
3378
+ await this.sendMessage(promptText);
3413
3379
  }
3414
3380
  async sendMessage(text) {
3415
3381
  if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
@@ -3427,9 +3393,7 @@ ${data.message || ""}`.trim();
3427
3393
  }
3428
3394
  if (!this.ready) {
3429
3395
  this.resolveStartupState("send_precheck");
3430
- const screenText = this.terminalScreen.getText() || "";
3431
- const hasPrompt = this.looksLikeVisibleIdlePrompt(screenText);
3432
- if (hasPrompt && this.currentStatus === "idle") {
3396
+ if (this.runDetectStatus(this.recentOutputBuffer) === "idle" && this.currentStatus === "idle") {
3433
3397
  this.ready = true;
3434
3398
  this.startupParseGate = false;
3435
3399
  LOG.info("CLI", `[${this.cliType}] sendMessage recovered idle prompt readiness`);
@@ -3535,7 +3499,10 @@ ${data.message || ""}`.trim();
3535
3499
  if (this.hasMeaningfulResponseBuffer(normalizedPromptSnippet)) return;
3536
3500
  const screenText2 = this.terminalScreen.getText();
3537
3501
  if (!promptLikelyVisible(screenText2, normalizedPromptSnippet)) return;
3538
- if (/Esc to interrupt|Do you want to proceed|This command requires approval|Allow Codex to|Approve and run now|Always approve this session|Running…|Running\.\.\./i.test(screenText2)) return;
3502
+ const liveApproval = this.runParseApproval(screenText2) || this.runParseApproval(this.recentOutputBuffer);
3503
+ if (liveApproval) return;
3504
+ const liveStatus = this.runDetectStatus(screenText2) || this.runDetectStatus(this.recentOutputBuffer);
3505
+ if (liveStatus === "generating" || liveStatus === "waiting_approval") return;
3539
3506
  this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
3540
3507
  LOG.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt ${attempt})`);
3541
3508
  this.recordTrace("submit_write", {
@@ -3572,6 +3539,10 @@ ${data.message || ""}`.trim();
3572
3539
  if (this.hasMeaningfulResponseBuffer(normalizedPromptSnippet)) return;
3573
3540
  const screenText = this.terminalScreen.getText();
3574
3541
  if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
3542
+ const liveApproval = this.runParseApproval(screenText) || this.runParseApproval(this.recentOutputBuffer);
3543
+ if (liveApproval) return;
3544
+ const liveStatus = this.runDetectStatus(screenText) || this.runDetectStatus(this.recentOutputBuffer);
3545
+ if (liveStatus === "generating" || liveStatus === "waiting_approval") return;
3575
3546
  LOG.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt 1)`);
3576
3547
  this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
3577
3548
  this.recordTrace("submit_write", {
@@ -3703,44 +3674,9 @@ ${data.message || ""}`.trim();
3703
3674
  }
3704
3675
  shutdown() {
3705
3676
  this.clearIdleFinishCandidate("shutdown");
3706
- if (this.settleTimer) {
3707
- clearTimeout(this.settleTimer);
3708
- this.settleTimer = null;
3709
- }
3710
- if (this.approvalExitTimeout) {
3711
- clearTimeout(this.approvalExitTimeout);
3712
- this.approvalExitTimeout = null;
3713
- }
3714
- if (this.submitRetryTimer) {
3715
- clearTimeout(this.submitRetryTimer);
3716
- this.submitRetryTimer = null;
3717
- }
3718
- if (this.finishRetryTimer) {
3719
- clearTimeout(this.finishRetryTimer);
3720
- this.finishRetryTimer = null;
3721
- }
3722
- if (this.responseTimeout) {
3723
- clearTimeout(this.responseTimeout);
3724
- this.responseTimeout = null;
3725
- }
3726
- if (this.idleTimeout) {
3727
- clearTimeout(this.idleTimeout);
3728
- this.idleTimeout = null;
3729
- }
3730
- if (this.pendingScriptStatusTimer) {
3731
- clearTimeout(this.pendingScriptStatusTimer);
3732
- this.pendingScriptStatusTimer = null;
3733
- }
3734
- if (this.pendingOutputParseTimer) {
3735
- clearTimeout(this.pendingOutputParseTimer);
3736
- this.pendingOutputParseTimer = null;
3737
- }
3677
+ this.clearAllTimers();
3738
3678
  this.pendingOutputParseBuffer = "";
3739
3679
  this.pendingTerminalQueryTail = "";
3740
- if (this.ptyOutputFlushTimer) {
3741
- clearTimeout(this.ptyOutputFlushTimer);
3742
- this.ptyOutputFlushTimer = null;
3743
- }
3744
3680
  this.ptyOutputBuffer = "";
3745
3681
  this.finishRetryCount = 0;
3746
3682
  if (this.ptyProcess) {
@@ -3761,44 +3697,9 @@ ${data.message || ""}`.trim();
3761
3697
  }
3762
3698
  detach() {
3763
3699
  this.clearIdleFinishCandidate("detach");
3764
- if (this.settleTimer) {
3765
- clearTimeout(this.settleTimer);
3766
- this.settleTimer = null;
3767
- }
3768
- if (this.approvalExitTimeout) {
3769
- clearTimeout(this.approvalExitTimeout);
3770
- this.approvalExitTimeout = null;
3771
- }
3772
- if (this.submitRetryTimer) {
3773
- clearTimeout(this.submitRetryTimer);
3774
- this.submitRetryTimer = null;
3775
- }
3776
- if (this.finishRetryTimer) {
3777
- clearTimeout(this.finishRetryTimer);
3778
- this.finishRetryTimer = null;
3779
- }
3780
- if (this.responseTimeout) {
3781
- clearTimeout(this.responseTimeout);
3782
- this.responseTimeout = null;
3783
- }
3784
- if (this.idleTimeout) {
3785
- clearTimeout(this.idleTimeout);
3786
- this.idleTimeout = null;
3787
- }
3788
- if (this.pendingScriptStatusTimer) {
3789
- clearTimeout(this.pendingScriptStatusTimer);
3790
- this.pendingScriptStatusTimer = null;
3791
- }
3792
- if (this.pendingOutputParseTimer) {
3793
- clearTimeout(this.pendingOutputParseTimer);
3794
- this.pendingOutputParseTimer = null;
3795
- }
3700
+ this.clearAllTimers();
3796
3701
  this.pendingOutputParseBuffer = "";
3797
3702
  this.pendingTerminalQueryTail = "";
3798
- if (this.ptyOutputFlushTimer) {
3799
- clearTimeout(this.ptyOutputFlushTimer);
3800
- this.ptyOutputFlushTimer = null;
3801
- }
3802
3703
  this.ptyOutputBuffer = "";
3803
3704
  this.finishRetryCount = 0;
3804
3705
  if (this.ptyProcess) {
@@ -3860,8 +3761,7 @@ ${data.message || ""}`.trim();
3860
3761
  this.ptyProcess?.write(data);
3861
3762
  }
3862
3763
  resolveModal(buttonIndex) {
3863
- const screenText = this.terminalScreen.getText() || "";
3864
- let modal = this.activeModal || this.getStartupConfirmationModal(screenText);
3764
+ let modal = this.activeModal || this.runParseApproval(this.recentOutputBuffer);
3865
3765
  if (!modal && typeof this.cliScripts?.parseOutput === "function") {
3866
3766
  try {
3867
3767
  const parsed = this.getScriptParsedStatus();
@@ -3892,12 +3792,7 @@ ${data.message || ""}`.trim();
3892
3792
  }
3893
3793
  this.setStatus("generating", "approval_resolved");
3894
3794
  this.onStatusChange?.();
3895
- const startupTrustModal = /Quick safety check|project trust|Confirm Claude Code project trust|trust (?:this project|the contents of this directory|the files in this folder)/i.test(String(modal?.message || ""));
3896
- if (startupTrustModal && buttonIndex in this.approvalKeys) {
3897
- this.ptyProcess.write(`${this.approvalKeys[buttonIndex]}\r`);
3898
- } else if (this.shouldResolveModalWithEnter(modal, buttonIndex)) {
3899
- this.ptyProcess.write("\r");
3900
- } else if (buttonIndex in this.approvalKeys) {
3795
+ if (buttonIndex in this.approvalKeys) {
3901
3796
  this.ptyProcess.write(this.approvalKeys[buttonIndex]);
3902
3797
  } else {
3903
3798
  const DOWN = "\x1B[B";
@@ -3917,7 +3812,7 @@ ${data.message || ""}`.trim();
3917
3812
  }
3918
3813
  getDebugState() {
3919
3814
  const screenText = sanitizeTerminalText(this.terminalScreen.getText());
3920
- const startupModal = this.startupParseGate ? this.getStartupConfirmationModal(screenText) : null;
3815
+ const startupModal = this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
3921
3816
  const effectiveStatus = this.projectEffectiveStatus(startupModal);
3922
3817
  const effectiveReady = this.ready || !!startupModal;
3923
3818
  return {
@@ -11250,25 +11145,23 @@ async function handleResolveAction(h, args) {
11250
11145
  const effectiveModal = statusModal || surfacedModal;
11251
11146
  const effectiveStatus = status?.status === "waiting_approval" || targetState?.activeChat?.status === "waiting_approval" ? "waiting_approval" : status?.status;
11252
11147
  LOG.info("Command", `[resolveAction] CLI PTY gate target=${String(args?.targetSessionId || "")} rawStatus=${String(status?.status || "")} effectiveStatus=${String(effectiveStatus || "")} statusModal=${statusModal ? "yes" : "no"} surfacedModal=${surfacedModal ? "yes" : "no"} instance=${targetInstance ? "yes" : "no"}`);
11253
- if (effectiveStatus !== "waiting_approval" && !effectiveModal) {
11148
+ if (!effectiveModal) {
11254
11149
  return { success: false, error: "Not in approval state" };
11255
11150
  }
11256
- const buttons = effectiveModal?.buttons || ["Allow once", "Always allow", "Deny"];
11151
+ const buttons = Array.isArray(effectiveModal.buttons) ? effectiveModal.buttons : [];
11257
11152
  let buttonIndex = typeof args?.buttonIndex === "number" ? args.buttonIndex : -1;
11258
- if (buttonIndex < 0) {
11153
+ if (buttonIndex < 0 && button) {
11259
11154
  const btnLower = button.toLowerCase();
11260
11155
  buttonIndex = buttons.findIndex((b) => b.toLowerCase().includes(btnLower));
11261
11156
  }
11157
+ if (buttonIndex < 0 && (action === "reject" || action === "deny")) {
11158
+ buttonIndex = buttons.findIndex((b) => /deny|reject|no/i.test(b));
11159
+ }
11160
+ if (buttonIndex < 0 && (action === "always" || /always/i.test(button))) {
11161
+ buttonIndex = buttons.findIndex((b) => /always/i.test(b));
11162
+ }
11262
11163
  if (buttonIndex < 0) {
11263
- if (action === "reject" || action === "deny") {
11264
- buttonIndex = buttons.findIndex((b) => /deny|reject|no/i.test(b));
11265
- if (buttonIndex < 0) buttonIndex = buttons.length - 1;
11266
- } else if (action === "always" || /always/i.test(button)) {
11267
- buttonIndex = buttons.findIndex((b) => /always/i.test(b));
11268
- if (buttonIndex < 0) buttonIndex = 1;
11269
- } else {
11270
- buttonIndex = 0;
11271
- }
11164
+ return { success: false, error: "Approval action did not match any visible button" };
11272
11165
  }
11273
11166
  if (typeof adapter.resolveModal === "function") {
11274
11167
  adapter.resolveModal(buttonIndex);