@adhdev/daemon-core 0.9.6 → 0.9.7

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,14 @@ 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;
2623
- const rawScriptStatus = this.runDetectStatus(tail);
2624
- const scriptStatus = startupModal ? "waiting_approval" : parsedModal && parsedTranscript?.status === "waiting_approval" ? "waiting_approval" : rawScriptStatus;
2644
+ const modal = this.runParseApproval(tail) || parsedModal;
2645
+ const scriptStatus = this.runDetectStatus(tail);
2625
2646
  const parsedMessages = Array.isArray(parsedTranscript?.messages) ? normalizeCliParsedMessages(parsedTranscript.messages, {
2626
2647
  committedMessages: this.committedMessages,
2627
2648
  scope: this.currentTurnScope,
@@ -2676,15 +2697,44 @@ var init_provider_cli_adapter = __esm({
2676
2697
  }
2677
2698
  if (!scriptStatus) return;
2678
2699
  const prevStatus = this.currentStatus;
2679
- const clearPendingScriptStatus = () => {
2700
+ const ctx = { now, screenText, modal, scriptStatus, parsedTranscript, parsedMessages, lastParsedAssistant, parsedShowsLiveAssistantProgress, prevStatus };
2701
+ if (!this.applyPendingScriptStatusDebounce(ctx)) return;
2702
+ const recentInteractiveActivity = this.hasRecentInteractiveActivity(now);
2703
+ LOG.info(
2704
+ "CLI",
2705
+ `[${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)}`
2706
+ );
2707
+ const shouldHoldGenerating = scriptStatus === "idle" && this.isWaitingForResponse && !modal && recentInteractiveActivity && !(parsedTranscript?.status === "idle" && !!lastParsedAssistant);
2708
+ if (shouldHoldGenerating) {
2709
+ this.applyHoldGenerating(ctx, recentInteractiveActivity);
2710
+ return;
2711
+ }
2712
+ if (scriptStatus === "waiting_approval") {
2713
+ this.applyWaitingApproval(ctx);
2714
+ return;
2715
+ }
2716
+ if (scriptStatus === "generating") {
2717
+ this.applyGenerating(ctx);
2718
+ return;
2719
+ }
2720
+ if (scriptStatus === "idle") {
2721
+ this.applyIdle(ctx, now);
2722
+ }
2723
+ }
2724
+ // Returns false if the caller should bail out (debounce pending).
2725
+ applyPendingScriptStatusDebounce(ctx) {
2726
+ const { now, scriptStatus, prevStatus } = ctx;
2727
+ const shouldDebounce = prevStatus === "idle" && !this.isWaitingForResponse && !this.currentTurnScope && (scriptStatus === "generating" || scriptStatus === "waiting_approval");
2728
+ if (!shouldDebounce) {
2680
2729
  this.pendingScriptStatus = null;
2681
2730
  this.pendingScriptStatusSince = 0;
2682
2731
  if (this.pendingScriptStatusTimer) {
2683
2732
  clearTimeout(this.pendingScriptStatusTimer);
2684
2733
  this.pendingScriptStatusTimer = null;
2685
2734
  }
2686
- };
2687
- const armPendingScriptStatus = (delayMs) => {
2735
+ return true;
2736
+ }
2737
+ const armPending = (delayMs) => {
2688
2738
  if (this.pendingScriptStatusTimer) clearTimeout(this.pendingScriptStatusTimer);
2689
2739
  this.pendingScriptStatusTimer = setTimeout(() => {
2690
2740
  this.pendingScriptStatusTimer = null;
@@ -2692,200 +2742,187 @@ var init_provider_cli_adapter = __esm({
2692
2742
  this.evaluateSettled();
2693
2743
  }, delayMs);
2694
2744
  };
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
- );
2745
+ if (this.pendingScriptStatus !== scriptStatus) {
2746
+ this.pendingScriptStatus = scriptStatus;
2747
+ this.pendingScriptStatusSince = now;
2748
+ armPending(_ProviderCliAdapter.SCRIPT_STATUS_DEBOUNCE_MS);
2749
+ return false;
2720
2750
  }
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;
2751
+ const elapsed = now - this.pendingScriptStatusSince;
2752
+ if (elapsed < _ProviderCliAdapter.SCRIPT_STATUS_DEBOUNCE_MS) {
2753
+ armPending(_ProviderCliAdapter.SCRIPT_STATUS_DEBOUNCE_MS - elapsed);
2754
+ return false;
2748
2755
  }
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;
2756
+ return true;
2757
+ }
2758
+ applyHoldGenerating(ctx, recentInteractiveActivity) {
2759
+ const { scriptStatus } = ctx;
2760
+ this.clearIdleFinishCandidate("hold_generating_recent_activity");
2761
+ this.setStatus("generating", "recent_activity_hold");
2762
+ if (this.idleTimeout) clearTimeout(this.idleTimeout);
2763
+ this.idleTimeout = setTimeout(() => {
2764
+ if (this.isWaitingForResponse && this.currentStatus !== "waiting_approval") {
2765
+ if (this.shouldDeferIdleTimeoutFinish()) return;
2766
+ this.finishResponse();
2767
+ }
2768
+ }, this.timeouts.generatingIdle);
2769
+ this.recordTrace("hold_generating_recent_activity", {
2770
+ scriptStatus,
2771
+ recentInteractiveActivity,
2772
+ lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
2773
+ lastScreenChangeAt: this.lastScreenChangeAt,
2774
+ holdMs: this.getStatusActivityHoldMs(),
2775
+ ...buildCliTraceParseSnapshot({
2776
+ accumulatedBuffer: this.accumulatedBuffer,
2777
+ accumulatedRawBuffer: this.accumulatedRawBuffer,
2778
+ responseBuffer: this.responseBuffer,
2779
+ partialResponse: this.responseBuffer,
2780
+ scope: this.currentTurnScope
2781
+ })
2782
+ });
2783
+ this.onStatusChange?.();
2784
+ }
2785
+ applyWaitingApproval(ctx) {
2786
+ const { modal } = ctx;
2787
+ this.clearIdleFinishCandidate("waiting_approval");
2788
+ const inCooldown = this.lastApprovalResolvedAt && Date.now() - this.lastApprovalResolvedAt < this.timeouts.approvalCooldown;
2789
+ if (inCooldown && !modal) {
2790
+ if (this.approvalExitTimeout) {
2791
+ clearTimeout(this.approvalExitTimeout);
2792
+ this.approvalExitTimeout = null;
2773
2793
  }
2774
- if (!inCooldown) {
2775
- this.isWaitingForResponse = true;
2776
- this.setStatus("waiting_approval", "script_detect");
2777
- this.activeModal = modal || { message: "Approval required", buttons: ["Allow", "Deny"] };
2794
+ this.activeModal = null;
2795
+ if (this.isWaitingForResponse) {
2796
+ this.setStatus("generating", inCooldown ? "approval_cooldown_ignore" : "approval_prompt_gone");
2778
2797
  if (this.idleTimeout) clearTimeout(this.idleTimeout);
2779
- this.armApprovalExitTimeout();
2780
- this.onStatusChange?.();
2781
- return;
2798
+ this.idleTimeout = setTimeout(() => {
2799
+ if (this.isWaitingForResponse && this.currentStatus !== "waiting_approval") {
2800
+ if (this.shouldDeferIdleTimeoutFinish()) return;
2801
+ this.finishResponse();
2802
+ }
2803
+ }, this.timeouts.generatingIdle);
2804
+ } else {
2805
+ this.setStatus("idle", inCooldown ? "approval_cooldown_ignore" : "approval_prompt_gone");
2782
2806
  }
2807
+ this.onStatusChange?.();
2808
+ return;
2783
2809
  }
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) {
2810
+ if (!inCooldown) {
2811
+ if (!modal) {
2812
+ LOG.warn("CLI", `[${this.cliType}] detectStatus reported waiting_approval without parseApproval modal; ignoring non-actionable approval state`);
2790
2813
  return;
2791
2814
  }
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");
2815
+ this.isWaitingForResponse = true;
2816
+ this.setStatus("waiting_approval", "script_detect");
2817
+ this.activeModal = modal;
2805
2818
  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);
2819
+ this.armApprovalExitTimeout();
2812
2820
  this.onStatusChange?.();
2821
+ }
2822
+ }
2823
+ applyGenerating(ctx) {
2824
+ const { screenText, modal, parsedShowsLiveAssistantProgress, prevStatus } = ctx;
2825
+ this.clearIdleFinishCandidate("generating");
2826
+ const effectiveScreenText = screenText || this.accumulatedBuffer;
2827
+ const noActiveTurn = !this.currentTurnScope;
2828
+ 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));
2829
+ if (prevStatus === "idle" && !this.isWaitingForResponse && noActiveTurn && !modal && looksIdleChrome && !parsedShowsLiveAssistantProgress) {
2813
2830
  return;
2814
2831
  }
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();
2832
+ if (prevStatus === "waiting_approval") {
2833
+ if (this.approvalExitTimeout) {
2834
+ clearTimeout(this.approvalExitTimeout);
2835
+ this.approvalExitTimeout = null;
2823
2836
  }
2837
+ this.activeModal = null;
2838
+ this.lastApprovalResolvedAt = Date.now();
2839
+ }
2840
+ if (!this.isWaitingForResponse) {
2841
+ this.isWaitingForResponse = true;
2842
+ this.responseBuffer = "";
2843
+ }
2844
+ this.setStatus("generating", "script_detect");
2845
+ if (this.idleTimeout) clearTimeout(this.idleTimeout);
2846
+ this.idleTimeout = setTimeout(() => {
2824
2847
  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") {
2848
+ if (this.shouldDeferIdleTimeoutFinish()) return;
2849
+ this.finishResponse();
2850
+ }
2851
+ }, this.timeouts.generatingIdle);
2852
+ this.onStatusChange?.();
2853
+ }
2854
+ applyIdle(ctx, now) {
2855
+ const { screenText, modal, lastParsedAssistant, prevStatus } = ctx;
2856
+ if (prevStatus === "waiting_approval") {
2857
+ if (this.approvalExitTimeout) {
2858
+ clearTimeout(this.approvalExitTimeout);
2859
+ this.approvalExitTimeout = null;
2860
+ }
2861
+ this.activeModal = null;
2862
+ this.lastApprovalResolvedAt = Date.now();
2863
+ }
2864
+ if (!this.isWaitingForResponse) {
2865
+ if (prevStatus !== "idle") {
2884
2866
  this.clearIdleFinishCandidate("idle_without_response");
2885
2867
  this.setStatus("idle", "script_detect");
2886
2868
  this.onStatusChange?.();
2887
2869
  }
2870
+ return;
2871
+ }
2872
+ const quietForMs = this.lastNonEmptyOutputAt ? now - this.lastNonEmptyOutputAt : Number.MAX_SAFE_INTEGER;
2873
+ const screenStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
2874
+ const hasAssistantTurn = !!lastParsedAssistant;
2875
+ const assistantLength = lastParsedAssistant?.content?.length || 0;
2876
+ const idleFinishConfirmMs = this.getIdleFinishConfirmMs();
2877
+ const idleQuietThresholdMs = Math.max(idleFinishConfirmMs, this.timeouts.outputSettle);
2878
+ const idleReady = !modal && hasAssistantTurn && quietForMs >= idleQuietThresholdMs && screenStableMs >= idleFinishConfirmMs;
2879
+ const candidate = this.idleFinishCandidate;
2880
+ const candidateQuiet = !!candidate && candidate.responseEpoch === this.responseEpoch && candidate.lastOutputAt === this.lastOutputAt && candidate.lastScreenChangeAt === this.lastScreenChangeAt && assistantLength >= candidate.assistantLength && now - candidate.armedAt >= idleFinishConfirmMs;
2881
+ this.recordTrace("idle_decision", {
2882
+ quietForMs,
2883
+ screenStableMs,
2884
+ hasAssistantTurn,
2885
+ assistantLength,
2886
+ hasModal: !!modal,
2887
+ idleQuietThresholdMs,
2888
+ idleStableThresholdMs: idleFinishConfirmMs,
2889
+ idleReady,
2890
+ idleFinishConfirmMs,
2891
+ idleFinishCandidate: candidate,
2892
+ candidateQuiet,
2893
+ canFinishImmediately: idleReady && candidateQuiet,
2894
+ submitPendingUntil: this.submitPendingUntil,
2895
+ responseSettleIgnoreUntil: this.responseSettleIgnoreUntil,
2896
+ ...buildCliTraceParseSnapshot({
2897
+ accumulatedBuffer: this.accumulatedBuffer,
2898
+ accumulatedRawBuffer: this.accumulatedRawBuffer,
2899
+ responseBuffer: this.responseBuffer,
2900
+ partialResponse: this.responseBuffer,
2901
+ scope: this.currentTurnScope
2902
+ })
2903
+ });
2904
+ if (idleReady && candidateQuiet) {
2905
+ this.clearIdleFinishCandidate("finish_response");
2906
+ if (this.idleTimeout) clearTimeout(this.idleTimeout);
2907
+ this.finishResponse();
2908
+ return;
2888
2909
  }
2910
+ if (idleReady) {
2911
+ if (!candidate) {
2912
+ this.armIdleFinishCandidate(assistantLength);
2913
+ return;
2914
+ }
2915
+ } else {
2916
+ this.clearIdleFinishCandidate("idle_not_ready");
2917
+ }
2918
+ if (this.idleTimeout) clearTimeout(this.idleTimeout);
2919
+ this.idleTimeout = setTimeout(() => {
2920
+ if (this.isWaitingForResponse && this.currentStatus !== "waiting_approval") {
2921
+ if (this.shouldDeferIdleTimeoutFinish()) return;
2922
+ this.clearIdleFinishCandidate("idle_timeout_finish");
2923
+ this.finishResponse();
2924
+ }
2925
+ }, this.timeouts.idleFinish);
2889
2926
  }
2890
2927
  finishResponse() {
2891
2928
  if (this.submitPendingUntil > Date.now()) return;
@@ -2924,26 +2961,7 @@ var init_provider_cli_adapter = __esm({
2924
2961
  }, _ProviderCliAdapter.FINISH_RETRY_DELAY_MS);
2925
2962
  return;
2926
2963
  }
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
- }
2964
+ this.clearAllTimers();
2947
2965
  this.responseBuffer = "";
2948
2966
  this.isWaitingForResponse = false;
2949
2967
  this.responseSettleIgnoreUntil = 0;
@@ -2955,18 +2973,12 @@ var init_provider_cli_adapter = __esm({
2955
2973
  this.setStatus("idle", "response_finished");
2956
2974
  this.onStatusChange?.();
2957
2975
  }
2958
- maybeCommitVisibleIdleTranscript(parsed, options) {
2976
+ maybeCommitVisibleIdleTranscript(parsed) {
2959
2977
  const allowImmediateScriptIdleCommit = this.provider.allowInputDuringGeneration === true;
2960
2978
  if (!allowImmediateScriptIdleCommit) return false;
2961
2979
  if (!parsed || !Array.isArray(parsed.messages) || parsed.status !== "idle" || !this.isWaitingForResponse || !this.currentTurnScope || this.activeModal || parsed.activeModal) {
2962
2980
  return false;
2963
2981
  }
2964
- if (options?.requireVisibleAssistantCandidate) {
2965
- const candidateText = options.screenText || this.terminalScreen.getText() || "";
2966
- if (!this.looksLikeVisibleAssistantCandidate(candidateText)) {
2967
- return false;
2968
- }
2969
- }
2970
2982
  const hydratedForIdleCommit = normalizeCliParsedMessages(parsed.messages, {
2971
2983
  committedMessages: this.committedMessages,
2972
2984
  scope: this.currentTurnScope,
@@ -2975,33 +2987,8 @@ var init_provider_cli_adapter = __esm({
2975
2987
  const visibleAssistant = [...hydratedForIdleCommit].reverse().find((message) => message.role === "assistant" && message.content.trim());
2976
2988
  if (!visibleAssistant) return false;
2977
2989
  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
- }
2990
+ this.trimLastAssistantEcho(this.committedMessages, this.currentTurnScope?.prompt || getLastUserPromptText(this.committedMessages));
2991
+ this.clearAllTimers();
3005
2992
  this.syncMessageViews();
3006
2993
  this.responseBuffer = "";
3007
2994
  this.isWaitingForResponse = false;
@@ -3031,13 +3018,7 @@ var init_provider_cli_adapter = __esm({
3031
3018
  scope: this.currentTurnScope,
3032
3019
  lastOutputAt: this.lastOutputAt
3033
3020
  });
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
- }
3021
+ this.trimLastAssistantEcho(this.committedMessages, this.currentTurnScope?.prompt || getLastUserPromptText(this.committedMessages));
3041
3022
  this.syncMessageViews();
3042
3023
  const lastAssistant = [...this.committedMessages].reverse().find((message) => message.role === "assistant");
3043
3024
  if (this.currentTurnScope) {
@@ -3095,7 +3076,7 @@ var init_provider_cli_adapter = __esm({
3095
3076
  screen: buildCliScreenSnapshot(screenText),
3096
3077
  tailScreen: buildCliScreenSnapshot(text.slice(-500))
3097
3078
  });
3098
- return this.refineDetectedStatus(status, text, screenText || "");
3079
+ return status;
3099
3080
  } catch (e) {
3100
3081
  LOG.warn("CLI", `[${this.cliType}] detectStatus error: ${e.message}`);
3101
3082
  return null;
@@ -3135,23 +3116,21 @@ var init_provider_cli_adapter = __esm({
3135
3116
  if (!inApprovalCooldown) {
3136
3117
  return parsed;
3137
3118
  }
3138
- const startupModal = this.getStartupConfirmationModal(screenText || "");
3139
- const visibleModal = this.runParseApproval(recentBuffer) || startupModal;
3119
+ const visibleModal = this.runParseApproval(recentBuffer);
3140
3120
  if (visibleModal) {
3141
3121
  return parsed;
3142
3122
  }
3143
3123
  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;
3124
+ const resolvedStatus = detectedStatus && detectedStatus !== "waiting_approval" ? detectedStatus : this.isWaitingForResponse || this.currentTurnScope ? "generating" : this.currentStatus === "waiting_approval" ? "idle" : this.currentStatus;
3145
3125
  return {
3146
3126
  ...parsed,
3147
- status: fallbackStatus,
3127
+ status: resolvedStatus,
3148
3128
  activeModal: null
3149
3129
  };
3150
3130
  }
3151
3131
  // ─── Public API (CliAdapter) ───────────────────
3152
3132
  getStatus() {
3153
- const screenText = this.terminalScreen.getText() || "";
3154
- const startupModal = this.startupParseGate ? this.getStartupConfirmationModal(screenText) : null;
3133
+ const startupModal = this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
3155
3134
  let effectiveStatus = this.projectEffectiveStatus(startupModal);
3156
3135
  let effectiveModal = startupModal || this.activeModal;
3157
3136
  if (!startupModal && !effectiveModal && typeof this.cliScripts?.parseOutput === "function") {
@@ -3232,8 +3211,7 @@ var init_provider_cli_adapter = __esm({
3232
3211
  receivedAt: typeof message.receivedAt === "number" ? message.receivedAt : message.timestamp
3233
3212
  }));
3234
3213
  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);
3214
+ 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
3215
  if (shouldAdoptParsedIdleReplay) {
3238
3216
  this.committedMessages = normalizeCliParsedMessages(parsed.messages, {
3239
3217
  committedMessages: this.committedMessages,
@@ -3362,17 +3340,9 @@ var init_provider_cli_adapter = __esm({
3362
3340
  if (parsed && typeof parsed === "object") {
3363
3341
  Object.assign(parsed, validateReadChatResultPayload(parsed, `${this.cliType} parseOutput`));
3364
3342
  }
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
3343
  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
- }
3344
+ if (normalizedParsed && Array.isArray(normalizedParsed.messages)) {
3345
+ this.trimLastAssistantEcho(normalizedParsed.messages, scope?.prompt || getLastUserPromptText(baseMessages));
3376
3346
  }
3377
3347
  this.parseErrorMessage = null;
3378
3348
  return normalizedParsed;
@@ -3400,16 +3370,11 @@ var init_provider_cli_adapter = __esm({
3400
3370
  LOG.warn("CLI", `[${this.cliType}] resolveAction error: ${e.message}`);
3401
3371
  }
3402
3372
  }
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);
3373
+ if (!promptText) {
3374
+ LOG.warn("CLI", `[${this.cliType}] resolveAction skipped: provider script did not supply a prompt`);
3375
+ return;
3412
3376
  }
3377
+ await this.sendMessage(promptText);
3413
3378
  }
3414
3379
  async sendMessage(text) {
3415
3380
  if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
@@ -3427,9 +3392,7 @@ ${data.message || ""}`.trim();
3427
3392
  }
3428
3393
  if (!this.ready) {
3429
3394
  this.resolveStartupState("send_precheck");
3430
- const screenText = this.terminalScreen.getText() || "";
3431
- const hasPrompt = this.looksLikeVisibleIdlePrompt(screenText);
3432
- if (hasPrompt && this.currentStatus === "idle") {
3395
+ if (this.runDetectStatus(this.recentOutputBuffer) === "idle" && this.currentStatus === "idle") {
3433
3396
  this.ready = true;
3434
3397
  this.startupParseGate = false;
3435
3398
  LOG.info("CLI", `[${this.cliType}] sendMessage recovered idle prompt readiness`);
@@ -3535,7 +3498,10 @@ ${data.message || ""}`.trim();
3535
3498
  if (this.hasMeaningfulResponseBuffer(normalizedPromptSnippet)) return;
3536
3499
  const screenText2 = this.terminalScreen.getText();
3537
3500
  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;
3501
+ const liveApproval = this.runParseApproval(screenText2) || this.runParseApproval(this.recentOutputBuffer);
3502
+ if (liveApproval) return;
3503
+ const liveStatus = this.runDetectStatus(screenText2) || this.runDetectStatus(this.recentOutputBuffer);
3504
+ if (liveStatus === "generating" || liveStatus === "waiting_approval") return;
3539
3505
  this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
3540
3506
  LOG.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt ${attempt})`);
3541
3507
  this.recordTrace("submit_write", {
@@ -3572,6 +3538,10 @@ ${data.message || ""}`.trim();
3572
3538
  if (this.hasMeaningfulResponseBuffer(normalizedPromptSnippet)) return;
3573
3539
  const screenText = this.terminalScreen.getText();
3574
3540
  if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
3541
+ const liveApproval = this.runParseApproval(screenText) || this.runParseApproval(this.recentOutputBuffer);
3542
+ if (liveApproval) return;
3543
+ const liveStatus = this.runDetectStatus(screenText) || this.runDetectStatus(this.recentOutputBuffer);
3544
+ if (liveStatus === "generating" || liveStatus === "waiting_approval") return;
3575
3545
  LOG.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt 1)`);
3576
3546
  this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
3577
3547
  this.recordTrace("submit_write", {
@@ -3703,44 +3673,9 @@ ${data.message || ""}`.trim();
3703
3673
  }
3704
3674
  shutdown() {
3705
3675
  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
- }
3676
+ this.clearAllTimers();
3738
3677
  this.pendingOutputParseBuffer = "";
3739
3678
  this.pendingTerminalQueryTail = "";
3740
- if (this.ptyOutputFlushTimer) {
3741
- clearTimeout(this.ptyOutputFlushTimer);
3742
- this.ptyOutputFlushTimer = null;
3743
- }
3744
3679
  this.ptyOutputBuffer = "";
3745
3680
  this.finishRetryCount = 0;
3746
3681
  if (this.ptyProcess) {
@@ -3761,44 +3696,9 @@ ${data.message || ""}`.trim();
3761
3696
  }
3762
3697
  detach() {
3763
3698
  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
- }
3699
+ this.clearAllTimers();
3796
3700
  this.pendingOutputParseBuffer = "";
3797
3701
  this.pendingTerminalQueryTail = "";
3798
- if (this.ptyOutputFlushTimer) {
3799
- clearTimeout(this.ptyOutputFlushTimer);
3800
- this.ptyOutputFlushTimer = null;
3801
- }
3802
3702
  this.ptyOutputBuffer = "";
3803
3703
  this.finishRetryCount = 0;
3804
3704
  if (this.ptyProcess) {
@@ -3860,8 +3760,7 @@ ${data.message || ""}`.trim();
3860
3760
  this.ptyProcess?.write(data);
3861
3761
  }
3862
3762
  resolveModal(buttonIndex) {
3863
- const screenText = this.terminalScreen.getText() || "";
3864
- let modal = this.activeModal || this.getStartupConfirmationModal(screenText);
3763
+ let modal = this.activeModal || this.runParseApproval(this.recentOutputBuffer);
3865
3764
  if (!modal && typeof this.cliScripts?.parseOutput === "function") {
3866
3765
  try {
3867
3766
  const parsed = this.getScriptParsedStatus();
@@ -3892,12 +3791,7 @@ ${data.message || ""}`.trim();
3892
3791
  }
3893
3792
  this.setStatus("generating", "approval_resolved");
3894
3793
  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) {
3794
+ if (buttonIndex in this.approvalKeys) {
3901
3795
  this.ptyProcess.write(this.approvalKeys[buttonIndex]);
3902
3796
  } else {
3903
3797
  const DOWN = "\x1B[B";
@@ -3917,7 +3811,7 @@ ${data.message || ""}`.trim();
3917
3811
  }
3918
3812
  getDebugState() {
3919
3813
  const screenText = sanitizeTerminalText(this.terminalScreen.getText());
3920
- const startupModal = this.startupParseGate ? this.getStartupConfirmationModal(screenText) : null;
3814
+ const startupModal = this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
3921
3815
  const effectiveStatus = this.projectEffectiveStatus(startupModal);
3922
3816
  const effectiveReady = this.ready || !!startupModal;
3923
3817
  return {
@@ -11250,25 +11144,23 @@ async function handleResolveAction(h, args) {
11250
11144
  const effectiveModal = statusModal || surfacedModal;
11251
11145
  const effectiveStatus = status?.status === "waiting_approval" || targetState?.activeChat?.status === "waiting_approval" ? "waiting_approval" : status?.status;
11252
11146
  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) {
11147
+ if (!effectiveModal) {
11254
11148
  return { success: false, error: "Not in approval state" };
11255
11149
  }
11256
- const buttons = effectiveModal?.buttons || ["Allow once", "Always allow", "Deny"];
11150
+ const buttons = Array.isArray(effectiveModal.buttons) ? effectiveModal.buttons : [];
11257
11151
  let buttonIndex = typeof args?.buttonIndex === "number" ? args.buttonIndex : -1;
11258
- if (buttonIndex < 0) {
11152
+ if (buttonIndex < 0 && button) {
11259
11153
  const btnLower = button.toLowerCase();
11260
11154
  buttonIndex = buttons.findIndex((b) => b.toLowerCase().includes(btnLower));
11261
11155
  }
11156
+ if (buttonIndex < 0 && (action === "reject" || action === "deny")) {
11157
+ buttonIndex = buttons.findIndex((b) => /deny|reject|no/i.test(b));
11158
+ }
11159
+ if (buttonIndex < 0 && (action === "always" || /always/i.test(button))) {
11160
+ buttonIndex = buttons.findIndex((b) => /always/i.test(b));
11161
+ }
11262
11162
  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
- }
11163
+ return { success: false, error: "Approval action did not match any visible button" };
11272
11164
  }
11273
11165
  if (typeof adapter.resolveModal === "function") {
11274
11166
  adapter.resolveModal(buttonIndex);