@adhdev/daemon-core 0.8.14 → 0.8.16

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
@@ -700,17 +700,35 @@ var init_terminal_screen = __esm({
700
700
  }
701
701
  });
702
702
 
703
+ // src/cli-adapters/spawn-env.ts
704
+ import {
705
+ sanitizeSpawnEnv,
706
+ applyTerminalColorEnv,
707
+ ensureNodePtySpawnHelperPermissions
708
+ } from "@adhdev/session-host-core";
709
+ var init_spawn_env = __esm({
710
+ "src/cli-adapters/spawn-env.ts"() {
711
+ "use strict";
712
+ }
713
+ });
714
+
703
715
  // src/cli-adapters/pty-transport.ts
704
716
  import * as os7 from "os";
705
- var pty, NodePtyRuntimeTransport, NodePtyTransportFactory;
717
+ function loadNodePty() {
718
+ if (cachedPty !== void 0) return cachedPty;
719
+ try {
720
+ cachedPty = __require("node-pty");
721
+ ensureNodePtySpawnHelperPermissions();
722
+ } catch {
723
+ cachedPty = null;
724
+ }
725
+ return cachedPty;
726
+ }
727
+ var cachedPty, NodePtyRuntimeTransport, NodePtyTransportFactory;
706
728
  var init_pty_transport = __esm({
707
729
  "src/cli-adapters/pty-transport.ts"() {
708
730
  "use strict";
709
- try {
710
- pty = __require("node-pty");
711
- } catch {
712
- pty = null;
713
- }
731
+ init_spawn_env();
714
732
  NodePtyRuntimeTransport = class {
715
733
  constructor(handle) {
716
734
  this.handle = handle;
@@ -741,6 +759,7 @@ var init_pty_transport = __esm({
741
759
  };
742
760
  NodePtyTransportFactory = class {
743
761
  spawn(command, args, options) {
762
+ const pty = loadNodePty();
744
763
  if (!pty) throw new Error("node-pty is not installed");
745
764
  let cwd = options.cwd;
746
765
  if (cwd) {
@@ -765,18 +784,6 @@ var init_pty_transport = __esm({
765
784
  }
766
785
  });
767
786
 
768
- // src/cli-adapters/spawn-env.ts
769
- import {
770
- sanitizeSpawnEnv,
771
- applyTerminalColorEnv,
772
- ensureNodePtySpawnHelperPermissions
773
- } from "@adhdev/session-host-core";
774
- var init_spawn_env = __esm({
775
- "src/cli-adapters/spawn-env.ts"() {
776
- "use strict";
777
- }
778
- });
779
-
780
787
  // src/cli-adapters/provider-cli-adapter.ts
781
788
  var provider_cli_adapter_exports = {};
782
789
  __export(provider_cli_adapter_exports, {
@@ -899,6 +906,40 @@ function normalizeScreenSnapshot(text) {
899
906
  function normalizeComparableMessageContent(text) {
900
907
  return String(text || "").replace(/\s+/g, " ").trim();
901
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
+ }
902
943
  function parsePatternEntry(x) {
903
944
  if (x instanceof RegExp) return x;
904
945
  if (x && typeof x === "object" && typeof x.source === "string") {
@@ -923,7 +964,7 @@ function normalizeCliProviderForRuntime(raw) {
923
964
  }
924
965
  };
925
966
  }
926
- var pty2, buildCliSpawnEnv, ProviderCliAdapter;
967
+ var buildCliSpawnEnv, ProviderCliAdapter;
927
968
  var init_provider_cli_adapter = __esm({
928
969
  "src/cli-adapters/provider-cli-adapter.ts"() {
929
970
  "use strict";
@@ -931,12 +972,6 @@ var init_provider_cli_adapter = __esm({
931
972
  init_terminal_screen();
932
973
  init_pty_transport();
933
974
  init_spawn_env();
934
- try {
935
- pty2 = __require("node-pty");
936
- ensureNodePtySpawnHelperPermissions((msg) => LOG.info("CLI", msg));
937
- } catch {
938
- LOG.error("CLI", "[ProviderCliAdapter] node-pty not found. Terminal features disabled.");
939
- }
940
975
  buildCliSpawnEnv = sanitizeSpawnEnv;
941
976
  ProviderCliAdapter = class _ProviderCliAdapter {
942
977
  constructor(provider, workingDir, extraArgs = [], transportFactory = new NodePtyTransportFactory()) {
@@ -1038,6 +1073,8 @@ var init_provider_cli_adapter = __esm({
1038
1073
  submitRetryUsed = false;
1039
1074
  submitRetryPromptSnippet = "";
1040
1075
  idleFinishCandidate = null;
1076
+ finishRetryTimer = null;
1077
+ finishRetryCount = 0;
1041
1078
  // Resize redraw suppression
1042
1079
  resizeSuppressUntil = 0;
1043
1080
  // Debug: status transition history
@@ -1059,6 +1096,8 @@ var init_provider_cli_adapter = __esm({
1059
1096
  static MAX_TRACE_ENTRIES = 250;
1060
1097
  providerResolutionMeta;
1061
1098
  static IDLE_FINISH_CONFIRM_MS = 900;
1099
+ static FINISH_RETRY_DELAY_MS = 300;
1100
+ static MAX_FINISH_RETRIES = 2;
1062
1101
  syncMessageViews() {
1063
1102
  this.messages = [...this.committedMessages];
1064
1103
  this.structuredMessages = [...this.committedMessages];
@@ -1118,7 +1157,8 @@ var init_provider_cli_adapter = __esm({
1118
1157
  recentBuffer: buffer.slice(-1e3) || this.recentOutputBuffer,
1119
1158
  screenText: this.terminalScreen.getText(),
1120
1159
  messages: [...baseMessages],
1121
- partialResponse
1160
+ partialResponse,
1161
+ promptText: scope?.prompt || ""
1122
1162
  };
1123
1163
  }
1124
1164
  setStatus(status, trigger) {
@@ -1361,6 +1401,11 @@ var init_provider_cli_adapter = __esm({
1361
1401
  this.terminalScreen.reset(24, 80);
1362
1402
  this.pendingTerminalQueryTail = "";
1363
1403
  this.currentTurnScope = null;
1404
+ this.finishRetryCount = 0;
1405
+ if (this.finishRetryTimer) {
1406
+ clearTimeout(this.finishRetryTimer);
1407
+ this.finishRetryTimer = null;
1408
+ }
1364
1409
  this.ready = false;
1365
1410
  await this.ptyProcess.ready;
1366
1411
  this.recordTrace("ready", {
@@ -1407,11 +1452,12 @@ var init_provider_cli_adapter = __esm({
1407
1452
  if (this.startupParseGate) {
1408
1453
  this.startupBuffer += cleanData;
1409
1454
  const elapsed = Date.now() - this.spawnAt;
1410
- const scriptStatus = this.runDetectStatus(this.startupBuffer);
1411
1455
  const screenText = this.terminalScreen.getText() || "";
1456
+ const startupModal = this.getStartupConfirmationModal(screenText);
1457
+ const scriptStatus = startupModal ? "waiting_approval" : this.runDetectStatus(this.startupBuffer);
1412
1458
  const hasInteractivePrompt = this.looksLikeVisibleIdlePrompt(screenText);
1413
1459
  const startupStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
1414
- 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;
1415
1461
  if (isReady) {
1416
1462
  this.startupParseGate = false;
1417
1463
  this.ready = true;
@@ -1443,7 +1489,9 @@ var init_provider_cli_adapter = __esm({
1443
1489
  this.approvalExitTimeout = setTimeout(() => {
1444
1490
  if (this.currentStatus !== "waiting_approval") return;
1445
1491
  const tail = this.recentOutputBuffer;
1446
- const modal = this.runParseApproval(tail);
1492
+ const screenText = this.terminalScreen.getText() || "";
1493
+ const startupModal = this.getStartupConfirmationModal(screenText);
1494
+ const modal = this.runParseApproval(tail) || startupModal;
1447
1495
  const stillWaiting = this.runDetectStatus(tail) === "waiting_approval" || !!modal;
1448
1496
  if (stillWaiting) {
1449
1497
  this.activeModal = modal || this.activeModal || { message: "Approval required", buttons: ["Allow", "Deny"] };
@@ -1463,6 +1511,63 @@ var init_provider_cli_adapter = __esm({
1463
1511
  if (!text.trim()) return false;
1464
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);
1465
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
+ }
1466
1571
  async waitForInteractivePrompt(maxWaitMs = 5e3) {
1467
1572
  const startedAt = Date.now();
1468
1573
  let loggedWait = false;
@@ -1512,9 +1617,10 @@ var init_provider_cli_adapter = __esm({
1512
1617
  }
1513
1618
  const tail = this.settledBuffer;
1514
1619
  const screenText = this.terminalScreen.getText() || "";
1515
- const modal = this.runParseApproval(tail);
1620
+ const startupModal = this.getStartupConfirmationModal(screenText);
1621
+ const modal = this.runParseApproval(tail) || startupModal;
1516
1622
  const rawScriptStatus = this.runDetectStatus(tail);
1517
- const scriptStatus = rawScriptStatus;
1623
+ const scriptStatus = startupModal ? "waiting_approval" : rawScriptStatus;
1518
1624
  const parsedTranscript = this.parseCurrentTranscript(
1519
1625
  this.committedMessages,
1520
1626
  this.responseBuffer,
@@ -1709,7 +1815,24 @@ var init_provider_cli_adapter = __esm({
1709
1815
  this.recordTrace("finish_response", {
1710
1816
  ...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer)
1711
1817
  });
1712
- 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
+ }
1713
1836
  if (this.responseTimeout) {
1714
1837
  clearTimeout(this.responseTimeout);
1715
1838
  this.responseTimeout = null;
@@ -1726,11 +1849,16 @@ var init_provider_cli_adapter = __esm({
1726
1849
  clearTimeout(this.submitRetryTimer);
1727
1850
  this.submitRetryTimer = null;
1728
1851
  }
1852
+ if (this.finishRetryTimer) {
1853
+ clearTimeout(this.finishRetryTimer);
1854
+ this.finishRetryTimer = null;
1855
+ }
1729
1856
  this.responseBuffer = "";
1730
1857
  this.isWaitingForResponse = false;
1731
1858
  this.responseSettleIgnoreUntil = 0;
1732
1859
  this.submitRetryUsed = false;
1733
1860
  this.submitRetryPromptSnippet = "";
1861
+ this.finishRetryCount = 0;
1734
1862
  this.currentTurnScope = null;
1735
1863
  this.activeModal = null;
1736
1864
  this.setStatus("idle", "response_finished");
@@ -1744,6 +1872,13 @@ var init_provider_cli_adapter = __esm({
1744
1872
  );
1745
1873
  if (parsed && Array.isArray(parsed.messages)) {
1746
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
+ }
1747
1882
  this.syncMessageViews();
1748
1883
  const lastAssistant = [...this.committedMessages].reverse().find((message) => message.role === "assistant");
1749
1884
  this.recordTrace("commit_transcript", {
@@ -1759,7 +1894,15 @@ var init_provider_cli_adapter = __esm({
1759
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 || "-"}`
1760
1895
  );
1761
1896
  }
1897
+ return {
1898
+ hasAssistant: !!lastAssistant,
1899
+ assistantContent: lastAssistant?.content || ""
1900
+ };
1762
1901
  }
1902
+ return {
1903
+ hasAssistant: false,
1904
+ assistantContent: ""
1905
+ };
1763
1906
  }
1764
1907
  // ─── Script Execution ──────────────────────────
1765
1908
  runDetectStatus(text) {
@@ -1838,7 +1981,15 @@ var init_provider_cli_adapter = __esm({
1838
1981
  if (!this.cliScripts?.parseOutput) return null;
1839
1982
  try {
1840
1983
  const input = this.buildParseInput(baseMessages, partialResponse, scope);
1841
- 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;
1842
1993
  } catch (e) {
1843
1994
  LOG.warn("CLI", `[${this.cliType}] parseOutput error: ${e.message}`);
1844
1995
  return null;
@@ -1883,10 +2034,19 @@ ${data.message || ""}`.trim();
1883
2034
  if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
1884
2035
  if (this.isWaitingForResponse) return;
1885
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
+ }
1886
2041
  this.committedMessages.push({ role: "user", content: text, timestamp: Date.now() });
1887
2042
  this.syncMessageViews();
1888
2043
  this.isWaitingForResponse = true;
1889
2044
  this.responseBuffer = "";
2045
+ this.finishRetryCount = 0;
2046
+ if (this.finishRetryTimer) {
2047
+ clearTimeout(this.finishRetryTimer);
2048
+ this.finishRetryTimer = null;
2049
+ }
1890
2050
  this.clearIdleFinishCandidate("send_message");
1891
2051
  this.currentTurnScope = {
1892
2052
  prompt: text,
@@ -2114,6 +2274,10 @@ ${data.message || ""}`.trim();
2114
2274
  clearTimeout(this.submitRetryTimer);
2115
2275
  this.submitRetryTimer = null;
2116
2276
  }
2277
+ if (this.finishRetryTimer) {
2278
+ clearTimeout(this.finishRetryTimer);
2279
+ this.finishRetryTimer = null;
2280
+ }
2117
2281
  if (this.responseTimeout) {
2118
2282
  clearTimeout(this.responseTimeout);
2119
2283
  this.responseTimeout = null;
@@ -2137,6 +2301,7 @@ ${data.message || ""}`.trim();
2137
2301
  this.ptyOutputFlushTimer = null;
2138
2302
  }
2139
2303
  this.ptyOutputBuffer = "";
2304
+ this.finishRetryCount = 0;
2140
2305
  if (this.ptyProcess) {
2141
2306
  this.ptyProcess.write("");
2142
2307
  setTimeout(() => {
@@ -2167,6 +2332,10 @@ ${data.message || ""}`.trim();
2167
2332
  clearTimeout(this.submitRetryTimer);
2168
2333
  this.submitRetryTimer = null;
2169
2334
  }
2335
+ if (this.finishRetryTimer) {
2336
+ clearTimeout(this.finishRetryTimer);
2337
+ this.finishRetryTimer = null;
2338
+ }
2170
2339
  if (this.responseTimeout) {
2171
2340
  clearTimeout(this.responseTimeout);
2172
2341
  this.responseTimeout = null;
@@ -2190,6 +2359,7 @@ ${data.message || ""}`.trim();
2190
2359
  this.ptyOutputFlushTimer = null;
2191
2360
  }
2192
2361
  this.ptyOutputBuffer = "";
2362
+ this.finishRetryCount = 0;
2193
2363
  if (this.ptyProcess) {
2194
2364
  try {
2195
2365
  if (typeof this.ptyProcess.detach === "function") {
@@ -2226,6 +2396,11 @@ ${data.message || ""}`.trim();
2226
2396
  this.ptyOutputFlushTimer = null;
2227
2397
  }
2228
2398
  this.ptyOutputBuffer = "";
2399
+ if (this.finishRetryTimer) {
2400
+ clearTimeout(this.finishRetryTimer);
2401
+ this.finishRetryTimer = null;
2402
+ }
2403
+ this.finishRetryCount = 0;
2229
2404
  this.terminalScreen.reset();
2230
2405
  this.ptyProcess?.clearBuffer?.();
2231
2406
  this.onStatusChange?.();
@@ -2245,10 +2420,11 @@ ${data.message || ""}`.trim();
2245
2420
  }
2246
2421
  resolveModal(buttonIndex) {
2247
2422
  if (!this.ptyProcess || this.currentStatus !== "waiting_approval" && !this.activeModal) return;
2423
+ const modal = this.activeModal;
2248
2424
  this.clearIdleFinishCandidate("resolve_modal");
2249
2425
  this.recordTrace("resolve_modal", {
2250
2426
  buttonIndex,
2251
- activeModal: this.activeModal
2427
+ activeModal: modal
2252
2428
  });
2253
2429
  this.activeModal = null;
2254
2430
  this.lastApprovalResolvedAt = Date.now();
@@ -2259,7 +2435,9 @@ ${data.message || ""}`.trim();
2259
2435
  }
2260
2436
  this.setStatus("generating", "approval_resolved");
2261
2437
  this.onStatusChange?.();
2262
- if (buttonIndex in this.approvalKeys) {
2438
+ if (this.shouldResolveModalWithEnter(modal, buttonIndex)) {
2439
+ this.ptyProcess.write("\r");
2440
+ } else if (buttonIndex in this.approvalKeys) {
2263
2441
  this.ptyProcess.write(this.approvalKeys[buttonIndex]);
2264
2442
  } else {
2265
2443
  const DOWN = "\x1B[B";
@@ -11597,7 +11775,10 @@ function appendUpgradeLog(message) {
11597
11775
  }
11598
11776
  }
11599
11777
  function getNpmExecutable() {
11600
- return process.platform === "win32" ? "npm.cmd" : "npm";
11778
+ return "npm";
11779
+ }
11780
+ function getNpmExecOptions() {
11781
+ return { shell: process.platform === "win32" };
11601
11782
  }
11602
11783
  function killPid(pid) {
11603
11784
  try {
@@ -11659,9 +11840,10 @@ function removeDaemonPidFile() {
11659
11840
  }
11660
11841
  }
11661
11842
  function cleanupStaleGlobalInstallDirs(pkgName) {
11662
- const npmRoot = execFileSync(getNpmExecutable(), ["root", "-g"], { encoding: "utf8" }).trim();
11843
+ const npmExecOpts = getNpmExecOptions();
11844
+ const npmRoot = execFileSync(getNpmExecutable(), ["root", "-g"], { encoding: "utf8", ...npmExecOpts }).trim();
11663
11845
  if (!npmRoot) return;
11664
- const npmPrefix = execFileSync(getNpmExecutable(), ["prefix", "-g"], { encoding: "utf8" }).trim();
11846
+ const npmPrefix = execFileSync(getNpmExecutable(), ["prefix", "-g"], { encoding: "utf8", ...npmExecOpts }).trim();
11665
11847
  const binDir = process.platform === "win32" ? npmPrefix : path13.join(npmPrefix, "bin");
11666
11848
  const packageBaseName = pkgName.startsWith("@") ? pkgName.split("/")[1] : pkgName;
11667
11849
  const binNames = /* @__PURE__ */ new Set([packageBaseName]);
@@ -11722,7 +11904,8 @@ async function runDaemonUpgradeHelper(payload) {
11722
11904
  {
11723
11905
  encoding: "utf8",
11724
11906
  stdio: "pipe",
11725
- maxBuffer: 20 * 1024 * 1024
11907
+ maxBuffer: 20 * 1024 * 1024,
11908
+ ...getNpmExecOptions()
11726
11909
  }
11727
11910
  );
11728
11911
  if (installOutput.trim()) {
@@ -14944,6 +15127,53 @@ async function runCliExerciseInternal(ctx, body) {
14944
15127
  let lastModalKey = "";
14945
15128
  let idleSince = 0;
14946
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
+ }
14947
15177
  ctx.instanceManager.sendEvent(bundle.target.instanceId, "send_message", { text });
14948
15178
  while (Date.now() - startAt < Math.max(1e3, timeoutMs)) {
14949
15179
  await sleep(150);
@@ -14958,32 +15188,14 @@ async function runCliExerciseInternal(ctx, body) {
14958
15188
  const sawSendMessage = traceEntries.some((entry) => entry?.type === "send_message");
14959
15189
  const sawSubmitWrite = traceEntries.some((entry) => entry?.type === "submit_write");
14960
15190
  const hasTurnStarted = sawSendMessage || sawSubmitWrite || !!debug?.currentTurnScope;
14961
- if (status !== lastStatus) {
14962
- statusesSeen.push(status);
14963
- lastStatus = status;
14964
- }
15191
+ noteStatus(status);
14965
15192
  if (status === "generating" || status === "waiting_approval") {
14966
15193
  sawBusy = true;
14967
15194
  idleSince = 0;
14968
15195
  }
14969
15196
  const modal = debug?.activeModal || trace?.activeModal || null;
14970
- if (autoResolveApprovals && status === "waiting_approval" && modal && Array.isArray(modal.buttons) && modal.buttons.length > 0) {
14971
- const clampedIndex = Math.max(0, Math.min(Number(approvalButtonIndex) || 0, modal.buttons.length - 1));
14972
- const modalKey = JSON.stringify({
14973
- message: modal.message || "",
14974
- buttons: modal.buttons,
14975
- index: clampedIndex
14976
- });
14977
- if (modalKey !== lastModalKey && typeof bundle.adapter.resolveModal === "function") {
14978
- lastModalKey = modalKey;
14979
- approvalsResolved.push({
14980
- at: Date.now(),
14981
- buttonIndex: clampedIndex,
14982
- label: modal.buttons[clampedIndex] || null
14983
- });
14984
- bundle.adapter.resolveModal(clampedIndex);
14985
- continue;
14986
- }
15197
+ if (resolveActiveModalIfNeeded(status, modal)) {
15198
+ continue;
14987
15199
  }
14988
15200
  const traceCount = Number(trace?.entryCount || 0);
14989
15201
  const hasProgress = hasTurnStarted && (traceCount > preTraceCount || statusesSeen.length > 1 || approvalsResolved.length > 0);
@@ -15979,10 +16191,10 @@ async function handleAutoImplement(ctx, type, req, res) {
15979
16191
  let isPty = false;
15980
16192
  const { spawn: spawnFn } = await import("child_process");
15981
16193
  try {
15982
- const pty3 = __require("node-pty");
16194
+ const pty = __require("node-pty");
15983
16195
  ctx.log(`Auto-implement spawn (PTY): ${shellCmd}`);
15984
16196
  const isWin2 = os17.platform() === "win32";
15985
- child = pty3.spawn(isWin2 ? "cmd.exe" : process.env.SHELL || "/bin/zsh", [isWin2 ? "/c" : "-c", shellCmd], {
16197
+ child = pty.spawn(isWin2 ? "cmd.exe" : process.env.SHELL || "/bin/zsh", [isWin2 ? "/c" : "-c", shellCmd], {
15986
16198
  name: "xterm-256color",
15987
16199
  cols: 120,
15988
16200
  rows: 40,
@@ -16638,6 +16850,13 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
16638
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.");
16639
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.");
16640
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
+ }
16641
16860
  lines.push("## Task");
16642
16861
  lines.push(`Edit files in \`${providerDir}\` to implement: **${functions.join(", ")}**`);
16643
16862
  lines.push("");