@adhdev/daemon-core 0.6.56 → 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.d.ts CHANGED
@@ -2664,6 +2664,11 @@ declare class ProviderCliAdapter implements CliAdapter {
2664
2664
  private settleTimer;
2665
2665
  private settledBuffer;
2666
2666
  private submitPendingUntil;
2667
+ private responseSettleIgnoreUntil;
2668
+ private responseEpoch;
2669
+ private submitRetryTimer;
2670
+ private submitRetryUsed;
2671
+ private submitRetryPromptSnippet;
2667
2672
  private resizeSuppressUntil;
2668
2673
  private statusHistory;
2669
2674
  private cliScripts;
@@ -2689,6 +2694,7 @@ declare class ProviderCliAdapter implements CliAdapter {
2689
2694
  spawn(): Promise<void>;
2690
2695
  private handleOutput;
2691
2696
  private scheduleSettle;
2697
+ private armApprovalExitTimeout;
2692
2698
  private evaluateSettled;
2693
2699
  private finishResponse;
2694
2700
  private runDetectStatus;
package/dist/index.js CHANGED
@@ -718,6 +718,38 @@ 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
+ }
721
753
  function parsePatternEntry(x) {
722
754
  if (x instanceof RegExp) return x;
723
755
  if (x && typeof x === "object" && typeof x.source === "string") {
@@ -837,6 +869,11 @@ var init_provider_cli_adapter = __esm({
837
869
  settleTimer = null;
838
870
  settledBuffer = "";
839
871
  submitPendingUntil = 0;
872
+ responseSettleIgnoreUntil = 0;
873
+ responseEpoch = 0;
874
+ submitRetryTimer = null;
875
+ submitRetryUsed = false;
876
+ submitRetryPromptSnippet = "";
840
877
  // Resize redraw suppression
841
878
  resizeSuppressUntil = 0;
842
879
  // Debug: status transition history
@@ -960,7 +997,7 @@ var init_provider_cli_adapter = __esm({
960
997
  this.startupParseGate = true;
961
998
  this.startupBuffer = "";
962
999
  this.terminalScreen.reset(40, 120);
963
- this.ready = true;
1000
+ this.ready = false;
964
1001
  this.setStatus("idle", "pty_ready");
965
1002
  this.onStatusChange?.();
966
1003
  }
@@ -1005,7 +1042,9 @@ var init_provider_cli_adapter = __esm({
1005
1042
  const isReady = scriptStatus === "idle" || elapsed > 8e3 || bufCap;
1006
1043
  if (isReady) {
1007
1044
  this.startupParseGate = false;
1045
+ this.ready = true;
1008
1046
  LOG.info("CLI", `[${this.cliType}] Startup gate end (${elapsed}ms, scriptStatus=${scriptStatus})`);
1047
+ this.onStatusChange?.();
1009
1048
  } else {
1010
1049
  return;
1011
1050
  }
@@ -1014,19 +1053,45 @@ var init_provider_cli_adapter = __esm({
1014
1053
  }
1015
1054
  scheduleSettle() {
1016
1055
  if (this.settleTimer) clearTimeout(this.settleTimer);
1056
+ const settleEpoch = this.responseEpoch;
1017
1057
  const delay = Math.max(
1018
1058
  this.timeouts.outputSettle,
1019
1059
  this.submitPendingUntil > Date.now() ? this.submitPendingUntil - Date.now() + this.timeouts.outputSettle : 0
1020
1060
  );
1021
1061
  this.settleTimer = setTimeout(() => {
1022
1062
  this.settleTimer = null;
1063
+ if (settleEpoch !== this.responseEpoch) return;
1023
1064
  this.settledBuffer = this.recentOutputBuffer;
1024
1065
  this.evaluateSettled();
1025
1066
  }, delay);
1026
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);
1087
+ }
1027
1088
  evaluateSettled() {
1089
+ if (this.submitPendingUntil > Date.now()) return;
1090
+ if (this.responseSettleIgnoreUntil > Date.now()) return;
1028
1091
  const tail = this.settledBuffer;
1029
- 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;
1030
1095
  if (!scriptStatus) return;
1031
1096
  const prevStatus = this.currentStatus;
1032
1097
  if (scriptStatus === "waiting_approval") {
@@ -1034,19 +1099,9 @@ var init_provider_cli_adapter = __esm({
1034
1099
  if (!inCooldown) {
1035
1100
  this.isWaitingForResponse = true;
1036
1101
  this.setStatus("waiting_approval", "script_detect");
1037
- const modal = this.runParseApproval(tail);
1038
1102
  this.activeModal = modal || { message: "Approval required", buttons: ["Allow", "Deny"] };
1039
1103
  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);
1104
+ this.armApprovalExitTimeout();
1050
1105
  this.onStatusChange?.();
1051
1106
  return;
1052
1107
  }
@@ -1082,7 +1137,12 @@ var init_provider_cli_adapter = __esm({
1082
1137
  this.lastApprovalResolvedAt = Date.now();
1083
1138
  }
1084
1139
  if (this.isWaitingForResponse) {
1085
- 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);
1086
1146
  } else if (prevStatus !== "idle") {
1087
1147
  this.setStatus("idle", "script_detect");
1088
1148
  this.onStatusChange?.();
@@ -1090,6 +1150,8 @@ var init_provider_cli_adapter = __esm({
1090
1150
  }
1091
1151
  }
1092
1152
  finishResponse() {
1153
+ if (this.submitPendingUntil > Date.now()) return;
1154
+ if (this.responseSettleIgnoreUntil > Date.now()) return;
1093
1155
  if (this.responseTimeout) {
1094
1156
  clearTimeout(this.responseTimeout);
1095
1157
  this.responseTimeout = null;
@@ -1102,8 +1164,15 @@ var init_provider_cli_adapter = __esm({
1102
1164
  clearTimeout(this.approvalExitTimeout);
1103
1165
  this.approvalExitTimeout = null;
1104
1166
  }
1167
+ if (this.submitRetryTimer) {
1168
+ clearTimeout(this.submitRetryTimer);
1169
+ this.submitRetryTimer = null;
1170
+ }
1105
1171
  this.responseBuffer = "";
1106
1172
  this.isWaitingForResponse = false;
1173
+ this.responseSettleIgnoreUntil = 0;
1174
+ this.submitRetryUsed = false;
1175
+ this.submitRetryPromptSnippet = "";
1107
1176
  this.activeModal = null;
1108
1177
  this.setStatus("idle", "response_finished");
1109
1178
  this.onStatusChange?.();
@@ -1218,29 +1287,95 @@ ${data.message || ""}`.trim();
1218
1287
  }
1219
1288
  async sendMessage(text) {
1220
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
+ }
1221
1296
  if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
1222
1297
  if (this.isWaitingForResponse) return;
1223
1298
  this.messages.push({ role: "user", content: text, timestamp: Date.now() });
1224
1299
  this.structuredMessages.push({ role: "user", content: text, timestamp: Date.now() });
1225
1300
  this.isWaitingForResponse = true;
1226
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;
1227
1319
  this.setStatus("generating", "sendMessage");
1228
1320
  this.onStatusChange?.();
1321
+ if (submitDelayMs > 0) {
1322
+ this.submitPendingUntil = Date.now() + submitDelayMs;
1323
+ }
1229
1324
  this.ptyProcess.write(text);
1230
1325
  const submit = () => {
1231
1326
  if (!this.ptyProcess) return;
1232
1327
  this.submitPendingUntil = 0;
1233
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);
1234
1347
  this.responseTimeout = setTimeout(() => {
1235
1348
  if (this.isWaitingForResponse) this.finishResponse();
1236
1349
  }, this.timeouts.maxResponse);
1237
1350
  };
1238
- if (this.sendDelayMs > 0) {
1239
- this.submitPendingUntil = Date.now() + this.sendDelayMs;
1240
- setTimeout(submit, this.sendDelayMs);
1241
- } else {
1242
- submit();
1243
- }
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();
1244
1379
  }
1245
1380
  getPartialResponse() {
1246
1381
  if (!this.isWaitingForResponse) return "";
@@ -1258,6 +1393,10 @@ ${data.message || ""}`.trim();
1258
1393
  clearTimeout(this.approvalExitTimeout);
1259
1394
  this.approvalExitTimeout = null;
1260
1395
  }
1396
+ if (this.submitRetryTimer) {
1397
+ clearTimeout(this.submitRetryTimer);
1398
+ this.submitRetryTimer = null;
1399
+ }
1261
1400
  if (this.ptyProcess) {
1262
1401
  this.ptyProcess.write("");
1263
1402
  setTimeout(() => {
@@ -1279,6 +1418,8 @@ ${data.message || ""}`.trim();
1279
1418
  this.structuredMessages = [];
1280
1419
  this.accumulatedBuffer = "";
1281
1420
  this.accumulatedRawBuffer = "";
1421
+ this.submitRetryUsed = false;
1422
+ this.submitRetryPromptSnippet = "";
1282
1423
  this.terminalScreen.reset();
1283
1424
  this.onStatusChange?.();
1284
1425
  }
@@ -1292,7 +1433,16 @@ ${data.message || ""}`.trim();
1292
1433
  this.ptyProcess?.write(data);
1293
1434
  }
1294
1435
  resolveModal(buttonIndex) {
1295
- 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?.();
1296
1446
  if (buttonIndex in this.approvalKeys) {
1297
1447
  this.ptyProcess.write(this.approvalKeys[buttonIndex]);
1298
1448
  } else {
@@ -1334,6 +1484,10 @@ ${data.message || ""}`.trim();
1334
1484
  isWaitingForResponse: this.isWaitingForResponse,
1335
1485
  activeModal: this.activeModal,
1336
1486
  lastApprovalResolvedAt: this.lastApprovalResolvedAt,
1487
+ sendDelayMs: this.sendDelayMs,
1488
+ sendKey: this.sendKey,
1489
+ submitPendingUntil: this.submitPendingUntil,
1490
+ responseSettleIgnoreUntil: this.responseSettleIgnoreUntil,
1337
1491
  resizeSuppressUntil: this.resizeSuppressUntil,
1338
1492
  hasCliScripts: this.hasCliScripts(),
1339
1493
  scriptNames: Object.keys(this.cliScripts).filter((k) => typeof this.cliScripts[k] === "function"),