@adhdev/daemon-core 0.8.15 → 0.8.17

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
@@ -906,6 +906,40 @@ function normalizeScreenSnapshot(text) {
906
906
  function normalizeComparableMessageContent(text) {
907
907
  return String(text || "").replace(/\s+/g, " ").trim();
908
908
  }
909
+ function trimPromptEchoPrefix(text, promptText) {
910
+ const prompt = normalizeComparableMessageContent(String(promptText || ""));
911
+ if (!prompt) return String(text || "");
912
+ const lines = String(text || "").split(/\r\n|\n|\r/g);
913
+ let dropCount = 0;
914
+ for (let index = 0; index < Math.min(lines.length, 6); index += 1) {
915
+ const fragment = normalizeComparableMessageContent(lines[index].replace(/^[.…]+\s*/, ""));
916
+ if (!fragment) {
917
+ if (dropCount === index) dropCount = index + 1;
918
+ continue;
919
+ }
920
+ const fragmentWordCount = fragment ? fragment.split(/\s+/).filter(Boolean).length : 0;
921
+ const canBePromptEcho = fragment.length >= 16 || fragmentWordCount >= 4;
922
+ if (canBePromptEcho && prompt.includes(fragment)) {
923
+ dropCount = index + 1;
924
+ continue;
925
+ }
926
+ break;
927
+ }
928
+ return lines.slice(dropCount).join("\n").trim();
929
+ }
930
+ function getLastUserPromptText(messages) {
931
+ const items = Array.isArray(messages) ? messages : [];
932
+ for (let index = items.length - 1; index >= 0; index -= 1) {
933
+ const message = items[index];
934
+ if (message?.role === "user" && typeof message.content === "string" && message.content.trim()) {
935
+ return message.content;
936
+ }
937
+ }
938
+ return "";
939
+ }
940
+ function looksLikeConfirmOnlyLabel(label) {
941
+ return /^(?:continue|confirm|ok|yes|trust|proceed|enter)$/i.test(String(label || "").trim());
942
+ }
909
943
  function parsePatternEntry(x) {
910
944
  if (x instanceof RegExp) return x;
911
945
  if (x && typeof x === "object" && typeof x.source === "string") {
@@ -1039,6 +1073,8 @@ var init_provider_cli_adapter = __esm({
1039
1073
  submitRetryUsed = false;
1040
1074
  submitRetryPromptSnippet = "";
1041
1075
  idleFinishCandidate = null;
1076
+ finishRetryTimer = null;
1077
+ finishRetryCount = 0;
1042
1078
  // Resize redraw suppression
1043
1079
  resizeSuppressUntil = 0;
1044
1080
  // Debug: status transition history
@@ -1060,6 +1096,8 @@ var init_provider_cli_adapter = __esm({
1060
1096
  static MAX_TRACE_ENTRIES = 250;
1061
1097
  providerResolutionMeta;
1062
1098
  static IDLE_FINISH_CONFIRM_MS = 900;
1099
+ static FINISH_RETRY_DELAY_MS = 300;
1100
+ static MAX_FINISH_RETRIES = 2;
1063
1101
  syncMessageViews() {
1064
1102
  this.messages = [...this.committedMessages];
1065
1103
  this.structuredMessages = [...this.committedMessages];
@@ -1119,7 +1157,8 @@ var init_provider_cli_adapter = __esm({
1119
1157
  recentBuffer: buffer.slice(-1e3) || this.recentOutputBuffer,
1120
1158
  screenText: this.terminalScreen.getText(),
1121
1159
  messages: [...baseMessages],
1122
- partialResponse
1160
+ partialResponse,
1161
+ promptText: scope?.prompt || ""
1123
1162
  };
1124
1163
  }
1125
1164
  setStatus(status, trigger) {
@@ -1362,6 +1401,11 @@ var init_provider_cli_adapter = __esm({
1362
1401
  this.terminalScreen.reset(24, 80);
1363
1402
  this.pendingTerminalQueryTail = "";
1364
1403
  this.currentTurnScope = null;
1404
+ this.finishRetryCount = 0;
1405
+ if (this.finishRetryTimer) {
1406
+ clearTimeout(this.finishRetryTimer);
1407
+ this.finishRetryTimer = null;
1408
+ }
1365
1409
  this.ready = false;
1366
1410
  await this.ptyProcess.ready;
1367
1411
  this.recordTrace("ready", {
@@ -1408,11 +1452,12 @@ var init_provider_cli_adapter = __esm({
1408
1452
  if (this.startupParseGate) {
1409
1453
  this.startupBuffer += cleanData;
1410
1454
  const elapsed = Date.now() - this.spawnAt;
1411
- const scriptStatus = this.runDetectStatus(this.startupBuffer);
1412
1455
  const screenText = this.terminalScreen.getText() || "";
1456
+ const startupModal = this.getStartupConfirmationModal(screenText);
1457
+ const scriptStatus = startupModal ? "waiting_approval" : this.runDetectStatus(this.startupBuffer);
1413
1458
  const hasInteractivePrompt = this.looksLikeVisibleIdlePrompt(screenText);
1414
1459
  const startupStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
1415
- const isReady = (scriptStatus === "idle" || scriptStatus === "waiting_approval") && hasInteractivePrompt && startupStableMs >= 700 || elapsed > 8e3 || this.startupBuffer.length > 12e3;
1460
+ const isReady = (scriptStatus === "idle" || scriptStatus === "waiting_approval") && hasInteractivePrompt && startupStableMs >= 700 || !!startupModal && startupStableMs >= 700 || elapsed > 8e3 || this.startupBuffer.length > 12e3;
1416
1461
  if (isReady) {
1417
1462
  this.startupParseGate = false;
1418
1463
  this.ready = true;
@@ -1444,7 +1489,9 @@ var init_provider_cli_adapter = __esm({
1444
1489
  this.approvalExitTimeout = setTimeout(() => {
1445
1490
  if (this.currentStatus !== "waiting_approval") return;
1446
1491
  const tail = this.recentOutputBuffer;
1447
- const modal = this.runParseApproval(tail);
1492
+ const screenText = this.terminalScreen.getText() || "";
1493
+ const startupModal = this.getStartupConfirmationModal(screenText);
1494
+ const modal = this.runParseApproval(tail) || startupModal;
1448
1495
  const stillWaiting = this.runDetectStatus(tail) === "waiting_approval" || !!modal;
1449
1496
  if (stillWaiting) {
1450
1497
  this.activeModal = modal || this.activeModal || { message: "Approval required", buttons: ["Allow", "Deny"] };
@@ -1464,6 +1511,63 @@ var init_provider_cli_adapter = __esm({
1464
1511
  if (!text.trim()) return false;
1465
1512
  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);
1466
1513
  }
1514
+ looksLikeVisibleAssistantCandidate(screenText) {
1515
+ const lines = sanitizeTerminalText(String(screenText || "")).split(/\r\n|\n|\r/g);
1516
+ for (const line of lines) {
1517
+ const trimmed = String(line || "").trim();
1518
+ if (!trimmed) continue;
1519
+ if (/^➜\s+\S+/.test(trimmed)) continue;
1520
+ if (/^Update available!/i.test(trimmed)) continue;
1521
+ if (/Claude Code v\d/i.test(trimmed)) continue;
1522
+ if (/^⏵⏵\s+accept edits on/i.test(trimmed)) continue;
1523
+ if (/^[◐◑◒◓◴◵◶◷◸◹◺◿].*\/effort/i.test(trimmed)) continue;
1524
+ if (/^[✻✶✳✢✽⠂⠐⠒⠓⠦⠴⠶⠷⠿]+$/.test(trimmed)) continue;
1525
+ if (/esc to (cancel|interrupt|stop)/i.test(trimmed)) continue;
1526
+ const assistantMatch = trimmed.match(/^⏺\s+(.+)$/);
1527
+ if (!assistantMatch) continue;
1528
+ const content = assistantMatch[1].trim();
1529
+ if (!content) continue;
1530
+ if (/^(?:Bash|Read|Write|Edit|MultiEdit|Task|Glob|Grep|LS|NotebookEdit)\(/.test(content)) continue;
1531
+ if (/This command requires approval|Do you want to proceed|Allow once|Always allow/i.test(content)) continue;
1532
+ return true;
1533
+ }
1534
+ return false;
1535
+ }
1536
+ shouldRetryFinishResponse(commitResult) {
1537
+ if (!this.currentTurnScope) return false;
1538
+ if (this.currentStatus === "waiting_approval" || this.activeModal) return false;
1539
+ if (this.finishRetryCount >= _ProviderCliAdapter.MAX_FINISH_RETRIES) return false;
1540
+ if (commitResult.hasAssistant && commitResult.assistantContent.trim()) return false;
1541
+ const screenText = this.terminalScreen.getText() || "";
1542
+ if (!this.looksLikeVisibleAssistantCandidate(screenText)) return false;
1543
+ const now = Date.now();
1544
+ const quietForMs = this.lastNonEmptyOutputAt ? now - this.lastNonEmptyOutputAt : Number.MAX_SAFE_INTEGER;
1545
+ const screenStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
1546
+ return quietForMs < 1200 || screenStableMs < 1200 || !commitResult.hasAssistant;
1547
+ }
1548
+ getStartupConfirmationModal(screenText) {
1549
+ const text = sanitizeTerminalText(String(screenText || ""));
1550
+ if (!text.trim()) return null;
1551
+ if (this.cliType === "claude-cli") {
1552
+ 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);
1553
+ const hasConfirmFooter = /Press Enter to (?:continue|confirm)/i.test(text) || /Enter to confirm/i.test(text) || /Esc to (?:cancel|exit)/i.test(text);
1554
+ if (hasTrustPrompt || hasConfirmFooter && /trust/i.test(text)) {
1555
+ return {
1556
+ message: "Confirm Claude Code project trust",
1557
+ buttons: ["Continue"]
1558
+ };
1559
+ }
1560
+ }
1561
+ return null;
1562
+ }
1563
+ shouldResolveModalWithEnter(modal, buttonIndex) {
1564
+ if (!modal || buttonIndex !== 0) return false;
1565
+ const buttons = Array.isArray(modal.buttons) ? modal.buttons : [];
1566
+ if (buttons.length !== 1) return false;
1567
+ const buttonLabel = String(buttons[0] || "").trim();
1568
+ const modalText = `${modal.message || ""} ${buttonLabel}`.trim();
1569
+ return looksLikeConfirmOnlyLabel(buttonLabel) || /Quick safety check|project trust|trust (?:this project|the contents of this directory|the files in this folder)|Enter to confirm/i.test(modalText);
1570
+ }
1467
1571
  async waitForInteractivePrompt(maxWaitMs = 5e3) {
1468
1572
  const startedAt = Date.now();
1469
1573
  let loggedWait = false;
@@ -1513,9 +1617,10 @@ var init_provider_cli_adapter = __esm({
1513
1617
  }
1514
1618
  const tail = this.settledBuffer;
1515
1619
  const screenText = this.terminalScreen.getText() || "";
1516
- const modal = this.runParseApproval(tail);
1620
+ const startupModal = this.getStartupConfirmationModal(screenText);
1621
+ const modal = this.runParseApproval(tail) || startupModal;
1517
1622
  const rawScriptStatus = this.runDetectStatus(tail);
1518
- const scriptStatus = rawScriptStatus;
1623
+ const scriptStatus = startupModal ? "waiting_approval" : rawScriptStatus;
1519
1624
  const parsedTranscript = this.parseCurrentTranscript(
1520
1625
  this.committedMessages,
1521
1626
  this.responseBuffer,
@@ -1710,7 +1815,24 @@ var init_provider_cli_adapter = __esm({
1710
1815
  this.recordTrace("finish_response", {
1711
1816
  ...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer)
1712
1817
  });
1713
- this.commitCurrentTranscript();
1818
+ const commitResult = this.commitCurrentTranscript();
1819
+ if (this.shouldRetryFinishResponse(commitResult)) {
1820
+ this.finishRetryCount += 1;
1821
+ this.recordTrace("finish_response_retry", {
1822
+ retryCount: this.finishRetryCount,
1823
+ retryDelayMs: _ProviderCliAdapter.FINISH_RETRY_DELAY_MS,
1824
+ assistantContent: this.summarizeTraceText(commitResult.assistantContent, 220),
1825
+ ...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer)
1826
+ });
1827
+ if (this.finishRetryTimer) clearTimeout(this.finishRetryTimer);
1828
+ this.finishRetryTimer = setTimeout(() => {
1829
+ this.finishRetryTimer = null;
1830
+ if (this.isWaitingForResponse && this.currentStatus !== "waiting_approval") {
1831
+ this.finishResponse();
1832
+ }
1833
+ }, _ProviderCliAdapter.FINISH_RETRY_DELAY_MS);
1834
+ return;
1835
+ }
1714
1836
  if (this.responseTimeout) {
1715
1837
  clearTimeout(this.responseTimeout);
1716
1838
  this.responseTimeout = null;
@@ -1727,11 +1849,16 @@ var init_provider_cli_adapter = __esm({
1727
1849
  clearTimeout(this.submitRetryTimer);
1728
1850
  this.submitRetryTimer = null;
1729
1851
  }
1852
+ if (this.finishRetryTimer) {
1853
+ clearTimeout(this.finishRetryTimer);
1854
+ this.finishRetryTimer = null;
1855
+ }
1730
1856
  this.responseBuffer = "";
1731
1857
  this.isWaitingForResponse = false;
1732
1858
  this.responseSettleIgnoreUntil = 0;
1733
1859
  this.submitRetryUsed = false;
1734
1860
  this.submitRetryPromptSnippet = "";
1861
+ this.finishRetryCount = 0;
1735
1862
  this.currentTurnScope = null;
1736
1863
  this.activeModal = null;
1737
1864
  this.setStatus("idle", "response_finished");
@@ -1745,6 +1872,13 @@ var init_provider_cli_adapter = __esm({
1745
1872
  );
1746
1873
  if (parsed && Array.isArray(parsed.messages)) {
1747
1874
  this.committedMessages = this.normalizeParsedMessages(parsed.messages);
1875
+ const promptForTrim = this.currentTurnScope?.prompt || getLastUserPromptText(this.committedMessages);
1876
+ if (promptForTrim) {
1877
+ const lastAssistantForTrim = [...this.committedMessages].reverse().find((message) => message.role === "assistant");
1878
+ if (lastAssistantForTrim) {
1879
+ lastAssistantForTrim.content = trimPromptEchoPrefix(lastAssistantForTrim.content, promptForTrim);
1880
+ }
1881
+ }
1748
1882
  this.syncMessageViews();
1749
1883
  const lastAssistant = [...this.committedMessages].reverse().find((message) => message.role === "assistant");
1750
1884
  this.recordTrace("commit_transcript", {
@@ -1760,7 +1894,15 @@ var init_provider_cli_adapter = __esm({
1760
1894
  `[${this.cliType}] Commit without assistant turn: prompt=${JSON.stringify(this.currentTurnScope.prompt).slice(0, 140)} responseBuffer=${JSON.stringify(this.summarizeTraceText(this.responseBuffer, 220)).slice(0, 260)} providerDir=${this.providerResolutionMeta.providerDir || "-"} scriptDir=${this.providerResolutionMeta.scriptDir || "-"} scriptsPath=${this.providerResolutionMeta.scriptsPath || "-"}`
1761
1895
  );
1762
1896
  }
1897
+ return {
1898
+ hasAssistant: !!lastAssistant,
1899
+ assistantContent: lastAssistant?.content || ""
1900
+ };
1763
1901
  }
1902
+ return {
1903
+ hasAssistant: false,
1904
+ assistantContent: ""
1905
+ };
1764
1906
  }
1765
1907
  // ─── Script Execution ──────────────────────────
1766
1908
  runDetectStatus(text) {
@@ -1839,7 +1981,15 @@ var init_provider_cli_adapter = __esm({
1839
1981
  if (!this.cliScripts?.parseOutput) return null;
1840
1982
  try {
1841
1983
  const input = this.buildParseInput(baseMessages, partialResponse, scope);
1842
- return this.cliScripts.parseOutput(input);
1984
+ const parsed = this.cliScripts.parseOutput(input);
1985
+ const promptForTrim = scope?.prompt || getLastUserPromptText(baseMessages);
1986
+ if (parsed && Array.isArray(parsed.messages) && promptForTrim) {
1987
+ const lastAssistant = [...parsed.messages].reverse().find((message) => message?.role === "assistant" && typeof message.content === "string");
1988
+ if (lastAssistant) {
1989
+ lastAssistant.content = trimPromptEchoPrefix(lastAssistant.content, promptForTrim);
1990
+ }
1991
+ }
1992
+ return parsed;
1843
1993
  } catch (e) {
1844
1994
  LOG.warn("CLI", `[${this.cliType}] parseOutput error: ${e.message}`);
1845
1995
  return null;
@@ -1884,10 +2034,19 @@ ${data.message || ""}`.trim();
1884
2034
  if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
1885
2035
  if (this.isWaitingForResponse) return;
1886
2036
  await this.waitForInteractivePrompt();
2037
+ const blockingModal = this.activeModal || this.getStartupConfirmationModal(this.terminalScreen.getText() || "");
2038
+ if (blockingModal || this.currentStatus === "waiting_approval") {
2039
+ throw new Error(`${this.cliName} is awaiting confirmation before it can accept a prompt`);
2040
+ }
1887
2041
  this.committedMessages.push({ role: "user", content: text, timestamp: Date.now() });
1888
2042
  this.syncMessageViews();
1889
2043
  this.isWaitingForResponse = true;
1890
2044
  this.responseBuffer = "";
2045
+ this.finishRetryCount = 0;
2046
+ if (this.finishRetryTimer) {
2047
+ clearTimeout(this.finishRetryTimer);
2048
+ this.finishRetryTimer = null;
2049
+ }
1891
2050
  this.clearIdleFinishCandidate("send_message");
1892
2051
  this.currentTurnScope = {
1893
2052
  prompt: text,
@@ -2115,6 +2274,10 @@ ${data.message || ""}`.trim();
2115
2274
  clearTimeout(this.submitRetryTimer);
2116
2275
  this.submitRetryTimer = null;
2117
2276
  }
2277
+ if (this.finishRetryTimer) {
2278
+ clearTimeout(this.finishRetryTimer);
2279
+ this.finishRetryTimer = null;
2280
+ }
2118
2281
  if (this.responseTimeout) {
2119
2282
  clearTimeout(this.responseTimeout);
2120
2283
  this.responseTimeout = null;
@@ -2138,6 +2301,7 @@ ${data.message || ""}`.trim();
2138
2301
  this.ptyOutputFlushTimer = null;
2139
2302
  }
2140
2303
  this.ptyOutputBuffer = "";
2304
+ this.finishRetryCount = 0;
2141
2305
  if (this.ptyProcess) {
2142
2306
  this.ptyProcess.write("");
2143
2307
  setTimeout(() => {
@@ -2168,6 +2332,10 @@ ${data.message || ""}`.trim();
2168
2332
  clearTimeout(this.submitRetryTimer);
2169
2333
  this.submitRetryTimer = null;
2170
2334
  }
2335
+ if (this.finishRetryTimer) {
2336
+ clearTimeout(this.finishRetryTimer);
2337
+ this.finishRetryTimer = null;
2338
+ }
2171
2339
  if (this.responseTimeout) {
2172
2340
  clearTimeout(this.responseTimeout);
2173
2341
  this.responseTimeout = null;
@@ -2191,6 +2359,7 @@ ${data.message || ""}`.trim();
2191
2359
  this.ptyOutputFlushTimer = null;
2192
2360
  }
2193
2361
  this.ptyOutputBuffer = "";
2362
+ this.finishRetryCount = 0;
2194
2363
  if (this.ptyProcess) {
2195
2364
  try {
2196
2365
  if (typeof this.ptyProcess.detach === "function") {
@@ -2227,6 +2396,11 @@ ${data.message || ""}`.trim();
2227
2396
  this.ptyOutputFlushTimer = null;
2228
2397
  }
2229
2398
  this.ptyOutputBuffer = "";
2399
+ if (this.finishRetryTimer) {
2400
+ clearTimeout(this.finishRetryTimer);
2401
+ this.finishRetryTimer = null;
2402
+ }
2403
+ this.finishRetryCount = 0;
2230
2404
  this.terminalScreen.reset();
2231
2405
  this.ptyProcess?.clearBuffer?.();
2232
2406
  this.onStatusChange?.();
@@ -2246,10 +2420,11 @@ ${data.message || ""}`.trim();
2246
2420
  }
2247
2421
  resolveModal(buttonIndex) {
2248
2422
  if (!this.ptyProcess || this.currentStatus !== "waiting_approval" && !this.activeModal) return;
2423
+ const modal = this.activeModal;
2249
2424
  this.clearIdleFinishCandidate("resolve_modal");
2250
2425
  this.recordTrace("resolve_modal", {
2251
2426
  buttonIndex,
2252
- activeModal: this.activeModal
2427
+ activeModal: modal
2253
2428
  });
2254
2429
  this.activeModal = null;
2255
2430
  this.lastApprovalResolvedAt = Date.now();
@@ -2260,7 +2435,9 @@ ${data.message || ""}`.trim();
2260
2435
  }
2261
2436
  this.setStatus("generating", "approval_resolved");
2262
2437
  this.onStatusChange?.();
2263
- if (buttonIndex in this.approvalKeys) {
2438
+ if (this.shouldResolveModalWithEnter(modal, buttonIndex)) {
2439
+ this.ptyProcess.write("\r");
2440
+ } else if (buttonIndex in this.approvalKeys) {
2264
2441
  this.ptyProcess.write(this.approvalKeys[buttonIndex]);
2265
2442
  } else {
2266
2443
  const DOWN = "\x1B[B";
@@ -11598,7 +11775,10 @@ function appendUpgradeLog(message) {
11598
11775
  }
11599
11776
  }
11600
11777
  function getNpmExecutable() {
11601
- return process.platform === "win32" ? "npm.cmd" : "npm";
11778
+ return "npm";
11779
+ }
11780
+ function getNpmExecOptions() {
11781
+ return { shell: process.platform === "win32" };
11602
11782
  }
11603
11783
  function killPid(pid) {
11604
11784
  try {
@@ -11660,9 +11840,10 @@ function removeDaemonPidFile() {
11660
11840
  }
11661
11841
  }
11662
11842
  function cleanupStaleGlobalInstallDirs(pkgName) {
11663
- const npmRoot = execFileSync(getNpmExecutable(), ["root", "-g"], { encoding: "utf8" }).trim();
11843
+ const npmExecOpts = getNpmExecOptions();
11844
+ const npmRoot = execFileSync(getNpmExecutable(), ["root", "-g"], { encoding: "utf8", ...npmExecOpts }).trim();
11664
11845
  if (!npmRoot) return;
11665
- const npmPrefix = execFileSync(getNpmExecutable(), ["prefix", "-g"], { encoding: "utf8" }).trim();
11846
+ const npmPrefix = execFileSync(getNpmExecutable(), ["prefix", "-g"], { encoding: "utf8", ...npmExecOpts }).trim();
11666
11847
  const binDir = process.platform === "win32" ? npmPrefix : path13.join(npmPrefix, "bin");
11667
11848
  const packageBaseName = pkgName.startsWith("@") ? pkgName.split("/")[1] : pkgName;
11668
11849
  const binNames = /* @__PURE__ */ new Set([packageBaseName]);
@@ -11723,7 +11904,8 @@ async function runDaemonUpgradeHelper(payload) {
11723
11904
  {
11724
11905
  encoding: "utf8",
11725
11906
  stdio: "pipe",
11726
- maxBuffer: 20 * 1024 * 1024
11907
+ maxBuffer: 20 * 1024 * 1024,
11908
+ ...getNpmExecOptions()
11727
11909
  }
11728
11910
  );
11729
11911
  if (installOutput.trim()) {
@@ -14945,6 +15127,53 @@ async function runCliExerciseInternal(ctx, body) {
14945
15127
  let lastModalKey = "";
14946
15128
  let idleSince = 0;
14947
15129
  let sawBusy = false;
15130
+ const noteStatus = (status) => {
15131
+ if (status !== lastStatus) {
15132
+ statusesSeen.push(status);
15133
+ lastStatus = status;
15134
+ }
15135
+ };
15136
+ const resolveActiveModalIfNeeded = (status, modal) => {
15137
+ if (!autoResolveApprovals || status !== "waiting_approval" || !modal || !Array.isArray(modal.buttons) || modal.buttons.length === 0) {
15138
+ return false;
15139
+ }
15140
+ const clampedIndex = Math.max(0, Math.min(Number(approvalButtonIndex) || 0, modal.buttons.length - 1));
15141
+ const modalKey = JSON.stringify({
15142
+ message: modal.message || "",
15143
+ buttons: modal.buttons,
15144
+ index: clampedIndex
15145
+ });
15146
+ if (modalKey === lastModalKey || typeof bundle?.adapter?.resolveModal !== "function") {
15147
+ return false;
15148
+ }
15149
+ lastModalKey = modalKey;
15150
+ approvalsResolved.push({
15151
+ at: Date.now(),
15152
+ buttonIndex: clampedIndex,
15153
+ label: modal.buttons[clampedIndex] || null
15154
+ });
15155
+ bundle.adapter.resolveModal(clampedIndex);
15156
+ return true;
15157
+ };
15158
+ const preflightStartedAt = Date.now();
15159
+ while (Date.now() - preflightStartedAt < Math.max(1e3, readyTimeoutMs)) {
15160
+ bundle = getCliTargetBundle(ctx, type, bundle.target.instanceId);
15161
+ if (!bundle) {
15162
+ throw new Error("CLI instance disappeared before exercise send");
15163
+ }
15164
+ const debug = typeof bundle.adapter.getDebugState === "function" ? bundle.adapter.getDebugState() : null;
15165
+ const trace = typeof bundle.adapter.getTraceState === "function" ? bundle.adapter.getTraceState(traceLimit) : null;
15166
+ const status = String(debug?.status || bundle.target.status || "unknown");
15167
+ const modal = debug?.activeModal || trace?.activeModal || null;
15168
+ noteStatus(status);
15169
+ if (resolveActiveModalIfNeeded(status, modal)) {
15170
+ await sleep(150);
15171
+ continue;
15172
+ }
15173
+ const startupParseGate = !!debug?.startupParseGate;
15174
+ if (status === "idle" && !startupParseGate) break;
15175
+ await sleep(150);
15176
+ }
14948
15177
  ctx.instanceManager.sendEvent(bundle.target.instanceId, "send_message", { text });
14949
15178
  while (Date.now() - startAt < Math.max(1e3, timeoutMs)) {
14950
15179
  await sleep(150);
@@ -14959,32 +15188,14 @@ async function runCliExerciseInternal(ctx, body) {
14959
15188
  const sawSendMessage = traceEntries.some((entry) => entry?.type === "send_message");
14960
15189
  const sawSubmitWrite = traceEntries.some((entry) => entry?.type === "submit_write");
14961
15190
  const hasTurnStarted = sawSendMessage || sawSubmitWrite || !!debug?.currentTurnScope;
14962
- if (status !== lastStatus) {
14963
- statusesSeen.push(status);
14964
- lastStatus = status;
14965
- }
15191
+ noteStatus(status);
14966
15192
  if (status === "generating" || status === "waiting_approval") {
14967
15193
  sawBusy = true;
14968
15194
  idleSince = 0;
14969
15195
  }
14970
15196
  const modal = debug?.activeModal || trace?.activeModal || null;
14971
- if (autoResolveApprovals && status === "waiting_approval" && modal && Array.isArray(modal.buttons) && modal.buttons.length > 0) {
14972
- const clampedIndex = Math.max(0, Math.min(Number(approvalButtonIndex) || 0, modal.buttons.length - 1));
14973
- const modalKey = JSON.stringify({
14974
- message: modal.message || "",
14975
- buttons: modal.buttons,
14976
- index: clampedIndex
14977
- });
14978
- if (modalKey !== lastModalKey && typeof bundle.adapter.resolveModal === "function") {
14979
- lastModalKey = modalKey;
14980
- approvalsResolved.push({
14981
- at: Date.now(),
14982
- buttonIndex: clampedIndex,
14983
- label: modal.buttons[clampedIndex] || null
14984
- });
14985
- bundle.adapter.resolveModal(clampedIndex);
14986
- continue;
14987
- }
15197
+ if (resolveActiveModalIfNeeded(status, modal)) {
15198
+ continue;
14988
15199
  }
14989
15200
  const traceCount = Number(trace?.entryCount || 0);
14990
15201
  const hasProgress = hasTurnStarted && (traceCount > preTraceCount || statusesSeen.length > 1 || approvalsResolved.length > 0);
@@ -16639,6 +16850,13 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
16639
16850
  lines.push("19. Literal string checks are allowed only for stable proper nouns or exact product chrome that cannot be expressed safely as a broader pattern. Everything else should generalize.");
16640
16851
  lines.push("20. When a bug comes from noisy PTY text, first normalize and classify the line family; do NOT just append another special-case substring to the parser.");
16641
16852
  lines.push("");
16853
+ if (verification?.focusAreas?.length) {
16854
+ lines.push("## Provider-Specific Focus Areas");
16855
+ for (const area of verification.focusAreas) {
16856
+ lines.push(`- ${area}`);
16857
+ }
16858
+ lines.push("");
16859
+ }
16642
16860
  lines.push("## Task");
16643
16861
  lines.push(`Edit files in \`${providerDir}\` to implement: **${functions.join(", ")}**`);
16644
16862
  lines.push("");
@@ -19329,6 +19547,7 @@ export {
19329
19547
  setLogLevel,
19330
19548
  setupIdeInstance,
19331
19549
  shutdownDaemonComponents,
19550
+ spawnDetachedDaemonUpgradeHelper,
19332
19551
  startDaemonDevSupport,
19333
19552
  updateConfig,
19334
19553
  upsertSavedProviderSession