@adhdev/daemon-core 0.6.55 → 0.6.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -592,240 +592,67 @@ var init_logger = __esm({
592
592
  });
593
593
 
594
594
  // src/cli-adapters/terminal-screen.ts
595
- function clamp(value, min, max) {
596
- return Math.max(min, Math.min(max, value));
595
+ function loadTerminalCtor() {
596
+ if (!TerminalCtor) {
597
+ const mod = require("@xterm/xterm");
598
+ TerminalCtor = mod.Terminal || mod.default?.Terminal || mod.default;
599
+ if (!TerminalCtor) {
600
+ throw new Error("@xterm/xterm Terminal export not found");
601
+ }
602
+ }
603
+ return TerminalCtor;
597
604
  }
598
- var TerminalScreen;
605
+ var TerminalCtor, TerminalScreen;
599
606
  var init_terminal_screen = __esm({
600
607
  "src/cli-adapters/terminal-screen.ts"() {
601
608
  "use strict";
609
+ TerminalCtor = null;
602
610
  TerminalScreen = class {
603
611
  rows;
604
612
  cols;
605
- cursorRow = 0;
606
- cursorCol = 0;
607
- savedRow = 0;
608
- savedCol = 0;
609
- lines;
613
+ terminal;
610
614
  constructor(rows = 40, cols = 120) {
611
- this.rows = rows;
612
- this.cols = cols;
613
- this.lines = this.makeLines(rows, cols);
615
+ this.rows = Math.max(1, rows | 0);
616
+ this.cols = Math.max(1, cols | 0);
617
+ this.terminal = this.createTerminal();
614
618
  }
615
619
  reset(rows = this.rows, cols = this.cols) {
616
- this.rows = rows;
617
- this.cols = cols;
618
- this.cursorRow = 0;
619
- this.cursorCol = 0;
620
- this.savedRow = 0;
621
- this.savedCol = 0;
622
- this.lines = this.makeLines(rows, cols);
620
+ this.rows = Math.max(1, rows | 0);
621
+ this.cols = Math.max(1, cols | 0);
622
+ this.terminal.dispose();
623
+ this.terminal = this.createTerminal();
623
624
  }
624
625
  resize(rows, cols) {
625
- const nextRows = Math.max(1, rows | 0);
626
- const nextCols = Math.max(1, cols | 0);
627
- const next = this.makeLines(nextRows, nextCols);
628
- const copyRows = Math.min(this.rows, nextRows);
629
- const copyCols = Math.min(this.cols, nextCols);
630
- for (let r = 0; r < copyRows; r++) {
631
- for (let c = 0; c < copyCols; c++) {
632
- next[r][c] = this.lines[r][c];
633
- }
634
- }
635
- this.rows = nextRows;
636
- this.cols = nextCols;
637
- this.lines = next;
638
- this.cursorRow = clamp(this.cursorRow, 0, this.rows - 1);
639
- this.cursorCol = clamp(this.cursorCol, 0, this.cols - 1);
640
- this.savedRow = clamp(this.savedRow, 0, this.rows - 1);
641
- this.savedCol = clamp(this.savedCol, 0, this.cols - 1);
626
+ this.rows = Math.max(1, rows | 0);
627
+ this.cols = Math.max(1, cols | 0);
628
+ this.terminal.resize(this.cols, this.rows);
642
629
  }
643
630
  write(data) {
644
- let i = 0;
645
- while (i < data.length) {
646
- const ch = data[i];
647
- if (ch === "\x1B") {
648
- const consumed = this.consumeEscape(data, i);
649
- i = consumed > i ? consumed : i + 1;
650
- continue;
651
- }
652
- if (ch === "\r") {
653
- this.cursorCol = 0;
654
- i++;
655
- continue;
656
- }
657
- if (ch === "\n") {
658
- this.newLine();
659
- i++;
660
- continue;
661
- }
662
- if (ch === "\b") {
663
- this.cursorCol = Math.max(0, this.cursorCol - 1);
664
- i++;
665
- continue;
666
- }
667
- if (ch === " ") {
668
- const nextStop = Math.min(this.cols - 1, this.cursorCol + (8 - (this.cursorCol % 8 || 8)));
669
- while (this.cursorCol < nextStop) this.putChar(" ");
670
- i++;
671
- continue;
672
- }
673
- if (ch >= " " && ch !== "\x7F") {
674
- this.putChar(ch);
675
- }
676
- i++;
677
- }
631
+ if (!data) return;
632
+ this.terminal.write(data);
678
633
  }
679
634
  getText() {
680
- const raw = this.lines.map((line) => line.join("").replace(/\s+$/, ""));
681
- let start = 0;
682
- let end = raw.length;
683
- while (start < end && raw[start] === "") start++;
684
- while (end > start && raw[end - 1] === "") end--;
685
- return raw.slice(start, end).join("\n");
686
- }
687
- consumeEscape(data, start) {
688
- const next = data[start + 1];
689
- if (!next) return start + 1;
690
- if (next === "[") {
691
- let end = start + 2;
692
- while (end < data.length && !/[@-~]/.test(data[end])) end++;
693
- if (end >= data.length) return data.length;
694
- this.applyCsi(data.slice(start + 2, end), data[end]);
695
- return end + 1;
696
- }
697
- if (next === "]") {
698
- let end = start + 2;
699
- while (end < data.length) {
700
- if (data[end] === "\x07") return end + 1;
701
- if (data[end] === "\x1B" && data[end + 1] === "\\") return end + 2;
702
- end++;
703
- }
704
- return data.length;
705
- }
706
- if (next === "7") {
707
- this.savedRow = this.cursorRow;
708
- this.savedCol = this.cursorCol;
709
- return start + 2;
710
- }
711
- if (next === "8") {
712
- this.cursorRow = this.savedRow;
713
- this.cursorCol = this.savedCol;
714
- return start + 2;
715
- }
716
- return start + 2;
717
- }
718
- applyCsi(paramText, finalChar) {
719
- const privateMode = paramText.startsWith("?");
720
- const normalized = privateMode ? paramText.slice(1) : paramText;
721
- const params = normalized.length > 0 ? normalized.split(";").map((p) => parseInt(p || "0", 10) || 0) : [0];
722
- switch (finalChar) {
723
- case "A":
724
- this.cursorRow = clamp(this.cursorRow - (params[0] || 1), 0, this.rows - 1);
725
- return;
726
- case "B":
727
- this.cursorRow = clamp(this.cursorRow + (params[0] || 1), 0, this.rows - 1);
728
- return;
729
- case "C":
730
- this.cursorCol = clamp(this.cursorCol + (params[0] || 1), 0, this.cols - 1);
731
- return;
732
- case "D":
733
- this.cursorCol = clamp(this.cursorCol - (params[0] || 1), 0, this.cols - 1);
734
- return;
735
- case "E":
736
- this.cursorRow = clamp(this.cursorRow + (params[0] || 1), 0, this.rows - 1);
737
- this.cursorCol = 0;
738
- return;
739
- case "F":
740
- this.cursorRow = clamp(this.cursorRow - (params[0] || 1), 0, this.rows - 1);
741
- this.cursorCol = 0;
742
- return;
743
- case "G":
744
- this.cursorCol = clamp((params[0] || 1) - 1, 0, this.cols - 1);
745
- return;
746
- case "H":
747
- case "f": {
748
- const row = (params[0] || 1) - 1;
749
- const col = (params[1] || 1) - 1;
750
- this.cursorRow = clamp(row, 0, this.rows - 1);
751
- this.cursorCol = clamp(col, 0, this.cols - 1);
752
- return;
753
- }
754
- case "J": {
755
- const mode = params[0] || 0;
756
- if (mode === 2 || mode === 3) {
757
- this.reset(this.rows, this.cols);
758
- } else if (mode === 0) {
759
- this.clearToEndOfScreen();
760
- } else if (mode === 1) {
761
- this.clearToStartOfScreen();
762
- }
763
- return;
764
- }
765
- case "K": {
766
- const mode = params[0] || 0;
767
- if (mode === 2) this.clearLine(this.cursorRow, 0, this.cols - 1);
768
- else if (mode === 1) this.clearLine(this.cursorRow, 0, this.cursorCol);
769
- else this.clearLine(this.cursorRow, this.cursorCol, this.cols - 1);
770
- return;
771
- }
772
- case "m":
773
- return;
774
- case "s":
775
- this.savedRow = this.cursorRow;
776
- this.savedCol = this.cursorCol;
777
- return;
778
- case "u":
779
- this.cursorRow = this.savedRow;
780
- this.cursorCol = this.savedCol;
781
- return;
782
- case "h":
783
- case "l":
784
- if (privateMode && (normalized === "1049" || normalized === "47")) {
785
- this.reset(this.rows, this.cols);
786
- }
787
- return;
788
- default:
789
- return;
790
- }
791
- }
792
- putChar(ch) {
793
- if (this.cursorRow < 0 || this.cursorRow >= this.rows) return;
794
- if (this.cursorCol < 0) this.cursorCol = 0;
795
- if (this.cursorCol >= this.cols) this.newLine();
796
- this.lines[this.cursorRow][this.cursorCol] = ch;
797
- this.cursorCol++;
798
- if (this.cursorCol >= this.cols) this.newLine();
799
- }
800
- newLine() {
801
- this.cursorCol = 0;
802
- if (this.cursorRow >= this.rows - 1) {
803
- this.lines.shift();
804
- this.lines.push(Array.from({ length: this.cols }, () => " "));
805
- } else {
806
- this.cursorRow++;
807
- }
808
- }
809
- clearLine(row, start, end) {
810
- if (row < 0 || row >= this.rows) return;
811
- for (let c = clamp(start, 0, this.cols - 1); c <= clamp(end, 0, this.cols - 1); c++) {
812
- this.lines[row][c] = " ";
813
- }
814
- }
815
- clearToEndOfScreen() {
816
- this.clearLine(this.cursorRow, this.cursorCol, this.cols - 1);
817
- for (let r = this.cursorRow + 1; r < this.rows; r++) {
818
- this.clearLine(r, 0, this.cols - 1);
819
- }
820
- }
821
- clearToStartOfScreen() {
822
- for (let r = 0; r < this.cursorRow; r++) {
823
- this.clearLine(r, 0, this.cols - 1);
824
- }
825
- this.clearLine(this.cursorRow, 0, this.cursorCol);
826
- }
827
- makeLines(rows, cols) {
828
- return Array.from({ length: rows }, () => Array.from({ length: cols }, () => " "));
635
+ const buffer = this.terminal.buffer.active;
636
+ const start = Math.max(0, buffer.viewportY || 0);
637
+ const end = Math.max(start, Math.min(buffer.length || 0, start + this.rows));
638
+ const lines = [];
639
+ for (let i = start; i < end; i++) {
640
+ const line = buffer.getLine(i);
641
+ lines.push(line ? line.translateToString(true) : "");
642
+ }
643
+ let first = 0;
644
+ let last = lines.length;
645
+ while (first < last && !lines[first]?.trim()) first++;
646
+ while (last > first && !lines[last - 1]?.trim()) last--;
647
+ return lines.slice(first, last).join("\n");
648
+ }
649
+ createTerminal() {
650
+ const Terminal = loadTerminalCtor();
651
+ return new Terminal({
652
+ cols: this.cols,
653
+ rows: this.rows,
654
+ scrollback: 2e3
655
+ });
829
656
  }
830
657
  };
831
658
  }
@@ -891,6 +718,38 @@ function shSingleQuote(arg) {
891
718
  if (/^[a-zA-Z0-9@%_+=:,./-]+$/.test(arg)) return arg;
892
719
  return `'${arg.replace(/'/g, `'\\''`)}'`;
893
720
  }
721
+ function estimatePromptDisplayLines(text, cols = 100) {
722
+ const normalized = String(text || "").replace(/\r/g, "");
723
+ if (!normalized) return 1;
724
+ return normalized.split("\n").reduce((sum, line) => sum + Math.max(1, Math.ceil(Math.max(1, line.length) / cols)), 0);
725
+ }
726
+ function extractPromptRetrySnippet(text) {
727
+ const lines = String(text || "").replace(/\r/g, "").split("\n").map((line) => line.trim()).filter(Boolean);
728
+ const candidate = lines[lines.length - 1] || lines[0] || "";
729
+ return candidate.slice(-120);
730
+ }
731
+ function normalizePromptText(text) {
732
+ return String(text || "").replace(/\s+/g, " ").trim();
733
+ }
734
+ function compactPromptText(text) {
735
+ return String(text || "").replace(/\s+/g, "").trim();
736
+ }
737
+ function promptLikelyVisible(screenText, promptSnippet) {
738
+ const snippet = normalizePromptText(promptSnippet);
739
+ if (!snippet) return false;
740
+ const normalizedScreen = normalizePromptText(screenText);
741
+ if (normalizedScreen.includes(snippet)) return true;
742
+ const compactScreen = compactPromptText(screenText);
743
+ const compactSnippet = compactPromptText(promptSnippet);
744
+ if (compactSnippet && compactScreen.includes(compactSnippet)) return true;
745
+ const tokens = snippet.split(/[^A-Za-z0-9_.:/-]+/).map((token) => token.trim()).filter((token) => token.length >= 4);
746
+ if (tokens.length === 0) return false;
747
+ const required = Math.min(tokens.length, 3);
748
+ const matched = tokens.filter(
749
+ (token) => normalizedScreen.includes(token) || compactScreen.includes(compactPromptText(token))
750
+ ).length;
751
+ return matched >= required;
752
+ }
894
753
  function parsePatternEntry(x) {
895
754
  if (x instanceof RegExp) return x;
896
755
  if (x && typeof x === "object" && typeof x.source === "string") {
@@ -965,6 +824,8 @@ var init_provider_cli_adapter = __esm({
965
824
  };
966
825
  const rawKeys = provider.approvalKeys;
967
826
  this.approvalKeys = rawKeys && typeof rawKeys === "object" ? rawKeys : {};
827
+ this.sendDelayMs = typeof provider.sendDelayMs === "number" ? Math.max(0, provider.sendDelayMs) : 0;
828
+ this.sendKey = typeof provider.sendKey === "string" && provider.sendKey.length > 0 ? provider.sendKey : "\r";
968
829
  this.cliScripts = provider.scripts || {};
969
830
  const scriptNames = Object.keys(this.cliScripts).filter((k) => typeof this.cliScripts[k] === "function");
970
831
  if (scriptNames.length > 0) {
@@ -1007,6 +868,12 @@ var init_provider_cli_adapter = __esm({
1007
868
  // Output settle debounce — fires after PTY output goes quiet
1008
869
  settleTimer = null;
1009
870
  settledBuffer = "";
871
+ submitPendingUntil = 0;
872
+ responseSettleIgnoreUntil = 0;
873
+ responseEpoch = 0;
874
+ submitRetryTimer = null;
875
+ submitRetryUsed = false;
876
+ submitRetryPromptSnippet = "";
1010
877
  // Resize redraw suppression
1011
878
  resizeSuppressUntil = 0;
1012
879
  // Debug: status transition history
@@ -1033,6 +900,8 @@ var init_provider_cli_adapter = __esm({
1033
900
  timeouts;
1034
901
  // Provider approval key mapping
1035
902
  approvalKeys;
903
+ sendDelayMs;
904
+ sendKey;
1036
905
  /** Inject CLI scripts after construction (e.g. when resolved by ProviderLoader) */
1037
906
  setCliScripts(scripts) {
1038
907
  this.cliScripts = scripts;
@@ -1128,13 +997,16 @@ var init_provider_cli_adapter = __esm({
1128
997
  this.startupParseGate = true;
1129
998
  this.startupBuffer = "";
1130
999
  this.terminalScreen.reset(40, 120);
1131
- this.ready = true;
1000
+ this.ready = false;
1132
1001
  this.setStatus("idle", "pty_ready");
1133
1002
  this.onStatusChange?.();
1134
1003
  }
1135
1004
  // ─── Output Handling ────────────────────────────
1136
1005
  handleOutput(rawData) {
1137
1006
  if (Date.now() < this.resizeSuppressUntil) return;
1007
+ if (rawData.includes("\x1B[6n") || rawData.includes("\x1B[?6n")) {
1008
+ this.ptyProcess?.write("\x1B[1;1R");
1009
+ }
1138
1010
  this.terminalScreen.write(rawData);
1139
1011
  const cleanData = stripAnsi(rawData);
1140
1012
  if (this.isWaitingForResponse && cleanData) {
@@ -1170,7 +1042,9 @@ var init_provider_cli_adapter = __esm({
1170
1042
  const isReady = scriptStatus === "idle" || elapsed > 8e3 || bufCap;
1171
1043
  if (isReady) {
1172
1044
  this.startupParseGate = false;
1045
+ this.ready = true;
1173
1046
  LOG.info("CLI", `[${this.cliType}] Startup gate end (${elapsed}ms, scriptStatus=${scriptStatus})`);
1047
+ this.onStatusChange?.();
1174
1048
  } else {
1175
1049
  return;
1176
1050
  }
@@ -1179,15 +1053,45 @@ var init_provider_cli_adapter = __esm({
1179
1053
  }
1180
1054
  scheduleSettle() {
1181
1055
  if (this.settleTimer) clearTimeout(this.settleTimer);
1056
+ const settleEpoch = this.responseEpoch;
1057
+ const delay = Math.max(
1058
+ this.timeouts.outputSettle,
1059
+ this.submitPendingUntil > Date.now() ? this.submitPendingUntil - Date.now() + this.timeouts.outputSettle : 0
1060
+ );
1182
1061
  this.settleTimer = setTimeout(() => {
1183
1062
  this.settleTimer = null;
1063
+ if (settleEpoch !== this.responseEpoch) return;
1184
1064
  this.settledBuffer = this.recentOutputBuffer;
1185
1065
  this.evaluateSettled();
1186
- }, this.timeouts.outputSettle);
1066
+ }, delay);
1067
+ }
1068
+ armApprovalExitTimeout() {
1069
+ if (this.approvalExitTimeout) clearTimeout(this.approvalExitTimeout);
1070
+ this.approvalExitTimeout = setTimeout(() => {
1071
+ if (this.currentStatus !== "waiting_approval") return;
1072
+ const tail = this.recentOutputBuffer;
1073
+ const modal = this.runParseApproval(tail);
1074
+ const stillWaiting = this.runDetectStatus(tail) === "waiting_approval" || !!modal;
1075
+ if (stillWaiting) {
1076
+ this.activeModal = modal || this.activeModal || { message: "Approval required", buttons: ["Allow", "Deny"] };
1077
+ this.onStatusChange?.();
1078
+ this.armApprovalExitTimeout();
1079
+ return;
1080
+ }
1081
+ LOG.warn("CLI", `[${this.cliType}] Approval timeout \u2014 auto-clearing`);
1082
+ this.activeModal = null;
1083
+ this.lastApprovalResolvedAt = Date.now();
1084
+ this.setStatus("idle", "approval_timeout");
1085
+ this.onStatusChange?.();
1086
+ }, 6e4);
1187
1087
  }
1188
1088
  evaluateSettled() {
1089
+ if (this.submitPendingUntil > Date.now()) return;
1090
+ if (this.responseSettleIgnoreUntil > Date.now()) return;
1189
1091
  const tail = this.settledBuffer;
1190
- const scriptStatus = this.runDetectStatus(tail);
1092
+ const modal = this.runParseApproval(tail);
1093
+ const rawScriptStatus = this.runDetectStatus(tail);
1094
+ const scriptStatus = rawScriptStatus === "waiting_approval" || modal ? "waiting_approval" : rawScriptStatus;
1191
1095
  if (!scriptStatus) return;
1192
1096
  const prevStatus = this.currentStatus;
1193
1097
  if (scriptStatus === "waiting_approval") {
@@ -1195,19 +1099,9 @@ var init_provider_cli_adapter = __esm({
1195
1099
  if (!inCooldown) {
1196
1100
  this.isWaitingForResponse = true;
1197
1101
  this.setStatus("waiting_approval", "script_detect");
1198
- const modal = this.runParseApproval(tail);
1199
1102
  this.activeModal = modal || { message: "Approval required", buttons: ["Allow", "Deny"] };
1200
1103
  if (this.idleTimeout) clearTimeout(this.idleTimeout);
1201
- if (this.approvalExitTimeout) clearTimeout(this.approvalExitTimeout);
1202
- this.approvalExitTimeout = setTimeout(() => {
1203
- if (this.currentStatus === "waiting_approval") {
1204
- LOG.warn("CLI", `[${this.cliType}] Approval timeout \u2014 auto-clearing`);
1205
- this.activeModal = null;
1206
- this.lastApprovalResolvedAt = Date.now();
1207
- this.setStatus("idle", "approval_timeout");
1208
- this.onStatusChange?.();
1209
- }
1210
- }, 6e4);
1104
+ this.armApprovalExitTimeout();
1211
1105
  this.onStatusChange?.();
1212
1106
  return;
1213
1107
  }
@@ -1243,7 +1137,12 @@ var init_provider_cli_adapter = __esm({
1243
1137
  this.lastApprovalResolvedAt = Date.now();
1244
1138
  }
1245
1139
  if (this.isWaitingForResponse) {
1246
- this.finishResponse();
1140
+ if (this.idleTimeout) clearTimeout(this.idleTimeout);
1141
+ this.idleTimeout = setTimeout(() => {
1142
+ if (this.isWaitingForResponse && this.currentStatus !== "waiting_approval") {
1143
+ this.finishResponse();
1144
+ }
1145
+ }, this.timeouts.idleFinish);
1247
1146
  } else if (prevStatus !== "idle") {
1248
1147
  this.setStatus("idle", "script_detect");
1249
1148
  this.onStatusChange?.();
@@ -1251,6 +1150,8 @@ var init_provider_cli_adapter = __esm({
1251
1150
  }
1252
1151
  }
1253
1152
  finishResponse() {
1153
+ if (this.submitPendingUntil > Date.now()) return;
1154
+ if (this.responseSettleIgnoreUntil > Date.now()) return;
1254
1155
  if (this.responseTimeout) {
1255
1156
  clearTimeout(this.responseTimeout);
1256
1157
  this.responseTimeout = null;
@@ -1263,8 +1164,15 @@ var init_provider_cli_adapter = __esm({
1263
1164
  clearTimeout(this.approvalExitTimeout);
1264
1165
  this.approvalExitTimeout = null;
1265
1166
  }
1167
+ if (this.submitRetryTimer) {
1168
+ clearTimeout(this.submitRetryTimer);
1169
+ this.submitRetryTimer = null;
1170
+ }
1266
1171
  this.responseBuffer = "";
1267
1172
  this.isWaitingForResponse = false;
1173
+ this.responseSettleIgnoreUntil = 0;
1174
+ this.submitRetryUsed = false;
1175
+ this.submitRetryPromptSnippet = "";
1268
1176
  this.activeModal = null;
1269
1177
  this.setStatus("idle", "response_finished");
1270
1178
  this.onStatusChange?.();
@@ -1273,7 +1181,11 @@ var init_provider_cli_adapter = __esm({
1273
1181
  runDetectStatus(text) {
1274
1182
  if (!this.cliScripts?.detectStatus) return null;
1275
1183
  try {
1276
- return this.cliScripts.detectStatus({ tail: text.slice(-500) });
1184
+ return this.cliScripts.detectStatus({
1185
+ tail: text.slice(-500),
1186
+ screenText: this.terminalScreen.getText(),
1187
+ rawBuffer: this.accumulatedRawBuffer
1188
+ });
1277
1189
  } catch (e) {
1278
1190
  LOG.warn("CLI", `[${this.cliType}] detectStatus error: ${e.message}`);
1279
1191
  return null;
@@ -1375,18 +1287,95 @@ ${data.message || ""}`.trim();
1375
1287
  }
1376
1288
  async sendMessage(text) {
1377
1289
  if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
1290
+ if (this.startupParseGate) {
1291
+ const deadline = Date.now() + 1e4;
1292
+ while (this.startupParseGate && Date.now() < deadline) {
1293
+ await new Promise((resolve8) => setTimeout(resolve8, 50));
1294
+ }
1295
+ }
1378
1296
  if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
1379
1297
  if (this.isWaitingForResponse) return;
1380
1298
  this.messages.push({ role: "user", content: text, timestamp: Date.now() });
1381
1299
  this.structuredMessages.push({ role: "user", content: text, timestamp: Date.now() });
1382
1300
  this.isWaitingForResponse = true;
1383
1301
  this.responseBuffer = "";
1302
+ this.submitRetryUsed = false;
1303
+ this.submitRetryPromptSnippet = extractPromptRetrySnippet(text);
1304
+ const normalizedPromptSnippet = normalizePromptText(this.submitRetryPromptSnippet);
1305
+ if (this.submitRetryTimer) {
1306
+ clearTimeout(this.submitRetryTimer);
1307
+ this.submitRetryTimer = null;
1308
+ }
1309
+ const estimatedLines = estimatePromptDisplayLines(text);
1310
+ const submitDelayMs = this.sendDelayMs + Math.min(2e3, Math.max(0, estimatedLines - 1) * 350);
1311
+ const maxEchoWaitMs = submitDelayMs + Math.max(1500, Math.min(5e3, estimatedLines * 500));
1312
+ const retryDelayMs = Math.max(350, Math.min(1500, Math.max(this.sendDelayMs, submitDelayMs)));
1313
+ if (this.settleTimer) {
1314
+ clearTimeout(this.settleTimer);
1315
+ this.settleTimer = null;
1316
+ }
1317
+ this.responseEpoch += 1;
1318
+ this.responseSettleIgnoreUntil = Date.now() + submitDelayMs + this.timeouts.outputSettle + 250;
1384
1319
  this.setStatus("generating", "sendMessage");
1385
1320
  this.onStatusChange?.();
1386
- this.ptyProcess.write(text + "\r");
1387
- this.responseTimeout = setTimeout(() => {
1388
- if (this.isWaitingForResponse) this.finishResponse();
1389
- }, this.timeouts.maxResponse);
1321
+ if (submitDelayMs > 0) {
1322
+ this.submitPendingUntil = Date.now() + submitDelayMs;
1323
+ }
1324
+ this.ptyProcess.write(text);
1325
+ const submit = () => {
1326
+ if (!this.ptyProcess) return;
1327
+ this.submitPendingUntil = 0;
1328
+ this.ptyProcess.write(this.sendKey);
1329
+ const retrySubmitIfStuck = (attempt) => {
1330
+ this.submitRetryTimer = null;
1331
+ if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
1332
+ if (this.currentStatus !== "generating") return;
1333
+ if ((this.responseBuffer || "").trim()) return;
1334
+ const screenText = this.terminalScreen.getText();
1335
+ if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
1336
+ 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(screenText)) return;
1337
+ this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
1338
+ LOG.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt ${attempt})`);
1339
+ this.ptyProcess.write(this.sendKey);
1340
+ if (attempt >= 3) {
1341
+ this.submitRetryUsed = true;
1342
+ return;
1343
+ }
1344
+ this.submitRetryTimer = setTimeout(() => retrySubmitIfStuck(attempt + 1), retryDelayMs);
1345
+ };
1346
+ this.submitRetryTimer = setTimeout(() => retrySubmitIfStuck(1), retryDelayMs);
1347
+ this.responseTimeout = setTimeout(() => {
1348
+ if (this.isWaitingForResponse) this.finishResponse();
1349
+ }, this.timeouts.maxResponse);
1350
+ };
1351
+ const submitStartedAt = Date.now();
1352
+ let lastNormalizedScreen = "";
1353
+ let lastScreenChangeAt = submitStartedAt;
1354
+ const waitForEchoAndSubmit = () => {
1355
+ if (!this.ptyProcess) return;
1356
+ const now = Date.now();
1357
+ const elapsed = now - submitStartedAt;
1358
+ const screenText = this.terminalScreen.getText();
1359
+ const normalizedScreen = normalizePromptText(screenText);
1360
+ if (normalizedScreen !== lastNormalizedScreen) {
1361
+ lastNormalizedScreen = normalizedScreen;
1362
+ lastScreenChangeAt = now;
1363
+ }
1364
+ const echoVisible = !normalizedPromptSnippet || promptLikelyVisible(screenText, normalizedPromptSnippet);
1365
+ if (echoVisible) {
1366
+ const screenSettled = now - lastScreenChangeAt >= 500;
1367
+ if (elapsed >= submitDelayMs && screenSettled) {
1368
+ submit();
1369
+ return;
1370
+ }
1371
+ }
1372
+ if (elapsed >= maxEchoWaitMs) {
1373
+ submit();
1374
+ return;
1375
+ }
1376
+ setTimeout(waitForEchoAndSubmit, 50);
1377
+ };
1378
+ waitForEchoAndSubmit();
1390
1379
  }
1391
1380
  getPartialResponse() {
1392
1381
  if (!this.isWaitingForResponse) return "";
@@ -1404,6 +1393,10 @@ ${data.message || ""}`.trim();
1404
1393
  clearTimeout(this.approvalExitTimeout);
1405
1394
  this.approvalExitTimeout = null;
1406
1395
  }
1396
+ if (this.submitRetryTimer) {
1397
+ clearTimeout(this.submitRetryTimer);
1398
+ this.submitRetryTimer = null;
1399
+ }
1407
1400
  if (this.ptyProcess) {
1408
1401
  this.ptyProcess.write("");
1409
1402
  setTimeout(() => {
@@ -1425,6 +1418,8 @@ ${data.message || ""}`.trim();
1425
1418
  this.structuredMessages = [];
1426
1419
  this.accumulatedBuffer = "";
1427
1420
  this.accumulatedRawBuffer = "";
1421
+ this.submitRetryUsed = false;
1422
+ this.submitRetryPromptSnippet = "";
1428
1423
  this.terminalScreen.reset();
1429
1424
  this.onStatusChange?.();
1430
1425
  }
@@ -1438,7 +1433,16 @@ ${data.message || ""}`.trim();
1438
1433
  this.ptyProcess?.write(data);
1439
1434
  }
1440
1435
  resolveModal(buttonIndex) {
1441
- if (!this.ptyProcess || this.currentStatus !== "waiting_approval") return;
1436
+ if (!this.ptyProcess || this.currentStatus !== "waiting_approval" && !this.activeModal) return;
1437
+ this.activeModal = null;
1438
+ this.lastApprovalResolvedAt = Date.now();
1439
+ this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
1440
+ if (this.approvalExitTimeout) {
1441
+ clearTimeout(this.approvalExitTimeout);
1442
+ this.approvalExitTimeout = null;
1443
+ }
1444
+ this.setStatus("generating", "approval_resolved");
1445
+ this.onStatusChange?.();
1442
1446
  if (buttonIndex in this.approvalKeys) {
1443
1447
  this.ptyProcess.write(this.approvalKeys[buttonIndex]);
1444
1448
  } else {
@@ -1469,13 +1473,21 @@ ${data.message || ""}`.trim();
1469
1473
  messages: this.messages.slice(-20),
1470
1474
  structuredMessages: this.structuredMessages.slice(-20),
1471
1475
  messageCount: this.messages.length,
1476
+ screenText: this.terminalScreen.getText().slice(-4e3),
1472
1477
  startupBuffer: this.startupBuffer.slice(-4e3),
1473
1478
  recentOutputBuffer: this.recentOutputBuffer.slice(-500),
1474
1479
  settledBuffer: this.settledBuffer.slice(-500),
1475
1480
  accumulatedBufferLength: this.accumulatedBuffer.length,
1481
+ accumulatedRawBufferLength: this.accumulatedRawBuffer.length,
1482
+ rawBufferPreview: this.accumulatedRawBuffer.slice(-1e3),
1483
+ responseBuffer: this.responseBuffer.slice(-1e3),
1476
1484
  isWaitingForResponse: this.isWaitingForResponse,
1477
1485
  activeModal: this.activeModal,
1478
1486
  lastApprovalResolvedAt: this.lastApprovalResolvedAt,
1487
+ sendDelayMs: this.sendDelayMs,
1488
+ sendKey: this.sendKey,
1489
+ submitPendingUntil: this.submitPendingUntil,
1490
+ responseSettleIgnoreUntil: this.responseSettleIgnoreUntil,
1479
1491
  resizeSuppressUntil: this.resizeSuppressUntil,
1480
1492
  hasCliScripts: this.hasCliScripts(),
1481
1493
  scriptNames: Object.keys(this.cliScripts).filter((k) => typeof this.cliScripts[k] === "function"),
@@ -8137,7 +8149,8 @@ var CliProviderInstance = class {
8137
8149
  return { ...m, content };
8138
8150
  });
8139
8151
  const partial = this.adapter.getPartialResponse();
8140
- if (adapterStatus.status === "generating" && partial) {
8152
+ const shouldAppendRawPartial = !parsedStatus;
8153
+ if (shouldAppendRawPartial && adapterStatus.status === "generating" && partial) {
8141
8154
  const cleaned = partial.trim();
8142
8155
  if (cleaned && cleaned !== "(generating...)") {
8143
8156
  recentMessages.push({
@@ -13236,7 +13249,7 @@ var DevServer = class _DevServer {
13236
13249
  lines.push("| Function | Input | Return |");
13237
13250
  lines.push("|---|---|---|");
13238
13251
  lines.push("| `parseOutput` | `{ buffer, rawBuffer, recentBuffer, screenText, messages, partialResponse }` | `{ id, status, title, messages, activeModal }` |");
13239
- lines.push("| `detectStatus` | `{ tail }` | `idle`, `generating`, `waiting_approval`, or `error` |");
13252
+ lines.push("| `detectStatus` | `{ tail, screenText, rawBuffer }` | `idle`, `generating`, `waiting_approval`, or `error` |");
13240
13253
  lines.push("| `parseApproval` | `{ buffer, rawBuffer, tail }` | `{ message, buttons }` or `null` |");
13241
13254
  lines.push("");
13242
13255
  lines.push("## Rules");
@@ -13249,6 +13262,7 @@ var DevServer = class _DevServer {
13249
13262
  lines.push("7. Use `rawBuffer` only when ANSI/control-sequence artifacts matter. Do not depend on raw escape noise unless necessary.");
13250
13263
  lines.push("8. Keep exports compatible with the existing `scripts.js` router (`module.exports = function ...`).");
13251
13264
  lines.push("9. Do not rewrite unrelated provider config. Only touch the scripts needed for this task unless a tiny supporting change is required.");
13265
+ lines.push("10. When the verification API returns `instanceId`, keep using that exact instance for follow-up `send`, `resolve`, `raw`, and `stop` calls. Do not assume type-only routing is safe if multiple sessions exist.");
13252
13266
  lines.push("");
13253
13267
  lines.push("## Task");
13254
13268
  lines.push(`Edit files in \`${providerDir}\` to implement: **${functions.join(", ")}**`);
@@ -13269,25 +13283,38 @@ var DevServer = class _DevServer {
13269
13283
  lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/status`);
13270
13284
  lines.push("```");
13271
13285
  lines.push("");
13272
- lines.push("### 3. Send a rich test prompt");
13286
+ lines.push("Extract the current `instanceId` from the launch or status response and keep using it below.");
13287
+ lines.push("");
13288
+ lines.push("### 3. Send a realistic approval-triggering prompt");
13273
13289
  lines.push("```bash");
13274
13290
  lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/send \\`);
13275
13291
  lines.push(' -H "Content-Type: application/json" \\');
13276
- lines.push(` -d '{"type":"${type}","text":"Write a short python snippet, include a markdown table, and briefly explain what you did."}'`);
13292
+ lines.push(` -d '{"type":"${type}","instanceId":"<INSTANCE_ID>","text":"Create a file at tmp/adhdev_provider_fix_test.py that prints the current working directory and the squares of 1 through 5, then run python3 tmp/adhdev_provider_fix_test.py and tell me the exact output."}'`);
13277
13293
  lines.push("```");
13278
13294
  lines.push("");
13279
- lines.push("### 4. If approval appears, resolve it");
13295
+ lines.push("### 4. If approval appears, resolve it until the CLI reaches idle");
13280
13296
  lines.push("```bash");
13281
13297
  lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/resolve \\`);
13282
13298
  lines.push(' -H "Content-Type: application/json" \\');
13283
- lines.push(` -d '{"type":"${type}","buttonIndex":0}'`);
13299
+ lines.push(` -d '{"type":"${type}","instanceId":"<INSTANCE_ID>","buttonIndex":0}'`);
13300
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/raw \\`);
13301
+ lines.push(' -H "Content-Type: application/json" \\');
13302
+ lines.push(` -d '{"type":"${type}","instanceId":"<INSTANCE_ID>","keys":"1"}'`);
13303
+ lines.push("```");
13304
+ lines.push("");
13305
+ lines.push("Use `resolve` when the parsed modal buttons are correct. Use `raw` when the CLI expects a literal keystroke like `1`, `y`, or Enter. Repeat until idle.");
13306
+ lines.push("");
13307
+ lines.push("### 5. Verify the side effects outside the CLI");
13308
+ lines.push("```bash");
13309
+ lines.push("test -f tmp/adhdev_provider_fix_test.py");
13310
+ lines.push("python3 tmp/adhdev_provider_fix_test.py");
13284
13311
  lines.push("```");
13285
13312
  lines.push("");
13286
- lines.push("### 5. Stop the CLI when finished");
13313
+ lines.push("### 6. Stop the CLI when finished");
13287
13314
  lines.push("```bash");
13288
13315
  lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/stop \\`);
13289
13316
  lines.push(' -H "Content-Type: application/json" \\');
13290
- lines.push(` -d '{"type":"${type}"}'`);
13317
+ lines.push(` -d '{"type":"${type}","instanceId":"<INSTANCE_ID>"}'`);
13291
13318
  lines.push("```");
13292
13319
  lines.push("");
13293
13320
  lines.push("## Required Validation");
@@ -13295,7 +13322,9 @@ var DevServer = class _DevServer {
13295
13322
  lines.push("2. Confirm `parseOutput` produces a stable transcript without duplicating past turns when the PTY redraws.");
13296
13323
  lines.push("3. Confirm the latest assistant message streams through `partialResponse` while generation is in progress.");
13297
13324
  lines.push("4. Confirm approval parsing returns meaningful button labels when the CLI requests permission.");
13298
- lines.push("5. Re-run the debug endpoints after edits. Do NOT finish until the parsed result looks correct.");
13325
+ lines.push("5. Confirm the Python file was actually created and executed, not just described in chat text.");
13326
+ lines.push("6. Confirm the final assistant transcript includes the exact Python output, including the working directory line and the five square numbers.");
13327
+ lines.push("7. Re-run the debug endpoints after edits. Do NOT finish until the parsed result looks correct.");
13299
13328
  lines.push("");
13300
13329
  if (userComment) {
13301
13330
  lines.push("## \u26A0\uFE0F User Instructions (HIGH PRIORITY)");
@@ -13417,6 +13446,14 @@ data: ${JSON.stringify(msg.data)}
13417
13446
  }));
13418
13447
  this.json(res, 200, { instances: result, count: result.length });
13419
13448
  }
13449
+ findCliTarget(type, instanceId) {
13450
+ if (!this.instanceManager) return null;
13451
+ const cliStates = this.instanceManager.collectAllStates().filter((s) => s.category === "cli" || s.category === "acp");
13452
+ if (instanceId) return cliStates.find((s) => s.instanceId === instanceId) || null;
13453
+ if (!type) return cliStates[cliStates.length - 1] || null;
13454
+ const matches = cliStates.filter((s) => s.type === type);
13455
+ return matches[matches.length - 1] || null;
13456
+ }
13420
13457
  /** POST /api/cli/launch — launch a CLI agent { type, workingDir?, args? } */
13421
13458
  async handleCliLaunch(req, res) {
13422
13459
  if (!this.cliManager) {
@@ -13448,10 +13485,7 @@ data: ${JSON.stringify(msg.data)}
13448
13485
  this.json(res, 400, { error: "text required" });
13449
13486
  return;
13450
13487
  }
13451
- const allStates = this.instanceManager.collectAllStates();
13452
- const target = allStates.find(
13453
- (s) => (s.category === "cli" || s.category === "acp") && (instanceId ? s.instanceId === instanceId : s.type === type)
13454
- );
13488
+ const target = this.findCliTarget(type, instanceId);
13455
13489
  if (!target) {
13456
13490
  this.json(res, 404, { error: `No running instance found for: ${type || instanceId}` });
13457
13491
  return;
@@ -13471,10 +13505,7 @@ data: ${JSON.stringify(msg.data)}
13471
13505
  }
13472
13506
  const body = await this.readBody(req);
13473
13507
  const { type, instanceId } = body;
13474
- const allStates = this.instanceManager.collectAllStates();
13475
- const target = allStates.find(
13476
- (s) => (s.category === "cli" || s.category === "acp") && (instanceId ? s.instanceId === instanceId : s.type === type)
13477
- );
13508
+ const target = this.findCliTarget(type, instanceId);
13478
13509
  if (!target) {
13479
13510
  this.json(res, 404, { error: `No running instance found for: ${type || instanceId}` });
13480
13511
  return;
@@ -13529,11 +13560,9 @@ data: ${JSON.stringify(msg.data)}
13529
13560
  this.json(res, 503, { error: "InstanceManager not available" });
13530
13561
  return;
13531
13562
  }
13532
- const allStates = this.instanceManager.collectAllStates();
13533
- const target = allStates.find(
13534
- (s) => (s.category === "cli" || s.category === "acp") && s.type === type
13535
- );
13563
+ const target = this.findCliTarget(type);
13536
13564
  if (!target) {
13565
+ const allStates = this.instanceManager.collectAllStates();
13537
13566
  this.json(res, 404, { error: `No running instance for: ${type}`, available: allStates.filter((s) => s.category === "cli" || s.category === "acp").map((s) => s.type) });
13538
13567
  return;
13539
13568
  }
@@ -13580,21 +13609,25 @@ data: ${JSON.stringify(msg.data)}
13580
13609
  this.json(res, 503, { error: "CliManager not available" });
13581
13610
  return;
13582
13611
  }
13583
- let adapter = null;
13584
- for (const [, a] of this.cliManager.adapters) {
13585
- if (type && a.cliType === type) {
13586
- adapter = a;
13587
- break;
13588
- }
13612
+ if (!this.instanceManager) {
13613
+ this.json(res, 503, { error: "InstanceManager not available" });
13614
+ return;
13589
13615
  }
13590
- if (!adapter) {
13616
+ const target = this.findCliTarget(type, instanceId);
13617
+ if (!target) {
13591
13618
  this.json(res, 404, { error: `No running adapter for: ${type || instanceId}` });
13592
13619
  return;
13593
13620
  }
13621
+ const instance = this.instanceManager.getInstance(target.instanceId);
13622
+ const adapter = instance?.getAdapter?.() || instance?.adapter;
13623
+ if (!adapter) {
13624
+ this.json(res, 404, { error: `Adapter not found for instance: ${target.instanceId}` });
13625
+ return;
13626
+ }
13594
13627
  try {
13595
13628
  if (typeof adapter.resolveModal === "function") {
13596
13629
  adapter.resolveModal(buttonIndex);
13597
- this.json(res, 200, { resolved: true, type, buttonIndex });
13630
+ this.json(res, 200, { resolved: true, type: target.type, instanceId: target.instanceId, buttonIndex });
13598
13631
  } else {
13599
13632
  this.json(res, 400, { error: "resolveModal not available on this adapter" });
13600
13633
  }
@@ -13614,21 +13647,25 @@ data: ${JSON.stringify(msg.data)}
13614
13647
  this.json(res, 503, { error: "CliManager not available" });
13615
13648
  return;
13616
13649
  }
13617
- let adapter = null;
13618
- for (const [, a] of this.cliManager.adapters) {
13619
- if (type && a.cliType === type) {
13620
- adapter = a;
13621
- break;
13622
- }
13650
+ if (!this.instanceManager) {
13651
+ this.json(res, 503, { error: "InstanceManager not available" });
13652
+ return;
13623
13653
  }
13624
- if (!adapter) {
13654
+ const target = this.findCliTarget(type, instanceId);
13655
+ if (!target) {
13625
13656
  this.json(res, 404, { error: `No running adapter for: ${type || instanceId}` });
13626
13657
  return;
13627
13658
  }
13659
+ const instance = this.instanceManager.getInstance(target.instanceId);
13660
+ const adapter = instance?.getAdapter?.() || instance?.adapter;
13661
+ if (!adapter) {
13662
+ this.json(res, 404, { error: `Adapter not found for instance: ${target.instanceId}` });
13663
+ return;
13664
+ }
13628
13665
  try {
13629
13666
  if (typeof adapter.writeRaw === "function") {
13630
13667
  adapter.writeRaw(keys);
13631
- this.json(res, 200, { sent: true, type, keysLength: keys.length });
13668
+ this.json(res, 200, { sent: true, type: target.type, instanceId: target.instanceId, keysLength: keys.length });
13632
13669
  } else {
13633
13670
  this.json(res, 400, { error: "writeRaw not available on this adapter" });
13634
13671
  }