@adhdev/daemon-core 0.9.82-rc.13 → 0.9.82-rc.130

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 (82) hide show
  1. package/dist/chat/subscription-updates.d.ts +1 -0
  2. package/dist/cli-adapter-types.d.ts +4 -1
  3. package/dist/cli-adapters/provider-cli-adapter.d.ts +25 -1
  4. package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
  5. package/dist/cli-adapters/provider-cli-shared.d.ts +14 -0
  6. package/dist/commands/router.d.ts +22 -0
  7. package/dist/config/chat-history.d.ts +4 -0
  8. package/dist/config/mesh-config.d.ts +68 -1
  9. package/dist/git/git-commands.d.ts +5 -1
  10. package/dist/index.d.ts +15 -5
  11. package/dist/index.js +7461 -1380
  12. package/dist/index.js.map +1 -1
  13. package/dist/index.mjs +7417 -1364
  14. package/dist/index.mjs.map +1 -1
  15. package/dist/installer.d.ts +1 -4
  16. package/dist/launch.d.ts +1 -1
  17. package/dist/logging/async-batch-writer.d.ts +10 -0
  18. package/dist/mesh/beads-db.d.ts +18 -0
  19. package/dist/mesh/mesh-active-work.d.ts +73 -0
  20. package/dist/mesh/mesh-events.d.ts +54 -5
  21. package/dist/mesh/mesh-fast-forward.d.ts +39 -0
  22. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  23. package/dist/mesh/mesh-ledger.d.ts +38 -1
  24. package/dist/mesh/mesh-refine-status.d.ts +27 -0
  25. package/dist/mesh/mesh-work-queue.d.ts +27 -5
  26. package/dist/mesh/preview-freshness.d.ts +18 -0
  27. package/dist/mesh/refine-config.d.ts +193 -0
  28. package/dist/mesh/worktree-bootstrap-config.d.ts +115 -0
  29. package/dist/providers/chat-message-normalization.d.ts +1 -0
  30. package/dist/providers/cli-provider-instance.d.ts +6 -1
  31. package/dist/repo-mesh-types.d.ts +62 -0
  32. package/dist/status/reporter.d.ts +2 -0
  33. package/package.json +3 -1
  34. package/src/boot/daemon-lifecycle.ts +1 -0
  35. package/src/chat/subscription-updates.ts +5 -1
  36. package/src/cli-adapter-types.ts +2 -1
  37. package/src/cli-adapters/provider-cli-adapter.ts +473 -18
  38. package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
  39. package/src/cli-adapters/provider-cli-parse.ts +4 -0
  40. package/src/cli-adapters/provider-cli-runtime.ts +3 -1
  41. package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
  42. package/src/cli-adapters/provider-cli-shared.ts +32 -10
  43. package/src/commands/chat-commands.ts +960 -40
  44. package/src/commands/cli-manager.ts +138 -2
  45. package/src/commands/handler.ts +8 -1
  46. package/src/commands/mesh-coordinator.ts +13 -143
  47. package/src/commands/router.ts +3238 -423
  48. package/src/config/chat-history.ts +37 -9
  49. package/src/config/mesh-config.ts +249 -2
  50. package/src/config/recent-activity.ts +8 -2
  51. package/src/daemon/dev-cli-debug.ts +10 -1
  52. package/src/detection/ide-detector.ts +26 -16
  53. package/src/git/git-commands.ts +17 -5
  54. package/src/index.ts +41 -4
  55. package/src/installer.d.ts +1 -1
  56. package/src/installer.ts +8 -6
  57. package/src/launch.d.ts +1 -1
  58. package/src/launch.ts +37 -28
  59. package/src/logging/async-batch-writer.ts +55 -0
  60. package/src/logging/logger.ts +2 -1
  61. package/src/mesh/beads-db.ts +176 -0
  62. package/src/mesh/coordinator-prompt.ts +31 -8
  63. package/src/mesh/mesh-active-work.ts +295 -0
  64. package/src/mesh/mesh-events.ts +595 -48
  65. package/src/mesh/mesh-fast-forward.ts +430 -0
  66. package/src/mesh/mesh-host-ownership.ts +73 -0
  67. package/src/mesh/mesh-ledger.ts +138 -1
  68. package/src/mesh/mesh-refine-status.ts +145 -0
  69. package/src/mesh/mesh-work-queue.ts +199 -137
  70. package/src/mesh/preview-freshness.ts +118 -0
  71. package/src/mesh/refine-config.ts +366 -0
  72. package/src/mesh/worktree-bootstrap-config.ts +234 -0
  73. package/src/providers/approval-utils.ts +12 -5
  74. package/src/providers/chat-message-normalization.ts +7 -12
  75. package/src/providers/cli-provider-instance.ts +289 -36
  76. package/src/providers/ide-provider-instance.ts +17 -3
  77. package/src/providers/provider-loader.ts +10 -4
  78. package/src/providers/read-chat-contract.ts +1 -1
  79. package/src/providers/version-archive.ts +38 -20
  80. package/src/repo-mesh-types.ts +67 -0
  81. package/src/status/reporter.ts +15 -0
  82. package/src/system/host-memory.ts +29 -12
@@ -110,6 +110,14 @@ interface SendMessageCompletion {
110
110
  rejectOnce: (error: unknown) => void;
111
111
  }
112
112
 
113
+ interface PendingOutboundMessage {
114
+ id: string;
115
+ role: 'user';
116
+ content: string;
117
+ queuedAt: number;
118
+ source: 'sendMessage';
119
+ }
120
+
113
121
  export function appendBoundedText(current: string, chunk: string, maxChars: number): string {
114
122
  if (!chunk) return current.length <= maxChars ? current : current.slice(-maxChars);
115
123
  if (maxChars <= 0) return '';
@@ -137,6 +145,9 @@ export class ProviderCliAdapter implements CliAdapter {
137
145
  private isWaitingForResponse = false;
138
146
  private activeModal: { message: string; buttons: string[] } | null = null;
139
147
  private parseErrorMessage: string | null = null;
148
+ private providerSessionId: string | null = null;
149
+ private providerErrorMessage: string | null = null;
150
+ private providerErrorReason: string | null = null;
140
151
  private responseTimeout: NodeJS.Timeout | null = null;
141
152
  private idleTimeout: NodeJS.Timeout | null = null;
142
153
  private ready = false;
@@ -186,6 +197,11 @@ export class ProviderCliAdapter implements CliAdapter {
186
197
  private idleFinishCandidate: IdleFinishCandidate | null = null;
187
198
  private finishRetryTimer: NodeJS.Timeout | null = null;
188
199
  private finishRetryCount = 0;
200
+ private pendingOutboundQueue: PendingOutboundMessage[] = [];
201
+ private pendingOutboundFlushTimer: NodeJS.Timeout | null = null;
202
+ private pendingOutboundFlushInFlight = false;
203
+ private providerErrorRetryTimer: NodeJS.Timeout | null = null;
204
+ private providerErrorRetryKey = '';
189
205
 
190
206
  // Resize redraw suppression
191
207
  private resizeSuppressUntil: number = 0;
@@ -761,6 +777,17 @@ export class ProviderCliAdapter implements CliAdapter {
761
777
  if (stableMs < 2000) return;
762
778
 
763
779
  const startupModal = this.runParseApproval(this.recentOutputBuffer);
780
+ const startupStatus = this.runDetectStatus(screenText || this.recentOutputBuffer);
781
+ if (!startupModal && startupStatus !== 'idle') {
782
+ this.recordTrace('startup_settle_deferred', {
783
+ trigger,
784
+ startupStatus,
785
+ stableMs,
786
+ screenText: summarizeCliTraceText(screenText, 500),
787
+ });
788
+ this.scheduleStartupSettleCheck();
789
+ return;
790
+ }
764
791
  this.startupParseGate = false;
765
792
  if (this.startupSettleTimer) {
766
793
  clearTimeout(this.startupSettleTimer);
@@ -934,6 +961,8 @@ export class ProviderCliAdapter implements CliAdapter {
934
961
  if (this.pendingScriptStatusTimer) { clearTimeout(this.pendingScriptStatusTimer); this.pendingScriptStatusTimer = null; }
935
962
  if (this.pendingOutputParseTimer) { clearTimeout(this.pendingOutputParseTimer); this.pendingOutputParseTimer = null; }
936
963
  if (this.ptyOutputFlushTimer) { clearTimeout(this.ptyOutputFlushTimer); this.ptyOutputFlushTimer = null; }
964
+ if (this.providerErrorRetryTimer) { clearTimeout(this.providerErrorRetryTimer); this.providerErrorRetryTimer = null; }
965
+ this.providerErrorRetryKey = '';
937
966
  }
938
967
 
939
968
  private clearStaleIdleResponseGuard(reason: string): boolean {
@@ -956,6 +985,38 @@ export class ProviderCliAdapter implements CliAdapter {
956
985
  return true;
957
986
  }
958
987
 
988
+ private clearParsedIdleResponseGuard(reason: string, parsedStatus: any): boolean {
989
+ const parsedRawStatus = typeof parsedStatus?.status === 'string' ? parsedStatus.status.trim() : '';
990
+ const parsedModal = parsedStatus?.activeModal ?? parsedStatus?.modal ?? null;
991
+ const blockingModal = this.activeModal || this.runParseApproval(this.recentOutputBuffer);
992
+ if (
993
+ !this.isWaitingForResponse
994
+ || parsedRawStatus !== 'idle'
995
+ || !!parsedModal
996
+ || !!blockingModal
997
+ || !this.parsedStatusHasFinalAssistantMessage(parsedStatus)
998
+ ) {
999
+ return false;
1000
+ }
1001
+ this.clearAllTimers();
1002
+ this.clearIdleFinishCandidate(reason);
1003
+ this.responseBuffer = '';
1004
+ this.isWaitingForResponse = false;
1005
+ this.responseSettleIgnoreUntil = 0;
1006
+ this.submitRetryUsed = false;
1007
+ this.submitRetryPromptSnippet = '';
1008
+ this.finishRetryCount = 0;
1009
+ this.currentTurnScope = null;
1010
+ this.activeModal = null;
1011
+ this.setStatus('idle', reason);
1012
+ this.recordTrace('parsed_idle_response_cleared', {
1013
+ reason,
1014
+ parsedStatus: parsedRawStatus,
1015
+ parsedMessageCount: Array.isArray(parsedStatus?.messages) ? parsedStatus.messages.length : 0,
1016
+ });
1017
+ return true;
1018
+ }
1019
+
959
1020
  private hasMeaningfulResponseBuffer(promptSnippet: string): boolean {
960
1021
  const raw = String(this.responseBuffer || '').trim();
961
1022
  if (!raw) return false;
@@ -1097,6 +1158,11 @@ export class ProviderCliAdapter implements CliAdapter {
1097
1158
  && !(parsedStatus === 'idle' && !!lastParsedAssistant);
1098
1159
 
1099
1160
  if (shouldHoldGenerating) { this.applyHoldGenerating(ctx, recentInteractiveActivity); return; }
1161
+ if (status === 'error') {
1162
+ if (this.maybeScheduleProviderErrorRetry(ctx, session)) return;
1163
+ this.applyError(ctx, session);
1164
+ return;
1165
+ }
1100
1166
  if (status === 'waiting_approval') { this.applyWaitingApproval(ctx); return; }
1101
1167
  if (status === 'generating') { this.applyGenerating(ctx); return; }
1102
1168
  if (status === 'idle') { this.applyIdle(ctx, now); }
@@ -1240,6 +1306,107 @@ export class ProviderCliAdapter implements CliAdapter {
1240
1306
  this.onStatusChange?.();
1241
1307
  }
1242
1308
 
1309
+ private applyError(ctx: SettledEvalContext, session: ParsedSession): void {
1310
+ this.clearIdleFinishCandidate('provider_error');
1311
+ if (this.responseTimeout) { clearTimeout(this.responseTimeout); this.responseTimeout = null; }
1312
+ if (this.idleTimeout) { clearTimeout(this.idleTimeout); this.idleTimeout = null; }
1313
+ if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
1314
+ this.isWaitingForResponse = false;
1315
+ this.responseSettleIgnoreUntil = 0;
1316
+ this.submitRetryUsed = false;
1317
+ this.submitRetryPromptSnippet = '';
1318
+ this.finishRetryCount = 0;
1319
+ this.currentTurnScope = null;
1320
+ this.activeModal = null;
1321
+ this.providerErrorMessage = typeof session.errorMessage === 'string' && session.errorMessage.trim()
1322
+ ? session.errorMessage.trim()
1323
+ : 'Provider reported an error';
1324
+ this.providerErrorReason = typeof session.errorReason === 'string' && session.errorReason.trim()
1325
+ ? session.errorReason.trim()
1326
+ : 'provider_error';
1327
+ this.setStatus('error', this.providerErrorReason);
1328
+ this.recordTrace('provider_error', {
1329
+ errorMessage: this.providerErrorMessage,
1330
+ errorReason: this.providerErrorReason,
1331
+ parsedStatus: ctx.parsedStatus || ctx.status,
1332
+ messageCount: ctx.parsedMessages.length,
1333
+ ...buildCliTraceParseSnapshot({
1334
+ accumulatedBuffer: this.accumulatedBuffer,
1335
+ accumulatedRawBuffer: this.accumulatedRawBuffer,
1336
+ responseBuffer: this.responseBuffer,
1337
+ partialResponse: this.responseBuffer,
1338
+ scope: this.currentTurnScope,
1339
+ }),
1340
+ });
1341
+ this.onStatusChange?.();
1342
+ }
1343
+
1344
+ private maybeScheduleProviderErrorRetry(ctx: SettledEvalContext, session: ParsedSession): boolean {
1345
+ const retryPrompt = typeof (session as any).retryPrompt === 'string'
1346
+ ? String((session as any).retryPrompt).trim()
1347
+ : '';
1348
+ const retryDelayMs = typeof (session as any).retryDelayMs === 'number'
1349
+ ? Number((session as any).retryDelayMs)
1350
+ : NaN;
1351
+ if (!retryPrompt || !Number.isFinite(retryDelayMs) || retryDelayMs < 0) return false;
1352
+ if (!this.ptyProcess) return false;
1353
+
1354
+ const retryAttempt = typeof (session as any).retryAttempt === 'number'
1355
+ ? Number((session as any).retryAttempt)
1356
+ : 0;
1357
+ const retryMaxAttempts = typeof (session as any).retryMaxAttempts === 'number'
1358
+ ? Number((session as any).retryMaxAttempts)
1359
+ : 0;
1360
+ const errorReason = typeof session.errorReason === 'string' && session.errorReason.trim()
1361
+ ? session.errorReason.trim()
1362
+ : 'provider_error';
1363
+ const retryKey = `${errorReason}:${retryAttempt}:${retryPrompt}`;
1364
+ if (this.providerErrorRetryTimer && this.providerErrorRetryKey === retryKey) return true;
1365
+
1366
+ if (this.providerErrorRetryTimer) clearTimeout(this.providerErrorRetryTimer);
1367
+ this.providerErrorRetryKey = retryKey;
1368
+ this.clearIdleFinishCandidate('provider_error_retry');
1369
+ if (this.idleTimeout) { clearTimeout(this.idleTimeout); this.idleTimeout = null; }
1370
+ if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
1371
+ this.providerErrorMessage = typeof session.errorMessage === 'string' && session.errorMessage.trim()
1372
+ ? session.errorMessage.trim()
1373
+ : 'Provider reported an error';
1374
+ this.providerErrorReason = errorReason;
1375
+ this.activeModal = null;
1376
+ this.responseSettleIgnoreUntil = Date.now() + retryDelayMs + this.timeouts.outputSettle + 400;
1377
+ this.setStatus('generating', 'provider_error_retry_scheduled');
1378
+ this.recordTrace('provider_error_retry_scheduled', {
1379
+ retryPrompt,
1380
+ retryDelayMs,
1381
+ retryAttempt,
1382
+ retryMaxAttempts,
1383
+ errorReason,
1384
+ parsedStatus: ctx.parsedStatus || ctx.status,
1385
+ });
1386
+ this.onStatusChange?.();
1387
+ this.providerErrorRetryTimer = setTimeout(() => {
1388
+ this.providerErrorRetryTimer = null;
1389
+ this.providerErrorRetryKey = '';
1390
+ if (!this.ptyProcess) return;
1391
+ this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
1392
+ this.submitRetryUsed = false;
1393
+ this.recordTrace('provider_error_retry_write', {
1394
+ retryPrompt,
1395
+ retryAttempt,
1396
+ retryMaxAttempts,
1397
+ errorReason,
1398
+ });
1399
+ this.ptyProcess.write(`${retryPrompt}\r`);
1400
+ if (this.settleTimer) clearTimeout(this.settleTimer);
1401
+ this.settleTimer = setTimeout(() => {
1402
+ this.settleTimer = null;
1403
+ this.settledBuffer = this.recentOutputBuffer;
1404
+ this.evaluateSettled();
1405
+ }, this.timeouts.outputSettle + 150);
1406
+ }, retryDelayMs);
1407
+ return true;
1408
+ }
1409
+
1243
1410
  private applyIdle(ctx: SettledEvalContext, now: number): void {
1244
1411
  const { modal, lastParsedAssistant, prevStatus } = ctx;
1245
1412
  if (prevStatus === 'waiting_approval') {
@@ -1318,6 +1485,11 @@ export class ProviderCliAdapter implements CliAdapter {
1318
1485
  this.idleTimeout = setTimeout(() => {
1319
1486
  if (this.isWaitingForResponse && !this.hasActionableApproval()) {
1320
1487
  if (this.shouldDeferIdleTimeoutFinish()) return;
1488
+ const parsed = this.runParseSession();
1489
+ if (this.shouldKeepCodexTurnOpenForFinish(parsed)) {
1490
+ this.rescheduleCodexFinishCheck('codex_idle_timeout_not_final');
1491
+ return;
1492
+ }
1321
1493
  this.clearIdleFinishCandidate('idle_timeout_finish');
1322
1494
  this.finishResponse();
1323
1495
  }
@@ -1327,6 +1499,11 @@ export class ProviderCliAdapter implements CliAdapter {
1327
1499
  private finishResponse(): void {
1328
1500
  if (this.submitPendingUntil > Date.now()) return;
1329
1501
  if (this.responseSettleIgnoreUntil > Date.now()) return;
1502
+ const parsedBeforeFinish = this.runParseSession();
1503
+ if (this.shouldKeepCodexTurnOpenForFinish(parsedBeforeFinish)) {
1504
+ this.rescheduleCodexFinishCheck('codex_finish_not_final');
1505
+ return;
1506
+ }
1330
1507
  this.clearIdleFinishCandidate('finish_response_enter');
1331
1508
  this.recordTrace('finish_response', {
1332
1509
  ...buildCliTraceParseSnapshot({
@@ -1372,6 +1549,7 @@ export class ProviderCliAdapter implements CliAdapter {
1372
1549
  this.activeModal = null;
1373
1550
  this.setStatus('idle', 'response_finished');
1374
1551
  this.onStatusChange?.();
1552
+ this.schedulePendingOutboundFlush();
1375
1553
  }
1376
1554
 
1377
1555
  private maybeCommitVisibleIdleTranscript(session: ParsedSession, parsedMessages: CliChatMessage[]): boolean {
@@ -1402,6 +1580,7 @@ export class ProviderCliAdapter implements CliAdapter {
1402
1580
  this.activeModal = null;
1403
1581
  this.setStatus('idle', 'script_idle_commit');
1404
1582
  this.onStatusChange?.();
1583
+ this.schedulePendingOutboundFlush();
1405
1584
  this.recordTrace('script_idle_commit', {
1406
1585
  messageCount: parsedMessages.length,
1407
1586
  lastAssistant: summarizeCliTraceText(visibleAssistant.content, 320),
@@ -1489,6 +1668,7 @@ export class ProviderCliAdapter implements CliAdapter {
1489
1668
  accumulatedRawBuffer: this.accumulatedRawBuffer,
1490
1669
  recentOutputBuffer: this.recentOutputBuffer,
1491
1670
  terminalScreenText: parseScreenText,
1671
+ workingDir: this.workingDir,
1492
1672
  baseMessages: [],
1493
1673
  partialResponse: this.responseBuffer,
1494
1674
  isWaitingForResponse: this.isWaitingForResponse,
@@ -1500,6 +1680,7 @@ export class ProviderCliAdapter implements CliAdapter {
1500
1680
  { ...input, tail, tailScreen: buildCliScreenSnapshot(tail) },
1501
1681
  );
1502
1682
  this.parseErrorMessage = null;
1683
+ if (session && typeof session === 'object') this.applyParsedSessionMetadata(session);
1503
1684
  return session && typeof session === 'object' ? session : null;
1504
1685
  } catch (e: any) {
1505
1686
  const message = e?.message || String(e);
@@ -1552,6 +1733,74 @@ export class ProviderCliAdapter implements CliAdapter {
1552
1733
  return !!(startupModal || this.activeModal);
1553
1734
  }
1554
1735
 
1736
+ private parsedStatusHasFinalAssistantMessage(parsed: any): boolean {
1737
+ const messages = Array.isArray(parsed?.messages) ? parsed.messages : [];
1738
+ const lastAssistant = [...messages].reverse().find((message: any) => {
1739
+ if (!message || message.role !== 'assistant') return false;
1740
+ return typeof message.content === 'string' && message.content.trim().length > 0;
1741
+ });
1742
+ return !!lastAssistant;
1743
+ }
1744
+
1745
+ private applyParsedSessionMetadata(parsed: any): void {
1746
+ const providerSessionId = typeof parsed?.providerSessionId === 'string' && parsed.providerSessionId.trim()
1747
+ ? parsed.providerSessionId.trim()
1748
+ : '';
1749
+ if (providerSessionId) {
1750
+ this.providerSessionId = providerSessionId;
1751
+ this.updateRuntimeMeta({ providerSessionId });
1752
+ }
1753
+ this.providerErrorMessage = typeof parsed?.errorMessage === 'string' && parsed.errorMessage.trim()
1754
+ ? parsed.errorMessage.trim()
1755
+ : null;
1756
+ this.providerErrorReason = typeof parsed?.errorReason === 'string' && parsed.errorReason.trim()
1757
+ ? parsed.errorReason.trim()
1758
+ : null;
1759
+ }
1760
+
1761
+ private parsedStatusHasFinalStandardAssistantMessage(parsed: any): boolean {
1762
+ const messages = Array.isArray(parsed?.messages) ? parsed.messages : [];
1763
+ const lastAssistant = [...messages].reverse().find((message: any) => {
1764
+ if (!message || message.role !== 'assistant') return false;
1765
+ return typeof message.content === 'string' && message.content.trim().length > 0;
1766
+ });
1767
+ if (!lastAssistant) return false;
1768
+ const kind = typeof lastAssistant.kind === 'string' && lastAssistant.kind.trim()
1769
+ ? lastAssistant.kind.trim()
1770
+ : 'standard';
1771
+ return kind === 'standard' && lastAssistant.meta?.streaming !== true;
1772
+ }
1773
+
1774
+ private shouldKeepCodexTurnOpenForFinish(parsed: any): boolean {
1775
+ if (this.cliType !== 'codex-cli') return false;
1776
+ if (!this.isWaitingForResponse || !this.currentTurnScope || this.hasActionableApproval()) return false;
1777
+ const parsedStatus = typeof parsed?.status === 'string' ? parsed.status.trim() : '';
1778
+ if (parsedStatus !== 'idle') return true;
1779
+ if (parsed?.activeModal || parsed?.modal) return true;
1780
+ return !this.parsedStatusHasFinalStandardAssistantMessage(parsed);
1781
+ }
1782
+
1783
+ private rescheduleCodexFinishCheck(reason: string): void {
1784
+ this.clearIdleFinishCandidate(reason);
1785
+ this.setStatus('generating', reason);
1786
+ if (this.idleTimeout) clearTimeout(this.idleTimeout);
1787
+ this.idleTimeout = setTimeout(() => {
1788
+ if (!this.isWaitingForResponse || this.hasActionableApproval()) return;
1789
+ this.settledBuffer = this.recentOutputBuffer;
1790
+ this.evaluateSettled();
1791
+ }, this.getIdleFinishConfirmMs());
1792
+ this.recordTrace('codex_finish_deferred', {
1793
+ reason,
1794
+ ...buildCliTraceParseSnapshot({
1795
+ accumulatedBuffer: this.accumulatedBuffer,
1796
+ accumulatedRawBuffer: this.accumulatedRawBuffer,
1797
+ responseBuffer: this.responseBuffer,
1798
+ partialResponse: this.responseBuffer,
1799
+ scope: this.currentTurnScope,
1800
+ }),
1801
+ });
1802
+ }
1803
+
1555
1804
  private projectEffectiveStatus(startupModal: { message: string; buttons: string[] } | null = null): CliSessionStatus['status'] {
1556
1805
  if (this.parseErrorMessage) return 'error';
1557
1806
  if (this.hasActionableApproval(startupModal)) return 'waiting_approval';
@@ -1564,8 +1813,16 @@ export class ProviderCliAdapter implements CliAdapter {
1564
1813
  getStatus(options: { allowParse?: boolean } = {}): CliSessionStatus {
1565
1814
  const allowParse = options.allowParse !== false;
1566
1815
  const startupModal = allowParse && this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
1816
+ const startupDetectedStatus = allowParse && this.startupParseGate && !startupModal
1817
+ ? this.runDetectStatus(this.recentOutputBuffer || this.terminalScreen.getText())
1818
+ : null;
1567
1819
  let effectiveStatus = this.projectEffectiveStatus(startupModal);
1568
1820
  let effectiveModal = startupModal || this.activeModal;
1821
+ if (startupDetectedStatus === 'waiting_approval') {
1822
+ effectiveStatus = 'waiting_approval';
1823
+ } else if (startupDetectedStatus === 'idle' && !startupModal && !effectiveModal) {
1824
+ effectiveStatus = 'idle';
1825
+ }
1569
1826
  if (allowParse && !startupModal && !effectiveModal) {
1570
1827
  const parsed = this.getFreshParsedStatusCache();
1571
1828
  const parsedModal = parsed?.activeModal && Array.isArray(parsed.activeModal.buttons)
@@ -1575,6 +1832,18 @@ export class ProviderCliAdapter implements CliAdapter {
1575
1832
  if (parsed?.status === 'waiting_approval' && parsedModal) {
1576
1833
  effectiveStatus = 'waiting_approval';
1577
1834
  effectiveModal = parsedModal;
1835
+ } else if (
1836
+ effectiveStatus === 'idle'
1837
+ && parsed?.status === 'generating'
1838
+ && !this.parsedStatusHasFinalAssistantMessage(parsed)
1839
+ ) {
1840
+ effectiveStatus = 'generating';
1841
+ } else if (
1842
+ effectiveStatus === 'generating'
1843
+ && parsed?.status === 'idle'
1844
+ && this.parsedStatusHasFinalAssistantMessage(parsed)
1845
+ ) {
1846
+ effectiveStatus = 'idle';
1578
1847
  }
1579
1848
  }
1580
1849
  const bufferState = this.getBufferState();
@@ -1583,8 +1852,17 @@ export class ProviderCliAdapter implements CliAdapter {
1583
1852
  messages: [],
1584
1853
  workingDir: this.workingDir,
1585
1854
  activeModal: effectiveModal,
1586
- errorMessage: this.parseErrorMessage || undefined,
1587
- errorReason: this.parseErrorMessage ? 'parse_error' : undefined,
1855
+ pendingOutboundCount: this.pendingOutboundQueue.length,
1856
+ pendingOutboundMessages: this.pendingOutboundQueue.map((message) => ({
1857
+ id: message.id,
1858
+ role: message.role,
1859
+ content: message.content,
1860
+ queuedAt: message.queuedAt,
1861
+ source: message.source,
1862
+ })),
1863
+ errorMessage: this.parseErrorMessage || this.providerErrorMessage || undefined,
1864
+ errorReason: this.parseErrorMessage ? 'parse_error' : (this.providerErrorReason || undefined),
1865
+ providerSessionId: this.providerSessionId || undefined,
1588
1866
  ...(bufferState ? { bufferState } : {}),
1589
1867
  };
1590
1868
  }
@@ -1600,7 +1878,8 @@ export class ProviderCliAdapter implements CliAdapter {
1600
1878
  const cached = this.parsedStatusCache;
1601
1879
  const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
1602
1880
  if (
1603
- cached
1881
+ !this.providerOwnsTranscript()
1882
+ && cached
1604
1883
  && cached.responseBuffer === this.responseBuffer
1605
1884
  && cached.currentTurnScope === this.currentTurnScope
1606
1885
  && cached.recentOutputBuffer === this.recentOutputBuffer
@@ -1630,7 +1909,13 @@ export class ProviderCliAdapter implements CliAdapter {
1630
1909
  lastOutputAt: this.lastOutputAt,
1631
1910
  }),
1632
1911
  activeModal,
1633
- providerSessionId: typeof (parsed as any).providerSessionId === 'string' ? (parsed as any).providerSessionId : undefined,
1912
+ providerSessionId: this.providerSessionId || (typeof (parsed as any).providerSessionId === 'string' ? (parsed as any).providerSessionId : undefined),
1913
+ errorMessage: typeof (parsed as any).errorMessage === 'string' && (parsed as any).errorMessage.trim()
1914
+ ? (parsed as any).errorMessage.trim()
1915
+ : undefined,
1916
+ errorReason: typeof (parsed as any).errorReason === 'string' && (parsed as any).errorReason.trim()
1917
+ ? (parsed as any).errorReason.trim()
1918
+ : undefined,
1634
1919
  ...(bufferState ? { bufferState } : {}),
1635
1920
  ...((parsed as any).transcriptAuthority === 'provider' || (parsed as any).transcriptAuthority === 'daemon'
1636
1921
  ? { transcriptAuthority: (parsed as any).transcriptAuthority }
@@ -1665,6 +1950,7 @@ export class ProviderCliAdapter implements CliAdapter {
1665
1950
  accumulatedRawBuffer: this.accumulatedRawBuffer,
1666
1951
  recentOutputBuffer: this.recentOutputBuffer,
1667
1952
  terminalScreenText: this.getParseScreenText(this.terminalScreen.getText()),
1953
+ workingDir: this.workingDir,
1668
1954
  baseMessages: [],
1669
1955
  partialResponse: this.responseBuffer,
1670
1956
  isWaitingForResponse: this.isWaitingForResponse,
@@ -1924,7 +2210,124 @@ export class ProviderCliAdapter implements CliAdapter {
1924
2210
  ), 50);
1925
2211
  }
1926
2212
 
1927
- async sendMessage(text: string): Promise<void> {
2213
+ async sendMessage(text: string, options: { force?: boolean } = {}): Promise<void> {
2214
+ if (options.force === true) {
2215
+ await this.forceSendMessage(text);
2216
+ return;
2217
+ }
2218
+ await this.sendMessageNow(text, true);
2219
+ }
2220
+
2221
+ async forceSendMessage(text: string): Promise<void> {
2222
+ if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
2223
+ const content = String(text || '');
2224
+ if (!content.trim()) return;
2225
+ this.recordTrace('force_send_message', {
2226
+ text: summarizeCliTraceText(content, 500),
2227
+ status: this.currentStatus,
2228
+ isWaitingForResponse: this.isWaitingForResponse,
2229
+ queueLength: this.pendingOutboundQueue.length,
2230
+ });
2231
+ LOG.info('CLI', `[${this.cliType}] force-sending prompt while status=${this.currentStatus}`);
2232
+ await this.writeToPty(content + this.sendKey);
2233
+ this.onStatusChange?.();
2234
+ }
2235
+
2236
+ private enqueuePendingOutboundMessage(text: string, reason: string): void {
2237
+ const content = String(text || '');
2238
+ const duplicate = this.pendingOutboundQueue.some((message) => message.content === content);
2239
+ if (duplicate) {
2240
+ this.recordTrace('send_message_queued_duplicate_suppressed', {
2241
+ reason,
2242
+ queueLength: this.pendingOutboundQueue.length,
2243
+ text: summarizeCliTraceText(content, 500),
2244
+ });
2245
+ return;
2246
+ }
2247
+ const queuedAt = Date.now();
2248
+ const message: PendingOutboundMessage = {
2249
+ id: `${queuedAt}:${this.pendingOutboundQueue.length}:${Math.random().toString(36).slice(2, 10)}`,
2250
+ role: 'user',
2251
+ content,
2252
+ queuedAt,
2253
+ source: 'sendMessage',
2254
+ };
2255
+ this.pendingOutboundQueue.push(message);
2256
+ this.recordTrace('send_message_queued', {
2257
+ reason,
2258
+ queueLength: this.pendingOutboundQueue.length,
2259
+ queuedAt,
2260
+ text: summarizeCliTraceText(content, 500),
2261
+ });
2262
+ LOG.info('CLI', `[${this.cliType}] queued outbound message while busy (${reason}); queue=${this.pendingOutboundQueue.length}`);
2263
+ this.onStatusChange?.();
2264
+ }
2265
+
2266
+ private shouldQueuePendingOutboundMessage(parsedStatusBeforeSend: any | null = null): string | null {
2267
+ if (this.provider.allowInputDuringGeneration === true) return null;
2268
+ if (this.hasActionableApproval()) return null;
2269
+ const parsedSessionStatus = typeof parsedStatusBeforeSend?.status === 'string'
2270
+ ? String(parsedStatusBeforeSend.status)
2271
+ : '';
2272
+ if (parsedSessionStatus === 'idle' && this.parsedStatusHasFinalAssistantMessage(parsedStatusBeforeSend)) return null;
2273
+ if (this.currentStatus === 'generating') return 'current_status_generating';
2274
+ if (parsedSessionStatus === 'generating' || parsedSessionStatus === 'long_generating') {
2275
+ const parsedModal = parsedStatusBeforeSend?.activeModal ?? parsedStatusBeforeSend?.modal ?? null;
2276
+ const parsedHasActionableModal = Boolean(
2277
+ parsedModal
2278
+ && Array.isArray(parsedModal.buttons)
2279
+ && parsedModal.buttons.some((candidate: unknown) => typeof candidate === 'string' && candidate.trim()),
2280
+ );
2281
+ const terminalLooksIdle = this.currentStatus === 'idle'
2282
+ && this.runDetectStatus(this.recentOutputBuffer) === 'idle'
2283
+ && !this.isWaitingForResponse
2284
+ && !this.currentTurnScope
2285
+ && !this.hasActionableApproval()
2286
+ && !parsedHasActionableModal;
2287
+ return terminalLooksIdle ? null : `parsed_status_${parsedSessionStatus}`;
2288
+ }
2289
+ if (this.isWaitingForResponse && this.currentTurnScope) return 'active_turn_in_progress';
2290
+ return null;
2291
+ }
2292
+
2293
+ private schedulePendingOutboundFlush(delayMs = 0): void {
2294
+ if (this.pendingOutboundFlushTimer) clearTimeout(this.pendingOutboundFlushTimer);
2295
+ this.pendingOutboundFlushTimer = setTimeout(() => {
2296
+ this.pendingOutboundFlushTimer = null;
2297
+ void this.flushPendingOutboundQueue();
2298
+ }, Math.max(0, delayMs));
2299
+ }
2300
+
2301
+ private async flushPendingOutboundQueue(): Promise<void> {
2302
+ if (this.pendingOutboundFlushInFlight || this.pendingOutboundQueue.length === 0) return;
2303
+ if (this.currentStatus !== 'idle' || this.isWaitingForResponse || this.hasActionableApproval()) return;
2304
+ this.pendingOutboundFlushInFlight = true;
2305
+ try {
2306
+ while (this.pendingOutboundQueue.length > 0) {
2307
+ if (this.currentStatus !== 'idle' || this.isWaitingForResponse || this.hasActionableApproval()) break;
2308
+ const next = this.pendingOutboundQueue[0];
2309
+ this.recordTrace('send_message_queue_flush', {
2310
+ id: next.id,
2311
+ queuedAt: next.queuedAt,
2312
+ queueLength: this.pendingOutboundQueue.length,
2313
+ text: summarizeCliTraceText(next.content, 500),
2314
+ });
2315
+ try {
2316
+ await this.sendMessageNow(next.content, false);
2317
+ this.pendingOutboundQueue.shift();
2318
+ this.onStatusChange?.();
2319
+ } catch (error: any) {
2320
+ LOG.warn('CLI', `[${this.cliType}] queued outbound flush failed: ${error?.message || error}`);
2321
+ this.schedulePendingOutboundFlush(1000);
2322
+ break;
2323
+ }
2324
+ }
2325
+ } finally {
2326
+ this.pendingOutboundFlushInFlight = false;
2327
+ }
2328
+ }
2329
+
2330
+ private async sendMessageNow(text: string, allowQueue: boolean): Promise<void> {
1928
2331
  if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
1929
2332
  const allowInputDuringGeneration = this.provider.allowInputDuringGeneration === true;
1930
2333
  const allowInterventionPrompt = allowInputDuringGeneration
@@ -1937,27 +2340,33 @@ export class ProviderCliAdapter implements CliAdapter {
1937
2340
  await new Promise(resolve => setTimeout(resolve, 50));
1938
2341
  }
1939
2342
  }
2343
+ const parsedStatusBeforeSend = !allowInputDuringGeneration
2344
+ ? (() => {
2345
+ try {
2346
+ return this.getScriptParsedStatus?.() || null;
2347
+ } catch {
2348
+ return null;
2349
+ }
2350
+ })()
2351
+ : null;
2352
+ const queueReason = this.shouldQueuePendingOutboundMessage(parsedStatusBeforeSend);
2353
+ if (allowQueue && queueReason) {
2354
+ this.enqueuePendingOutboundMessage(text, queueReason);
2355
+ return;
2356
+ }
1940
2357
  if (!allowInterventionPrompt) {
1941
2358
  await this.waitForInteractivePrompt();
1942
2359
  }
1943
2360
  if (!this.ready) {
1944
2361
  this.resolveStartupState('send_precheck');
1945
- if (this.runDetectStatus(this.recentOutputBuffer) === 'idle' && this.currentStatus === 'idle') {
2362
+ if (this.runDetectStatus(this.recentOutputBuffer) === 'idle') {
1946
2363
  this.ready = true;
1947
2364
  this.startupParseGate = false;
2365
+ this.setStatus('idle', 'send_message_idle_prompt_recovery');
1948
2366
  LOG.info('CLI', `[${this.cliType}] sendMessage recovered idle prompt readiness`);
1949
2367
  }
1950
2368
  }
1951
2369
  if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
1952
- const parsedStatusBeforeSend = !allowInputDuringGeneration
1953
- ? (() => {
1954
- try {
1955
- return this.getScriptParsedStatus?.() || null;
1956
- } catch {
1957
- return null;
1958
- }
1959
- })()
1960
- : null;
1961
2370
  const parsedSessionStatus = typeof parsedStatusBeforeSend?.status === 'string'
1962
2371
  ? String(parsedStatusBeforeSend.status)
1963
2372
  : '';
@@ -1975,11 +2384,22 @@ export class ProviderCliAdapter implements CliAdapter {
1975
2384
  && !this.hasActionableApproval()
1976
2385
  && !parsedHasActionableModal;
1977
2386
  if (!terminalLooksIdle) {
2387
+ if (allowQueue) {
2388
+ this.enqueuePendingOutboundMessage(text, `parsed_status_${parsedSessionStatus}`);
2389
+ return;
2390
+ }
1978
2391
  throw new Error(`${this.cliName} is still processing the previous prompt`);
1979
2392
  }
1980
2393
  }
1981
2394
  if (this.isWaitingForResponse && !allowInputDuringGeneration) {
1982
- if (!this.clearStaleIdleResponseGuard('send_message_guard')) {
2395
+ if (
2396
+ !this.clearStaleIdleResponseGuard('send_message_guard')
2397
+ && !this.clearParsedIdleResponseGuard('send_message_parsed_idle_guard', parsedStatusBeforeSend)
2398
+ ) {
2399
+ if (allowQueue) {
2400
+ this.enqueuePendingOutboundMessage(text, 'waiting_for_response');
2401
+ return;
2402
+ }
1983
2403
  throw new Error(`${this.cliName} is still processing the previous prompt`);
1984
2404
  }
1985
2405
  }
@@ -2230,6 +2650,9 @@ export class ProviderCliAdapter implements CliAdapter {
2230
2650
  this.pendingTerminalQueryTail = '';
2231
2651
  this.ptyOutputChunks = [];
2232
2652
  this.finishRetryCount = 0;
2653
+ if (this.pendingOutboundFlushTimer) { clearTimeout(this.pendingOutboundFlushTimer); this.pendingOutboundFlushTimer = null; }
2654
+ this.pendingOutboundQueue = [];
2655
+ this.pendingOutboundFlushInFlight = false;
2233
2656
  if (this.ptyProcess) {
2234
2657
  this.ptyProcess.write('\x03');
2235
2658
  setTimeout(() => {
@@ -2251,6 +2674,9 @@ export class ProviderCliAdapter implements CliAdapter {
2251
2674
  this.pendingTerminalQueryTail = '';
2252
2675
  this.ptyOutputChunks = [];
2253
2676
  this.finishRetryCount = 0;
2677
+ if (this.pendingOutboundFlushTimer) { clearTimeout(this.pendingOutboundFlushTimer); this.pendingOutboundFlushTimer = null; }
2678
+ this.pendingOutboundQueue = [];
2679
+ this.pendingOutboundFlushInFlight = false;
2254
2680
  if (this.ptyProcess) {
2255
2681
  try {
2256
2682
  if (typeof this.ptyProcess.detach === 'function') {
@@ -2281,6 +2707,9 @@ export class ProviderCliAdapter implements CliAdapter {
2281
2707
  this.ptyOutputChunks = [];
2282
2708
  if (this.finishRetryTimer) { clearTimeout(this.finishRetryTimer); this.finishRetryTimer = null; }
2283
2709
  this.finishRetryCount = 0;
2710
+ if (this.pendingOutboundFlushTimer) { clearTimeout(this.pendingOutboundFlushTimer); this.pendingOutboundFlushTimer = null; }
2711
+ this.pendingOutboundQueue = [];
2712
+ this.pendingOutboundFlushInFlight = false;
2284
2713
  this.resetTerminalScreen();
2285
2714
  this.ptyProcess?.clearBuffer?.();
2286
2715
  this.onStatusChange?.();
@@ -2369,10 +2798,26 @@ export class ProviderCliAdapter implements CliAdapter {
2369
2798
  getDebugState(): Record<string, any> {
2370
2799
  const screenText = sanitizeTerminalText(this.terminalScreen.getText());
2371
2800
  const startupModal = this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
2372
- const effectiveStatus = this.projectEffectiveStatus(startupModal);
2373
- const effectiveReady = this.ready || !!startupModal;
2801
+ const startupDetectedStatus = this.startupParseGate && !startupModal
2802
+ ? this.runDetectStatus(this.recentOutputBuffer || screenText)
2803
+ : null;
2804
+ const effectiveReady = this.ready || !!startupModal || startupDetectedStatus === 'waiting_approval';
2374
2805
  const parsedDebugState = this.getParsedDebugState();
2375
2806
  const parsedMessages = Array.isArray(parsedDebugState?.messages) ? parsedDebugState.messages : [];
2807
+ let effectiveStatus = this.projectEffectiveStatus(startupModal);
2808
+ if (parsedDebugState?.status === 'error') {
2809
+ effectiveStatus = 'error';
2810
+ }
2811
+ if (startupDetectedStatus === 'waiting_approval') {
2812
+ effectiveStatus = 'waiting_approval';
2813
+ }
2814
+ if (
2815
+ effectiveStatus === 'idle'
2816
+ && parsedDebugState?.status === 'generating'
2817
+ && !this.parsedStatusHasFinalAssistantMessage(parsedDebugState)
2818
+ ) {
2819
+ effectiveStatus = 'generating';
2820
+ }
2376
2821
  return {
2377
2822
  type: this.cliType,
2378
2823
  name: this.cliName,
@@ -2394,6 +2839,8 @@ export class ProviderCliAdapter implements CliAdapter {
2394
2839
  providerSessionId: parsedDebugState.providerSessionId,
2395
2840
  transcriptAuthority: parsedDebugState.transcriptAuthority,
2396
2841
  coverage: parsedDebugState.coverage,
2842
+ errorMessage: parsedDebugState.errorMessage,
2843
+ errorReason: parsedDebugState.errorReason,
2397
2844
  activeModal: parsedDebugState.activeModal,
2398
2845
  messageCount: parsedMessages.length,
2399
2846
  } : null,
@@ -2407,6 +2854,14 @@ export class ProviderCliAdapter implements CliAdapter {
2407
2854
  rawBufferPreview: this.accumulatedRawBuffer.slice(-1000),
2408
2855
  sanitizedRawPreview: sanitizeTerminalText(this.accumulatedRawBuffer).slice(-1000),
2409
2856
  responseBuffer: this.responseBuffer.slice(-1000),
2857
+ pendingOutboundQueue: this.pendingOutboundQueue.map((message) => ({
2858
+ id: message.id,
2859
+ role: message.role,
2860
+ content: message.content,
2861
+ queuedAt: message.queuedAt,
2862
+ source: message.source,
2863
+ })),
2864
+ pendingOutboundCount: this.pendingOutboundQueue.length,
2410
2865
  lastOutputAt: this.lastOutputAt,
2411
2866
  lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
2412
2867
  lastScreenChangeAt: this.lastScreenChangeAt,