@adhdev/daemon-core 0.6.56 → 0.6.58

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.
Files changed (37) hide show
  1. package/dist/index.d.ts +23 -0
  2. package/dist/index.js +423 -95
  3. package/dist/index.js.map +1 -1
  4. package/package.json +1 -1
  5. package/providers/_builtin/cli/aider-cli/scripts/1.0/parse_output.js +51 -3
  6. package/providers/_builtin/cli/claude-cli/provider.json +18 -6
  7. package/providers/_builtin/cli/claude-cli/scripts/1.0/detect_status.js +68 -16
  8. package/providers/_builtin/cli/claude-cli/scripts/1.0/parse_approval.js +81 -22
  9. package/providers/_builtin/cli/claude-cli/scripts/1.0/parse_output.js +347 -94
  10. package/providers/_builtin/cli/codex-cli/provider.json +2 -0
  11. package/providers/_builtin/cli/codex-cli/scripts/1.0/detect_status.js +44 -10
  12. package/providers/_builtin/cli/codex-cli/scripts/1.0/parse_approval.js +83 -7
  13. package/providers/_builtin/cli/codex-cli/scripts/1.0/parse_output.js +501 -47
  14. package/providers/_builtin/cli/cursor-cli/scripts/1.0/parse_output.js +1 -1
  15. package/providers/_builtin/cli/github-copilot-cli/scripts/1.0/parse_output.js +1 -1
  16. package/providers/_builtin/cli/goose-cli/scripts/1.0/parse_output.js +1 -1
  17. package/providers/_builtin/cli/opencode-cli/scripts/1.0/parse_output.js +1 -1
  18. package/providers/_builtin/ide/vscode/provider.json +5 -1
  19. package/providers/_builtin/ide/vscode/scripts/1.0/focus_editor.js +1 -0
  20. package/providers/_builtin/ide/vscode/scripts/1.0/list_models.js +1 -0
  21. package/providers/_builtin/ide/vscode/scripts/1.0/list_sessions.js +1 -0
  22. package/providers/_builtin/ide/vscode/scripts/1.0/new_session.js +1 -0
  23. package/providers/_builtin/ide/vscode/scripts/1.0/open_panel.js +1 -0
  24. package/providers/_builtin/ide/vscode/scripts/1.0/read_chat.js +1 -0
  25. package/providers/_builtin/ide/vscode/scripts/1.0/resolve_action.js +1 -0
  26. package/providers/_builtin/ide/vscode/scripts/1.0/scripts.js +25 -0
  27. package/providers/_builtin/ide/vscode/scripts/1.0/send_message.js +1 -0
  28. package/providers/_builtin/ide/vscode/scripts/1.0/set_model.js +1 -0
  29. package/providers/_builtin/ide/vscode/scripts/1.0/switch_session.js +1 -0
  30. package/providers/_builtin/registry.json +1 -1
  31. package/src/cli-adapters/provider-cli-adapter.ts +410 -65
  32. package/src/commands/chat-commands.ts +7 -1
  33. package/src/config/chat-history.ts +53 -1
  34. package/src/daemon/dev-server.ts +7 -9
  35. package/src/providers/cli-provider-instance.ts +10 -23
  36. package/src/providers/provider-instance.ts +1 -0
  37. package/src/providers/version-archive.ts +4 -1
package/dist/index.js CHANGED
@@ -718,6 +718,68 @@ function shSingleQuote(arg) {
718
718
  if (/^[a-zA-Z0-9@%_+=:,./-]+$/.test(arg)) return arg;
719
719
  return `'${arg.replace(/'/g, `'\\''`)}'`;
720
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
+ }
753
+ function splitHistoryLines(text) {
754
+ return String(text || "").split("\n").map((line) => line.replace(/\s+$/, ""));
755
+ }
756
+ function normalizeHistoryLine(line) {
757
+ return String(line || "").replace(/\s+/g, " ").trim();
758
+ }
759
+ function mergeTerminalHistory(existing, snapshot) {
760
+ const next = String(snapshot || "").trim();
761
+ if (!next) return existing;
762
+ const prev = String(existing || "").trim();
763
+ if (!prev) return next;
764
+ if (prev === next || prev.endsWith(next)) return prev;
765
+ const prevLines = splitHistoryLines(prev);
766
+ const nextLines = splitHistoryLines(next);
767
+ const prevNorm = prevLines.map(normalizeHistoryLine);
768
+ const nextNorm = nextLines.map(normalizeHistoryLine);
769
+ const maxOverlap = Math.min(prevLines.length, nextLines.length);
770
+ for (let overlap = maxOverlap; overlap >= 1; overlap -= 1) {
771
+ const prevTail = prevNorm.slice(prevNorm.length - overlap);
772
+ const nextHead = nextNorm.slice(0, overlap);
773
+ if (prevTail.every((line, index) => line === nextHead[index])) {
774
+ return [...prevLines, ...nextLines.slice(overlap)].join("\n").trim();
775
+ }
776
+ }
777
+ const compactPrev = prevNorm.join("\n");
778
+ const compactNext = nextNorm.join("\n");
779
+ if (compactPrev.includes(compactNext)) return prev;
780
+ return `${prev}
781
+ ${next}`.trim();
782
+ }
721
783
  function parsePatternEntry(x) {
722
784
  if (x instanceof RegExp) return x;
723
785
  if (x && typeof x === "object" && typeof x.source === "string") {
@@ -794,6 +856,7 @@ var init_provider_cli_adapter = __esm({
794
856
  this.approvalKeys = rawKeys && typeof rawKeys === "object" ? rawKeys : {};
795
857
  this.sendDelayMs = typeof provider.sendDelayMs === "number" ? Math.max(0, provider.sendDelayMs) : 0;
796
858
  this.sendKey = typeof provider.sendKey === "string" && provider.sendKey.length > 0 ? provider.sendKey : "\r";
859
+ this.submitStrategy = provider.submitStrategy === "immediate" ? "immediate" : "wait_for_echo";
797
860
  this.cliScripts = provider.scripts || {};
798
861
  const scriptNames = Object.keys(this.cliScripts).filter((k) => typeof this.cliScripts[k] === "function");
799
862
  if (scriptNames.length > 0) {
@@ -808,6 +871,7 @@ var init_provider_cli_adapter = __esm({
808
871
  provider;
809
872
  ptyProcess = null;
810
873
  messages = [];
874
+ committedMessages = [];
811
875
  structuredMessages = [];
812
876
  currentStatus = "starting";
813
877
  onStatusChange = null;
@@ -837,6 +901,11 @@ var init_provider_cli_adapter = __esm({
837
901
  settleTimer = null;
838
902
  settledBuffer = "";
839
903
  submitPendingUntil = 0;
904
+ responseSettleIgnoreUntil = 0;
905
+ responseEpoch = 0;
906
+ submitRetryTimer = null;
907
+ submitRetryUsed = false;
908
+ submitRetryPromptSnippet = "";
840
909
  // Resize redraw suppression
841
910
  resizeSuppressUntil = 0;
842
911
  // Debug: status transition history
@@ -849,8 +918,35 @@ var init_provider_cli_adapter = __esm({
849
918
  accumulatedRawBuffer = "";
850
919
  /** Current visible terminal screen snapshot */
851
920
  terminalScreen = new TerminalScreen(40, 120);
921
+ /** Rolling append-only terminal transcript built from screen snapshots */
922
+ terminalHistory = "";
852
923
  /** Max accumulated buffer size (last 50KB) */
853
924
  static MAX_ACCUMULATED_BUFFER = 5e4;
925
+ currentTurnScope = null;
926
+ syncMessageViews() {
927
+ this.messages = [...this.committedMessages];
928
+ this.structuredMessages = [...this.committedMessages];
929
+ }
930
+ sliceFromOffset(text, start) {
931
+ if (!text) return "";
932
+ if (!Number.isFinite(start) || start <= 0) return text;
933
+ if (start >= text.length) return "";
934
+ return text.slice(start);
935
+ }
936
+ buildParseInput(baseMessages, partialResponse, scope) {
937
+ const buffer = scope ? this.sliceFromOffset(this.terminalHistory, scope.terminalHistoryStart) || this.sliceFromOffset(this.accumulatedBuffer, scope.bufferStart) || this.accumulatedBuffer : this.accumulatedBuffer;
938
+ const rawBuffer = scope ? this.sliceFromOffset(this.accumulatedRawBuffer, scope.rawBufferStart) || this.accumulatedRawBuffer : this.accumulatedRawBuffer;
939
+ const terminalHistory = scope ? this.sliceFromOffset(this.terminalHistory, scope.terminalHistoryStart) || this.terminalHistory : this.terminalHistory;
940
+ return {
941
+ buffer,
942
+ rawBuffer,
943
+ recentBuffer: buffer.slice(-1e3) || this.recentOutputBuffer,
944
+ screenText: this.terminalScreen.getText(),
945
+ terminalHistory,
946
+ messages: [...baseMessages],
947
+ partialResponse
948
+ };
949
+ }
854
950
  setStatus(status, trigger) {
855
951
  const prev = this.currentStatus;
856
952
  if (prev === status) return;
@@ -865,6 +961,7 @@ var init_provider_cli_adapter = __esm({
865
961
  approvalKeys;
866
962
  sendDelayMs;
867
963
  sendKey;
964
+ submitStrategy;
868
965
  /** Inject CLI scripts after construction (e.g. when resolved by ProviderLoader) */
869
966
  setCliScripts(scripts) {
870
967
  this.cliScripts = scripts;
@@ -960,7 +1057,9 @@ var init_provider_cli_adapter = __esm({
960
1057
  this.startupParseGate = true;
961
1058
  this.startupBuffer = "";
962
1059
  this.terminalScreen.reset(40, 120);
963
- this.ready = true;
1060
+ this.terminalHistory = "";
1061
+ this.currentTurnScope = null;
1062
+ this.ready = false;
964
1063
  this.setStatus("idle", "pty_ready");
965
1064
  this.onStatusChange?.();
966
1065
  }
@@ -971,6 +1070,7 @@ var init_provider_cli_adapter = __esm({
971
1070
  this.ptyProcess?.write("\x1B[1;1R");
972
1071
  }
973
1072
  this.terminalScreen.write(rawData);
1073
+ this.terminalHistory = mergeTerminalHistory(this.terminalHistory, this.terminalScreen.getText());
974
1074
  const cleanData = stripAnsi(rawData);
975
1075
  if (this.isWaitingForResponse && cleanData) {
976
1076
  this.responseBuffer = (this.responseBuffer + cleanData).slice(-8e3);
@@ -1005,7 +1105,9 @@ var init_provider_cli_adapter = __esm({
1005
1105
  const isReady = scriptStatus === "idle" || elapsed > 8e3 || bufCap;
1006
1106
  if (isReady) {
1007
1107
  this.startupParseGate = false;
1108
+ this.ready = true;
1008
1109
  LOG.info("CLI", `[${this.cliType}] Startup gate end (${elapsed}ms, scriptStatus=${scriptStatus})`);
1110
+ this.onStatusChange?.();
1009
1111
  } else {
1010
1112
  return;
1011
1113
  }
@@ -1014,19 +1116,45 @@ var init_provider_cli_adapter = __esm({
1014
1116
  }
1015
1117
  scheduleSettle() {
1016
1118
  if (this.settleTimer) clearTimeout(this.settleTimer);
1119
+ const settleEpoch = this.responseEpoch;
1017
1120
  const delay = Math.max(
1018
1121
  this.timeouts.outputSettle,
1019
1122
  this.submitPendingUntil > Date.now() ? this.submitPendingUntil - Date.now() + this.timeouts.outputSettle : 0
1020
1123
  );
1021
1124
  this.settleTimer = setTimeout(() => {
1022
1125
  this.settleTimer = null;
1126
+ if (settleEpoch !== this.responseEpoch) return;
1023
1127
  this.settledBuffer = this.recentOutputBuffer;
1024
1128
  this.evaluateSettled();
1025
1129
  }, delay);
1026
1130
  }
1131
+ armApprovalExitTimeout() {
1132
+ if (this.approvalExitTimeout) clearTimeout(this.approvalExitTimeout);
1133
+ this.approvalExitTimeout = setTimeout(() => {
1134
+ if (this.currentStatus !== "waiting_approval") return;
1135
+ const tail = this.recentOutputBuffer;
1136
+ const modal = this.runParseApproval(tail);
1137
+ const stillWaiting = this.runDetectStatus(tail) === "waiting_approval" || !!modal;
1138
+ if (stillWaiting) {
1139
+ this.activeModal = modal || this.activeModal || { message: "Approval required", buttons: ["Allow", "Deny"] };
1140
+ this.onStatusChange?.();
1141
+ this.armApprovalExitTimeout();
1142
+ return;
1143
+ }
1144
+ LOG.warn("CLI", `[${this.cliType}] Approval timeout \u2014 auto-clearing`);
1145
+ this.activeModal = null;
1146
+ this.lastApprovalResolvedAt = Date.now();
1147
+ this.setStatus("idle", "approval_timeout");
1148
+ this.onStatusChange?.();
1149
+ }, 6e4);
1150
+ }
1027
1151
  evaluateSettled() {
1152
+ if (this.submitPendingUntil > Date.now()) return;
1153
+ if (this.responseSettleIgnoreUntil > Date.now()) return;
1028
1154
  const tail = this.settledBuffer;
1029
- const scriptStatus = this.runDetectStatus(tail);
1155
+ const modal = this.runParseApproval(tail);
1156
+ const rawScriptStatus = this.runDetectStatus(tail);
1157
+ const scriptStatus = rawScriptStatus === "waiting_approval" || modal ? "waiting_approval" : rawScriptStatus;
1030
1158
  if (!scriptStatus) return;
1031
1159
  const prevStatus = this.currentStatus;
1032
1160
  if (scriptStatus === "waiting_approval") {
@@ -1034,19 +1162,9 @@ var init_provider_cli_adapter = __esm({
1034
1162
  if (!inCooldown) {
1035
1163
  this.isWaitingForResponse = true;
1036
1164
  this.setStatus("waiting_approval", "script_detect");
1037
- const modal = this.runParseApproval(tail);
1038
1165
  this.activeModal = modal || { message: "Approval required", buttons: ["Allow", "Deny"] };
1039
1166
  if (this.idleTimeout) clearTimeout(this.idleTimeout);
1040
- if (this.approvalExitTimeout) clearTimeout(this.approvalExitTimeout);
1041
- this.approvalExitTimeout = setTimeout(() => {
1042
- if (this.currentStatus === "waiting_approval") {
1043
- LOG.warn("CLI", `[${this.cliType}] Approval timeout \u2014 auto-clearing`);
1044
- this.activeModal = null;
1045
- this.lastApprovalResolvedAt = Date.now();
1046
- this.setStatus("idle", "approval_timeout");
1047
- this.onStatusChange?.();
1048
- }
1049
- }, 6e4);
1167
+ this.armApprovalExitTimeout();
1050
1168
  this.onStatusChange?.();
1051
1169
  return;
1052
1170
  }
@@ -1082,7 +1200,12 @@ var init_provider_cli_adapter = __esm({
1082
1200
  this.lastApprovalResolvedAt = Date.now();
1083
1201
  }
1084
1202
  if (this.isWaitingForResponse) {
1085
- this.finishResponse();
1203
+ if (this.idleTimeout) clearTimeout(this.idleTimeout);
1204
+ this.idleTimeout = setTimeout(() => {
1205
+ if (this.isWaitingForResponse && this.currentStatus !== "waiting_approval") {
1206
+ this.finishResponse();
1207
+ }
1208
+ }, this.timeouts.idleFinish);
1086
1209
  } else if (prevStatus !== "idle") {
1087
1210
  this.setStatus("idle", "script_detect");
1088
1211
  this.onStatusChange?.();
@@ -1090,6 +1213,9 @@ var init_provider_cli_adapter = __esm({
1090
1213
  }
1091
1214
  }
1092
1215
  finishResponse() {
1216
+ if (this.submitPendingUntil > Date.now()) return;
1217
+ if (this.responseSettleIgnoreUntil > Date.now()) return;
1218
+ this.commitCurrentTranscript();
1093
1219
  if (this.responseTimeout) {
1094
1220
  clearTimeout(this.responseTimeout);
1095
1221
  this.responseTimeout = null;
@@ -1102,12 +1228,67 @@ var init_provider_cli_adapter = __esm({
1102
1228
  clearTimeout(this.approvalExitTimeout);
1103
1229
  this.approvalExitTimeout = null;
1104
1230
  }
1231
+ if (this.submitRetryTimer) {
1232
+ clearTimeout(this.submitRetryTimer);
1233
+ this.submitRetryTimer = null;
1234
+ }
1105
1235
  this.responseBuffer = "";
1106
1236
  this.isWaitingForResponse = false;
1237
+ this.responseSettleIgnoreUntil = 0;
1238
+ this.submitRetryUsed = false;
1239
+ this.submitRetryPromptSnippet = "";
1240
+ this.currentTurnScope = null;
1107
1241
  this.activeModal = null;
1108
1242
  this.setStatus("idle", "response_finished");
1109
1243
  this.onStatusChange?.();
1110
1244
  }
1245
+ commitCurrentTranscript() {
1246
+ const baseMessages = [...this.committedMessages];
1247
+ const parsed = this.parseCurrentTranscript(baseMessages, "", this.currentTurnScope);
1248
+ if (parsed && Array.isArray(parsed.messages) && parsed.messages.length > 0) {
1249
+ const parsedMessages = parsed.messages.filter((m) => m && (m.role === "user" || m.role === "assistant")).map((m) => ({
1250
+ role: m.role,
1251
+ content: typeof m.content === "string" ? m.content : String(m.content || ""),
1252
+ timestamp: m.timestamp
1253
+ }));
1254
+ const latestAssistant = [...parsedMessages].reverse().find((m) => m.role === "assistant" && m.content.trim());
1255
+ if (latestAssistant) {
1256
+ LOG.info("CLI", `[${this.cliType}] commitCurrentTranscript parsed assistant len=${latestAssistant.content.length} scopePrompt=${JSON.stringify(this.currentTurnScope?.prompt || "").slice(0, 120)}`);
1257
+ const nextMessages = [...baseMessages];
1258
+ const last2 = nextMessages[nextMessages.length - 1];
1259
+ if (last2?.role === "assistant") {
1260
+ last2.content = latestAssistant.content;
1261
+ last2.timestamp = latestAssistant.timestamp || last2.timestamp;
1262
+ } else if (last2?.role === "user") {
1263
+ nextMessages.push({
1264
+ role: "assistant",
1265
+ content: latestAssistant.content,
1266
+ timestamp: latestAssistant.timestamp || Date.now()
1267
+ });
1268
+ } else {
1269
+ nextMessages.push({
1270
+ role: "assistant",
1271
+ content: latestAssistant.content,
1272
+ timestamp: latestAssistant.timestamp || Date.now()
1273
+ });
1274
+ }
1275
+ this.committedMessages = nextMessages;
1276
+ this.syncMessageViews();
1277
+ return;
1278
+ }
1279
+ }
1280
+ const fallback = String(this.responseBuffer || "").trim();
1281
+ LOG.info("CLI", `[${this.cliType}] commitCurrentTranscript fallback len=${fallback.length} scopePrompt=${JSON.stringify(this.currentTurnScope?.prompt || "").slice(0, 120)}`);
1282
+ if (!fallback) return;
1283
+ const last = baseMessages[baseMessages.length - 1];
1284
+ if (last?.role === "assistant") {
1285
+ last.content = fallback;
1286
+ } else {
1287
+ baseMessages.push({ role: "assistant", content: fallback, timestamp: Date.now() });
1288
+ }
1289
+ this.committedMessages = baseMessages;
1290
+ this.syncMessageViews();
1291
+ }
1111
1292
  // ─── Script Execution ──────────────────────────
1112
1293
  runDetectStatus(text) {
1113
1294
  if (!this.cliScripts?.detectStatus) return null;
@@ -1137,24 +1318,12 @@ var init_provider_cli_adapter = __esm({
1137
1318
  }
1138
1319
  // ─── Public API (CliAdapter) ───────────────────
1139
1320
  getStatus() {
1140
- const scriptResult = this.getScriptParsedStatus();
1141
- if (scriptResult) {
1142
- return {
1143
- status: this.currentStatus,
1144
- messages: (scriptResult.messages || []).map((m) => ({
1145
- role: m.role,
1146
- content: m.content,
1147
- timestamp: m.timestamp
1148
- })),
1149
- workingDir: this.workingDir,
1150
- activeModal: this.activeModal
1151
- };
1152
- }
1153
1321
  return {
1154
1322
  status: this.currentStatus,
1155
- messages: [...this.messages],
1323
+ messages: [...this.committedMessages],
1156
1324
  workingDir: this.workingDir,
1157
- activeModal: this.activeModal
1325
+ activeModal: this.activeModal,
1326
+ terminalHistory: this.terminalHistory
1158
1327
  };
1159
1328
  }
1160
1329
  /**
@@ -1162,31 +1331,32 @@ var init_provider_cli_adapter = __esm({
1162
1331
  * Called by command handler / dashboard for rich content rendering.
1163
1332
  */
1164
1333
  getScriptParsedStatus() {
1334
+ const messages = [...this.committedMessages];
1335
+ return {
1336
+ id: "cli_session",
1337
+ status: this.currentStatus,
1338
+ title: this.cliName,
1339
+ terminalHistory: this.terminalHistory,
1340
+ messages: messages.slice(-50).map((message, index) => ({
1341
+ id: `msg_${index}`,
1342
+ role: message.role,
1343
+ content: message.content,
1344
+ timestamp: message.timestamp,
1345
+ index,
1346
+ kind: "standard"
1347
+ })),
1348
+ activeModal: this.activeModal
1349
+ };
1350
+ }
1351
+ parseCurrentTranscript(baseMessages, partialResponse, scope) {
1165
1352
  if (!this.cliScripts?.parseOutput) return null;
1166
1353
  try {
1167
- const input = {
1168
- buffer: this.accumulatedBuffer,
1169
- rawBuffer: this.accumulatedRawBuffer,
1170
- recentBuffer: this.recentOutputBuffer,
1171
- screenText: this.terminalScreen.getText(),
1172
- messages: [...this.structuredMessages.length > 0 ? this.structuredMessages : this.messages],
1173
- partialResponse: this.responseBuffer
1174
- };
1175
- const result = this.cliScripts.parseOutput(input);
1176
- if (result && typeof result === "object") {
1177
- if (Array.isArray(result.messages)) {
1178
- this.structuredMessages = result.messages.map((m) => ({
1179
- role: m.role,
1180
- content: m.content,
1181
- timestamp: m.timestamp
1182
- }));
1183
- }
1184
- return result;
1185
- }
1354
+ const input = this.buildParseInput(baseMessages, partialResponse, scope);
1355
+ return this.cliScripts.parseOutput(input);
1186
1356
  } catch (e) {
1187
1357
  LOG.warn("CLI", `[${this.cliType}] parseOutput error: ${e.message}`);
1358
+ return null;
1188
1359
  }
1189
- return null;
1190
1360
  }
1191
1361
  /** Whether this adapter has CLI scripts loaded */
1192
1362
  hasCliScripts() {
@@ -1218,29 +1388,125 @@ ${data.message || ""}`.trim();
1218
1388
  }
1219
1389
  async sendMessage(text) {
1220
1390
  if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
1391
+ if (this.startupParseGate) {
1392
+ const deadline = Date.now() + 1e4;
1393
+ while (this.startupParseGate && Date.now() < deadline) {
1394
+ await new Promise((resolve8) => setTimeout(resolve8, 50));
1395
+ }
1396
+ }
1221
1397
  if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
1222
1398
  if (this.isWaitingForResponse) return;
1223
- this.messages.push({ role: "user", content: text, timestamp: Date.now() });
1224
- this.structuredMessages.push({ role: "user", content: text, timestamp: Date.now() });
1399
+ this.committedMessages.push({ role: "user", content: text, timestamp: Date.now() });
1400
+ this.syncMessageViews();
1225
1401
  this.isWaitingForResponse = true;
1226
1402
  this.responseBuffer = "";
1403
+ this.currentTurnScope = {
1404
+ prompt: text,
1405
+ startedAt: Date.now(),
1406
+ bufferStart: this.accumulatedBuffer.length,
1407
+ rawBufferStart: this.accumulatedRawBuffer.length,
1408
+ terminalHistoryStart: this.terminalHistory.length
1409
+ };
1410
+ LOG.info("CLI", `[${this.cliType}] sendMessage turn scope buffer=${this.currentTurnScope.bufferStart} raw=${this.currentTurnScope.rawBufferStart} terminal=${this.currentTurnScope.terminalHistoryStart} prompt=${JSON.stringify(text).slice(0, 120)}`);
1411
+ this.submitRetryUsed = false;
1412
+ this.submitRetryPromptSnippet = extractPromptRetrySnippet(text);
1413
+ const normalizedPromptSnippet = normalizePromptText(this.submitRetryPromptSnippet);
1414
+ if (this.submitRetryTimer) {
1415
+ clearTimeout(this.submitRetryTimer);
1416
+ this.submitRetryTimer = null;
1417
+ }
1418
+ const estimatedLines = estimatePromptDisplayLines(text);
1419
+ const submitDelayMs = this.sendDelayMs + Math.min(2e3, Math.max(0, estimatedLines - 1) * 350);
1420
+ const maxEchoWaitMs = submitDelayMs + Math.max(1500, Math.min(5e3, estimatedLines * 500));
1421
+ const retryDelayMs = Math.max(350, Math.min(1500, Math.max(this.sendDelayMs, submitDelayMs)));
1422
+ if (this.settleTimer) {
1423
+ clearTimeout(this.settleTimer);
1424
+ this.settleTimer = null;
1425
+ }
1426
+ this.responseEpoch += 1;
1427
+ this.responseSettleIgnoreUntil = Date.now() + submitDelayMs + this.timeouts.outputSettle + 250;
1227
1428
  this.setStatus("generating", "sendMessage");
1228
1429
  this.onStatusChange?.();
1229
- this.ptyProcess.write(text);
1430
+ const startResponseTimeout = () => {
1431
+ if (this.responseTimeout) clearTimeout(this.responseTimeout);
1432
+ this.responseTimeout = setTimeout(() => {
1433
+ if (this.isWaitingForResponse) this.finishResponse();
1434
+ }, this.timeouts.maxResponse);
1435
+ };
1230
1436
  const submit = () => {
1231
1437
  if (!this.ptyProcess) return;
1232
1438
  this.submitPendingUntil = 0;
1233
1439
  this.ptyProcess.write(this.sendKey);
1234
- this.responseTimeout = setTimeout(() => {
1235
- if (this.isWaitingForResponse) this.finishResponse();
1236
- }, this.timeouts.maxResponse);
1440
+ const retrySubmitIfStuck = (attempt) => {
1441
+ this.submitRetryTimer = null;
1442
+ if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
1443
+ if (this.currentStatus !== "generating") return;
1444
+ if ((this.responseBuffer || "").trim()) return;
1445
+ const screenText = this.terminalScreen.getText();
1446
+ if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
1447
+ 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;
1448
+ this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
1449
+ LOG.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt ${attempt})`);
1450
+ this.ptyProcess.write(this.sendKey);
1451
+ if (attempt >= 3) {
1452
+ this.submitRetryUsed = true;
1453
+ return;
1454
+ }
1455
+ this.submitRetryTimer = setTimeout(() => retrySubmitIfStuck(attempt + 1), retryDelayMs);
1456
+ };
1457
+ this.submitRetryTimer = setTimeout(() => retrySubmitIfStuck(1), retryDelayMs);
1458
+ startResponseTimeout();
1237
1459
  };
1238
- if (this.sendDelayMs > 0) {
1239
- this.submitPendingUntil = Date.now() + this.sendDelayMs;
1240
- setTimeout(submit, this.sendDelayMs);
1241
- } else {
1242
- submit();
1460
+ if (this.submitStrategy === "immediate") {
1461
+ this.submitPendingUntil = 0;
1462
+ this.ptyProcess.write(text + this.sendKey);
1463
+ this.submitRetryTimer = setTimeout(() => {
1464
+ this.submitRetryTimer = null;
1465
+ if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
1466
+ if (this.currentStatus !== "generating") return;
1467
+ if ((this.responseBuffer || "").trim()) return;
1468
+ const screenText = this.terminalScreen.getText();
1469
+ if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
1470
+ LOG.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt 1)`);
1471
+ this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
1472
+ this.ptyProcess.write(this.sendKey);
1473
+ this.submitRetryUsed = true;
1474
+ }, retryDelayMs);
1475
+ startResponseTimeout();
1476
+ return;
1243
1477
  }
1478
+ if (submitDelayMs > 0) {
1479
+ this.submitPendingUntil = Date.now() + submitDelayMs;
1480
+ }
1481
+ this.ptyProcess.write(text);
1482
+ const submitStartedAt = Date.now();
1483
+ let lastNormalizedScreen = "";
1484
+ let lastScreenChangeAt = submitStartedAt;
1485
+ const waitForEchoAndSubmit = () => {
1486
+ if (!this.ptyProcess) return;
1487
+ const now = Date.now();
1488
+ const elapsed = now - submitStartedAt;
1489
+ const screenText = this.terminalScreen.getText();
1490
+ const normalizedScreen = normalizePromptText(screenText);
1491
+ if (normalizedScreen !== lastNormalizedScreen) {
1492
+ lastNormalizedScreen = normalizedScreen;
1493
+ lastScreenChangeAt = now;
1494
+ }
1495
+ const echoVisible = !normalizedPromptSnippet || promptLikelyVisible(screenText, normalizedPromptSnippet);
1496
+ if (echoVisible) {
1497
+ const screenSettled = now - lastScreenChangeAt >= 500;
1498
+ if (elapsed >= submitDelayMs && screenSettled) {
1499
+ submit();
1500
+ return;
1501
+ }
1502
+ }
1503
+ if (elapsed >= maxEchoWaitMs) {
1504
+ submit();
1505
+ return;
1506
+ }
1507
+ setTimeout(waitForEchoAndSubmit, 50);
1508
+ };
1509
+ waitForEchoAndSubmit();
1244
1510
  }
1245
1511
  getPartialResponse() {
1246
1512
  if (!this.isWaitingForResponse) return "";
@@ -1258,6 +1524,10 @@ ${data.message || ""}`.trim();
1258
1524
  clearTimeout(this.approvalExitTimeout);
1259
1525
  this.approvalExitTimeout = null;
1260
1526
  }
1527
+ if (this.submitRetryTimer) {
1528
+ clearTimeout(this.submitRetryTimer);
1529
+ this.submitRetryTimer = null;
1530
+ }
1261
1531
  if (this.ptyProcess) {
1262
1532
  this.ptyProcess.write("");
1263
1533
  setTimeout(() => {
@@ -1275,10 +1545,14 @@ ${data.message || ""}`.trim();
1275
1545
  }
1276
1546
  }
1277
1547
  clearHistory() {
1278
- this.messages = [];
1279
- this.structuredMessages = [];
1548
+ this.committedMessages = [];
1549
+ this.syncMessageViews();
1280
1550
  this.accumulatedBuffer = "";
1281
1551
  this.accumulatedRawBuffer = "";
1552
+ this.terminalHistory = "";
1553
+ this.currentTurnScope = null;
1554
+ this.submitRetryUsed = false;
1555
+ this.submitRetryPromptSnippet = "";
1282
1556
  this.terminalScreen.reset();
1283
1557
  this.onStatusChange?.();
1284
1558
  }
@@ -1292,7 +1566,16 @@ ${data.message || ""}`.trim();
1292
1566
  this.ptyProcess?.write(data);
1293
1567
  }
1294
1568
  resolveModal(buttonIndex) {
1295
- if (!this.ptyProcess || this.currentStatus !== "waiting_approval") return;
1569
+ if (!this.ptyProcess || this.currentStatus !== "waiting_approval" && !this.activeModal) return;
1570
+ this.activeModal = null;
1571
+ this.lastApprovalResolvedAt = Date.now();
1572
+ this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
1573
+ if (this.approvalExitTimeout) {
1574
+ clearTimeout(this.approvalExitTimeout);
1575
+ this.approvalExitTimeout = null;
1576
+ }
1577
+ this.setStatus("generating", "approval_resolved");
1578
+ this.onStatusChange?.();
1296
1579
  if (buttonIndex in this.approvalKeys) {
1297
1580
  this.ptyProcess.write(this.approvalKeys[buttonIndex]);
1298
1581
  } else {
@@ -1321,9 +1604,12 @@ ${data.message || ""}`.trim();
1321
1604
  spawnAt: this.spawnAt,
1322
1605
  workingDir: this.workingDir,
1323
1606
  messages: this.messages.slice(-20),
1607
+ committedMessages: this.committedMessages.slice(-20),
1324
1608
  structuredMessages: this.structuredMessages.slice(-20),
1325
- messageCount: this.messages.length,
1609
+ messageCount: this.committedMessages.length,
1326
1610
  screenText: this.terminalScreen.getText().slice(-4e3),
1611
+ terminalHistory: this.terminalHistory.slice(-8e3),
1612
+ currentTurnScope: this.currentTurnScope,
1327
1613
  startupBuffer: this.startupBuffer.slice(-4e3),
1328
1614
  recentOutputBuffer: this.recentOutputBuffer.slice(-500),
1329
1615
  settledBuffer: this.settledBuffer.slice(-500),
@@ -1334,6 +1620,11 @@ ${data.message || ""}`.trim();
1334
1620
  isWaitingForResponse: this.isWaitingForResponse,
1335
1621
  activeModal: this.activeModal,
1336
1622
  lastApprovalResolvedAt: this.lastApprovalResolvedAt,
1623
+ sendDelayMs: this.sendDelayMs,
1624
+ sendKey: this.sendKey,
1625
+ submitStrategy: this.submitStrategy,
1626
+ submitPendingUntil: this.submitPendingUntil,
1627
+ responseSettleIgnoreUntil: this.responseSettleIgnoreUntil,
1337
1628
  resizeSuppressUntil: this.resizeSuppressUntil,
1338
1629
  hasCliScripts: this.hasCliScripts(),
1339
1630
  scriptNames: Object.keys(this.cliScripts).filter((k) => typeof this.cliScripts[k] === "function"),
@@ -3124,6 +3415,8 @@ var ChatHistoryWriter = class {
3124
3415
  lastSeenCounts = /* @__PURE__ */ new Map();
3125
3416
  /** Last seen message hash per agent (deduplication) */
3126
3417
  lastSeenHashes = /* @__PURE__ */ new Map();
3418
+ /** Last seen append-only terminal transcript per agent */
3419
+ lastSeenTerminal = /* @__PURE__ */ new Map();
3127
3420
  rotated = false;
3128
3421
  /**
3129
3422
  * Append new messages to history
@@ -3181,10 +3474,51 @@ var ChatHistoryWriter = class {
3181
3474
  } catch {
3182
3475
  }
3183
3476
  }
3477
+ appendTerminalHistory(agentType, terminalHistory, sessionTitle, instanceId) {
3478
+ const next = String(terminalHistory || "");
3479
+ if (!next.trim()) return;
3480
+ try {
3481
+ const dedupKey = instanceId ? `${agentType}:${instanceId}:terminal` : `${agentType}:terminal`;
3482
+ const prev = this.lastSeenTerminal.get(dedupKey) || "";
3483
+ if (prev === next) return;
3484
+ let delta = "";
3485
+ if (!prev) {
3486
+ delta = next;
3487
+ } else if (next.startsWith(prev)) {
3488
+ delta = next.slice(prev.length);
3489
+ } else if (prev.includes(next)) {
3490
+ this.lastSeenTerminal.set(dedupKey, next);
3491
+ return;
3492
+ } else {
3493
+ delta = `
3494
+
3495
+ [terminal snapshot reset ${(/* @__PURE__ */ new Date()).toISOString()} | ${sessionTitle || agentType}]
3496
+ ${next}`;
3497
+ }
3498
+ if (!delta) {
3499
+ this.lastSeenTerminal.set(dedupKey, next);
3500
+ return;
3501
+ }
3502
+ const dir = path4.join(HISTORY_DIR, this.sanitize(agentType));
3503
+ fs3.mkdirSync(dir, { recursive: true });
3504
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3505
+ const filePrefix = instanceId ? `${this.sanitize(instanceId)}_` : "";
3506
+ const filePath = path4.join(dir, `${filePrefix}${date}.terminal.log`);
3507
+ fs3.appendFileSync(filePath, delta, "utf-8");
3508
+ this.lastSeenTerminal.set(dedupKey, next);
3509
+ if (!this.rotated) {
3510
+ this.rotated = true;
3511
+ this.rotateOldFiles().catch(() => {
3512
+ });
3513
+ }
3514
+ } catch {
3515
+ }
3516
+ }
3184
3517
  /** Called when agent session is explicitly changed */
3185
3518
  onSessionChange(agentType) {
3186
3519
  this.lastSeenHashes.delete(agentType);
3187
3520
  this.lastSeenCounts.delete(agentType);
3521
+ this.lastSeenTerminal.delete(`${agentType}:terminal`);
3188
3522
  }
3189
3523
  /** Delete history files older than 30 days */
3190
3524
  async rotateOldFiles() {
@@ -3194,7 +3528,7 @@ var ChatHistoryWriter = class {
3194
3528
  const agentDirs = fs3.readdirSync(HISTORY_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
3195
3529
  for (const dir of agentDirs) {
3196
3530
  const dirPath = path4.join(HISTORY_DIR, dir.name);
3197
- const files = fs3.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
3531
+ const files = fs3.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl") || f.endsWith(".terminal.log"));
3198
3532
  for (const file of files) {
3199
3533
  const filePath = path4.join(dirPath, file);
3200
3534
  const stat = fs3.statSync(filePath);
@@ -4102,7 +4436,13 @@ async function handleReadChat(h, args) {
4102
4436
  _log(`${provider.category} adapter: ${adapter.cliType}`);
4103
4437
  const status = adapter.getStatus?.();
4104
4438
  if (status) {
4105
- return { success: true, messages: status.messages || [], status: status.status, activeModal: status.activeModal };
4439
+ return {
4440
+ success: true,
4441
+ messages: status.messages || [],
4442
+ status: status.status,
4443
+ activeModal: status.activeModal,
4444
+ terminalHistory: status.terminalHistory || ""
4445
+ };
4106
4446
  }
4107
4447
  }
4108
4448
  return { success: false, error: `${provider.category} adapter not found` };
@@ -7982,31 +8322,12 @@ var CliProviderInstance = class {
7982
8322
  async onTick() {
7983
8323
  }
7984
8324
  getState() {
7985
- const rawStatus = this.adapter.getStatus();
7986
- const parsedStatus = this.adapter.getScriptParsedStatus();
7987
- const adapterStatus = parsedStatus ? {
7988
- ...rawStatus,
7989
- messages: parsedStatus.messages || rawStatus.messages,
7990
- activeModal: parsedStatus.activeModal || rawStatus.activeModal
7991
- } : rawStatus;
8325
+ const adapterStatus = this.adapter.getStatus();
7992
8326
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
7993
8327
  const recentMessages = adapterStatus.messages.slice(-50).map((m) => {
7994
8328
  const content = typeof m.content === "string" && m.content.length > 8e3 ? m.content.slice(0, 8e3) + "\n... (truncated)" : m.content;
7995
8329
  return { ...m, content };
7996
8330
  });
7997
- const partial = this.adapter.getPartialResponse();
7998
- const shouldAppendRawPartial = !parsedStatus;
7999
- if (shouldAppendRawPartial && adapterStatus.status === "generating" && partial) {
8000
- const cleaned = partial.trim();
8001
- if (cleaned && cleaned !== "(generating...)") {
8002
- recentMessages.push({
8003
- role: "assistant",
8004
- content: (cleaned.length > 8e3 ? cleaned.slice(0, 8e3) + "..." : cleaned) + "...",
8005
- timestamp: Date.now(),
8006
- meta: { streaming: true }
8007
- });
8008
- }
8009
- }
8010
8331
  if (recentMessages.length > 0) {
8011
8332
  const dirName2 = this.workingDir.split("/").filter(Boolean).pop() || "session";
8012
8333
  this.historyWriter.appendNewMessages(
@@ -8016,6 +8337,14 @@ var CliProviderInstance = class {
8016
8337
  this.instanceId
8017
8338
  );
8018
8339
  }
8340
+ if (adapterStatus.terminalHistory?.trim()) {
8341
+ this.historyWriter.appendTerminalHistory(
8342
+ this.type,
8343
+ adapterStatus.terminalHistory,
8344
+ `${this.provider.name} \xB7 ${dirName}`,
8345
+ this.instanceId
8346
+ );
8347
+ }
8019
8348
  return {
8020
8349
  type: this.type,
8021
8350
  name: this.provider.name,
@@ -8028,6 +8357,7 @@ var CliProviderInstance = class {
8028
8357
  status: adapterStatus.status,
8029
8358
  messages: recentMessages,
8030
8359
  activeModal: adapterStatus.activeModal,
8360
+ terminalHistory: adapterStatus.terminalHistory,
8031
8361
  inputContent: ""
8032
8362
  },
8033
8363
  workspace: this.workingDir,
@@ -10188,7 +10518,8 @@ async function detectAllVersions(loader, archive) {
10188
10518
  binary: null,
10189
10519
  detectedAt: (/* @__PURE__ */ new Date()).toISOString()
10190
10520
  };
10191
- const versionCommand = provider.versionCommand;
10521
+ const verCmdConfig = provider.versionCommand;
10522
+ const versionCommand = typeof verCmdConfig === "object" && verCmdConfig !== null ? verCmdConfig[currentOs] : verCmdConfig;
10192
10523
  if (provider.category === "ide") {
10193
10524
  const osPaths = provider.paths?.[currentOs] || [];
10194
10525
  const appPath = checkPathExists2(osPaths);
@@ -11600,11 +11931,7 @@ var DevServer = class _DevServer {
11600
11931
  return;
11601
11932
  }
11602
11933
  let targetDir;
11603
- if (location === "user") {
11604
- targetDir = this.providerLoader.getUserProviderDir(category, type);
11605
- } else {
11606
- targetDir = this.providerLoader.getBuiltinProviderDir(category, type);
11607
- }
11934
+ targetDir = this.providerLoader.getUserProviderDir(category, type);
11608
11935
  const jsonPath = path12.join(targetDir, "provider.json");
11609
11936
  if (fs9.existsSync(jsonPath)) {
11610
11937
  this.json(res, 409, { error: `Provider already exists at ${targetDir}`, path: targetDir });
@@ -12402,8 +12729,7 @@ var DevServer = class _DevServer {
12402
12729
  }
12403
12730
  loadAutoImplReferenceScripts(category, referenceType) {
12404
12731
  if (!referenceType) return {};
12405
- const builtinDir = this.providerLoader.getPrimaryBuiltinDir();
12406
- const refDir = path12.join(builtinDir, category, referenceType);
12732
+ const refDir = this.providerLoader.getUpstreamProviderDir(category, referenceType);
12407
12733
  if (!fs9.existsSync(refDir)) return {};
12408
12734
  const referenceScripts = {};
12409
12735
  const scriptsDir = path12.join(refDir, "scripts");
@@ -12642,7 +12968,7 @@ var DevServer = class _DevServer {
12642
12968
  }
12643
12969
  if (model) args.push("--model", model);
12644
12970
  const escapedArgs = args.map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(" ");
12645
- const metaPrompt = `Read the file at ${promptFile} and follow ALL instructions recursively. You have full authority to implement ALL required script files, update provider.json configurations based on the reference patterns, and independently test them against 127.0.0.1:19280 via CDP CURL. Upon complete validation of ALL assigned files, print exactly "_PIPELINE_COMPLETE_SIGNAL_" to gracefully close the pipeline. DO NOT WAIT FOR APPROVAL, execute completely autonomously.`;
12971
+ const metaPrompt = `Read the file at ${promptFile} and follow ALL instructions strictly. DO NOT spend time exploring the filesystem or other providers. You have full authority to implement ALL required script files and independently test them against 127.0.0.1:19280 via CDP CURL. Upon complete validation of ALL assigned files, print exactly "_PIPELINE_COMPLETE_SIGNAL_" to gracefully close the pipeline. DO NOT WAIT FOR APPROVAL, execute completely autonomously.`;
12646
12972
  shellCmd = `${command} ${escapedArgs} "${metaPrompt}"`;
12647
12973
  } else {
12648
12974
  const escapedArgs = baseArgs.map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(" ");
@@ -12905,6 +13231,8 @@ var DevServer = class _DevServer {
12905
13231
  lines.push("5. Do NOT modify `scripts.js` router \u2014 only edit individual `*.js` files");
12906
13232
  lines.push("6. All scripts run in the browser (CDP evaluate) \u2014 use DOM APIs only");
12907
13233
  lines.push('7. **Cross-Platform Compatibility**: If you use ARIA labels that contain keyboard shortcuts (e.g., `Cascade (\u2318L)`), you MUST use substring matches (`aria-label*="Cascade"`) or handle both macOS (`\u2318`, `Cmd`) and Windows (`Ctrl`) so the script does not break on other operating systems.');
13234
+ lines.push("8. **CRITICAL: DO NOT explore the filesystem or read other providers.** The reference implementation pattern is already provided below. Do not run `find`, `rg`, or `cat` on upstream providers. Doing so wastes context tokens and will crash the agent session. Focus entirely on modifying the target files.");
13235
+ lines.push("9. Do NOT delete any files. Implement the logic by replacing the empty stubs.");
12908
13236
  lines.push("");
12909
13237
  lines.push("## Required Return Format");
12910
13238
  lines.push("| Function | Return JSON |");