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