@adhdev/daemon-core 0.9.82-rc.11 → 0.9.82-rc.111

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 (68) hide show
  1. package/dist/cli-adapters/provider-cli-adapter.d.ts +21 -0
  2. package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
  3. package/dist/cli-adapters/provider-cli-shared.d.ts +14 -0
  4. package/dist/commands/router.d.ts +22 -0
  5. package/dist/config/chat-history.d.ts +4 -0
  6. package/dist/config/mesh-config.d.ts +66 -1
  7. package/dist/index.d.ts +12 -5
  8. package/dist/index.js +6001 -1225
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.mjs +5967 -1212
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/installer.d.ts +1 -4
  13. package/dist/launch.d.ts +1 -1
  14. package/dist/logging/async-batch-writer.d.ts +10 -0
  15. package/dist/mesh/beads-db.d.ts +18 -0
  16. package/dist/mesh/mesh-active-work.d.ts +60 -0
  17. package/dist/mesh/mesh-events.d.ts +26 -5
  18. package/dist/mesh/mesh-fast-forward.d.ts +39 -0
  19. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  20. package/dist/mesh/mesh-ledger.d.ts +38 -1
  21. package/dist/mesh/mesh-work-queue.d.ts +27 -5
  22. package/dist/mesh/refine-config.d.ts +176 -0
  23. package/dist/providers/chat-message-normalization.d.ts +1 -0
  24. package/dist/providers/cli-provider-instance.d.ts +2 -1
  25. package/dist/repo-mesh-types.d.ts +45 -0
  26. package/dist/status/reporter.d.ts +2 -0
  27. package/package.json +3 -1
  28. package/src/boot/daemon-lifecycle.ts +1 -0
  29. package/src/cli-adapters/provider-cli-adapter.ts +453 -17
  30. package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
  31. package/src/cli-adapters/provider-cli-parse.ts +4 -0
  32. package/src/cli-adapters/provider-cli-runtime.ts +3 -1
  33. package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
  34. package/src/cli-adapters/provider-cli-shared.ts +32 -10
  35. package/src/commands/chat-commands.ts +626 -20
  36. package/src/commands/cli-manager.ts +129 -1
  37. package/src/commands/handler.ts +8 -1
  38. package/src/commands/mesh-coordinator.ts +13 -143
  39. package/src/commands/router.ts +2820 -437
  40. package/src/config/chat-history.ts +37 -9
  41. package/src/config/mesh-config.ts +245 -1
  42. package/src/daemon/dev-cli-debug.ts +10 -1
  43. package/src/detection/ide-detector.ts +26 -16
  44. package/src/index.ts +30 -4
  45. package/src/installer.d.ts +1 -1
  46. package/src/installer.ts +8 -6
  47. package/src/launch.d.ts +1 -1
  48. package/src/launch.ts +37 -28
  49. package/src/logging/async-batch-writer.ts +55 -0
  50. package/src/logging/logger.ts +2 -1
  51. package/src/mesh/beads-db.ts +176 -0
  52. package/src/mesh/coordinator-prompt.ts +31 -8
  53. package/src/mesh/mesh-active-work.ts +255 -0
  54. package/src/mesh/mesh-events.ts +389 -47
  55. package/src/mesh/mesh-fast-forward.ts +430 -0
  56. package/src/mesh/mesh-host-ownership.ts +73 -0
  57. package/src/mesh/mesh-ledger.ts +138 -1
  58. package/src/mesh/mesh-work-queue.ts +199 -137
  59. package/src/mesh/refine-config.ts +356 -0
  60. package/src/providers/chat-message-normalization.ts +7 -12
  61. package/src/providers/cli-provider-instance.ts +143 -18
  62. package/src/providers/ide-provider-instance.ts +17 -3
  63. package/src/providers/provider-loader.ts +10 -4
  64. package/src/providers/read-chat-contract.ts +1 -1
  65. package/src/providers/version-archive.ts +38 -20
  66. package/src/repo-mesh-types.ts +50 -0
  67. package/src/status/reporter.ts +15 -0
  68. 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,
@@ -1925,6 +2211,104 @@ export class ProviderCliAdapter implements CliAdapter {
1925
2211
  }
1926
2212
 
1927
2213
  async sendMessage(text: string): Promise<void> {
2214
+ await this.sendMessageNow(text, true);
2215
+ }
2216
+
2217
+ private enqueuePendingOutboundMessage(text: string, reason: string): void {
2218
+ const content = String(text || '');
2219
+ const duplicate = this.pendingOutboundQueue.some((message) => message.content === content);
2220
+ if (duplicate) {
2221
+ this.recordTrace('send_message_queued_duplicate_suppressed', {
2222
+ reason,
2223
+ queueLength: this.pendingOutboundQueue.length,
2224
+ text: summarizeCliTraceText(content, 500),
2225
+ });
2226
+ return;
2227
+ }
2228
+ const queuedAt = Date.now();
2229
+ const message: PendingOutboundMessage = {
2230
+ id: `${queuedAt}:${this.pendingOutboundQueue.length}:${Math.random().toString(36).slice(2, 10)}`,
2231
+ role: 'user',
2232
+ content,
2233
+ queuedAt,
2234
+ source: 'sendMessage',
2235
+ };
2236
+ this.pendingOutboundQueue.push(message);
2237
+ this.recordTrace('send_message_queued', {
2238
+ reason,
2239
+ queueLength: this.pendingOutboundQueue.length,
2240
+ queuedAt,
2241
+ text: summarizeCliTraceText(content, 500),
2242
+ });
2243
+ LOG.info('CLI', `[${this.cliType}] queued outbound message while busy (${reason}); queue=${this.pendingOutboundQueue.length}`);
2244
+ this.onStatusChange?.();
2245
+ }
2246
+
2247
+ private shouldQueuePendingOutboundMessage(parsedStatusBeforeSend: any | null = null): string | null {
2248
+ if (this.provider.allowInputDuringGeneration === true) return null;
2249
+ if (this.hasActionableApproval()) return null;
2250
+ const parsedSessionStatus = typeof parsedStatusBeforeSend?.status === 'string'
2251
+ ? String(parsedStatusBeforeSend.status)
2252
+ : '';
2253
+ if (parsedSessionStatus === 'idle' && this.parsedStatusHasFinalAssistantMessage(parsedStatusBeforeSend)) return null;
2254
+ if (this.currentStatus === 'generating') return 'current_status_generating';
2255
+ if (parsedSessionStatus === 'generating' || parsedSessionStatus === 'long_generating') {
2256
+ const parsedModal = parsedStatusBeforeSend?.activeModal ?? parsedStatusBeforeSend?.modal ?? null;
2257
+ const parsedHasActionableModal = Boolean(
2258
+ parsedModal
2259
+ && Array.isArray(parsedModal.buttons)
2260
+ && parsedModal.buttons.some((candidate: unknown) => typeof candidate === 'string' && candidate.trim()),
2261
+ );
2262
+ const terminalLooksIdle = this.currentStatus === 'idle'
2263
+ && this.runDetectStatus(this.recentOutputBuffer) === 'idle'
2264
+ && !this.isWaitingForResponse
2265
+ && !this.currentTurnScope
2266
+ && !this.hasActionableApproval()
2267
+ && !parsedHasActionableModal;
2268
+ return terminalLooksIdle ? null : `parsed_status_${parsedSessionStatus}`;
2269
+ }
2270
+ if (this.isWaitingForResponse && this.currentTurnScope) return 'active_turn_in_progress';
2271
+ return null;
2272
+ }
2273
+
2274
+ private schedulePendingOutboundFlush(delayMs = 0): void {
2275
+ if (this.pendingOutboundFlushTimer) clearTimeout(this.pendingOutboundFlushTimer);
2276
+ this.pendingOutboundFlushTimer = setTimeout(() => {
2277
+ this.pendingOutboundFlushTimer = null;
2278
+ void this.flushPendingOutboundQueue();
2279
+ }, Math.max(0, delayMs));
2280
+ }
2281
+
2282
+ private async flushPendingOutboundQueue(): Promise<void> {
2283
+ if (this.pendingOutboundFlushInFlight || this.pendingOutboundQueue.length === 0) return;
2284
+ if (this.currentStatus !== 'idle' || this.isWaitingForResponse || this.hasActionableApproval()) return;
2285
+ this.pendingOutboundFlushInFlight = true;
2286
+ try {
2287
+ while (this.pendingOutboundQueue.length > 0) {
2288
+ if (this.currentStatus !== 'idle' || this.isWaitingForResponse || this.hasActionableApproval()) break;
2289
+ const next = this.pendingOutboundQueue[0];
2290
+ this.recordTrace('send_message_queue_flush', {
2291
+ id: next.id,
2292
+ queuedAt: next.queuedAt,
2293
+ queueLength: this.pendingOutboundQueue.length,
2294
+ text: summarizeCliTraceText(next.content, 500),
2295
+ });
2296
+ try {
2297
+ await this.sendMessageNow(next.content, false);
2298
+ this.pendingOutboundQueue.shift();
2299
+ this.onStatusChange?.();
2300
+ } catch (error: any) {
2301
+ LOG.warn('CLI', `[${this.cliType}] queued outbound flush failed: ${error?.message || error}`);
2302
+ this.schedulePendingOutboundFlush(1000);
2303
+ break;
2304
+ }
2305
+ }
2306
+ } finally {
2307
+ this.pendingOutboundFlushInFlight = false;
2308
+ }
2309
+ }
2310
+
2311
+ private async sendMessageNow(text: string, allowQueue: boolean): Promise<void> {
1928
2312
  if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
1929
2313
  const allowInputDuringGeneration = this.provider.allowInputDuringGeneration === true;
1930
2314
  const allowInterventionPrompt = allowInputDuringGeneration
@@ -1937,27 +2321,33 @@ export class ProviderCliAdapter implements CliAdapter {
1937
2321
  await new Promise(resolve => setTimeout(resolve, 50));
1938
2322
  }
1939
2323
  }
2324
+ const parsedStatusBeforeSend = !allowInputDuringGeneration
2325
+ ? (() => {
2326
+ try {
2327
+ return this.getScriptParsedStatus?.() || null;
2328
+ } catch {
2329
+ return null;
2330
+ }
2331
+ })()
2332
+ : null;
2333
+ const queueReason = this.shouldQueuePendingOutboundMessage(parsedStatusBeforeSend);
2334
+ if (allowQueue && queueReason) {
2335
+ this.enqueuePendingOutboundMessage(text, queueReason);
2336
+ return;
2337
+ }
1940
2338
  if (!allowInterventionPrompt) {
1941
2339
  await this.waitForInteractivePrompt();
1942
2340
  }
1943
2341
  if (!this.ready) {
1944
2342
  this.resolveStartupState('send_precheck');
1945
- if (this.runDetectStatus(this.recentOutputBuffer) === 'idle' && this.currentStatus === 'idle') {
2343
+ if (this.runDetectStatus(this.recentOutputBuffer) === 'idle') {
1946
2344
  this.ready = true;
1947
2345
  this.startupParseGate = false;
2346
+ this.setStatus('idle', 'send_message_idle_prompt_recovery');
1948
2347
  LOG.info('CLI', `[${this.cliType}] sendMessage recovered idle prompt readiness`);
1949
2348
  }
1950
2349
  }
1951
2350
  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
2351
  const parsedSessionStatus = typeof parsedStatusBeforeSend?.status === 'string'
1962
2352
  ? String(parsedStatusBeforeSend.status)
1963
2353
  : '';
@@ -1975,11 +2365,22 @@ export class ProviderCliAdapter implements CliAdapter {
1975
2365
  && !this.hasActionableApproval()
1976
2366
  && !parsedHasActionableModal;
1977
2367
  if (!terminalLooksIdle) {
2368
+ if (allowQueue) {
2369
+ this.enqueuePendingOutboundMessage(text, `parsed_status_${parsedSessionStatus}`);
2370
+ return;
2371
+ }
1978
2372
  throw new Error(`${this.cliName} is still processing the previous prompt`);
1979
2373
  }
1980
2374
  }
1981
2375
  if (this.isWaitingForResponse && !allowInputDuringGeneration) {
1982
- if (!this.clearStaleIdleResponseGuard('send_message_guard')) {
2376
+ if (
2377
+ !this.clearStaleIdleResponseGuard('send_message_guard')
2378
+ && !this.clearParsedIdleResponseGuard('send_message_parsed_idle_guard', parsedStatusBeforeSend)
2379
+ ) {
2380
+ if (allowQueue) {
2381
+ this.enqueuePendingOutboundMessage(text, 'waiting_for_response');
2382
+ return;
2383
+ }
1983
2384
  throw new Error(`${this.cliName} is still processing the previous prompt`);
1984
2385
  }
1985
2386
  }
@@ -2230,6 +2631,9 @@ export class ProviderCliAdapter implements CliAdapter {
2230
2631
  this.pendingTerminalQueryTail = '';
2231
2632
  this.ptyOutputChunks = [];
2232
2633
  this.finishRetryCount = 0;
2634
+ if (this.pendingOutboundFlushTimer) { clearTimeout(this.pendingOutboundFlushTimer); this.pendingOutboundFlushTimer = null; }
2635
+ this.pendingOutboundQueue = [];
2636
+ this.pendingOutboundFlushInFlight = false;
2233
2637
  if (this.ptyProcess) {
2234
2638
  this.ptyProcess.write('\x03');
2235
2639
  setTimeout(() => {
@@ -2251,6 +2655,9 @@ export class ProviderCliAdapter implements CliAdapter {
2251
2655
  this.pendingTerminalQueryTail = '';
2252
2656
  this.ptyOutputChunks = [];
2253
2657
  this.finishRetryCount = 0;
2658
+ if (this.pendingOutboundFlushTimer) { clearTimeout(this.pendingOutboundFlushTimer); this.pendingOutboundFlushTimer = null; }
2659
+ this.pendingOutboundQueue = [];
2660
+ this.pendingOutboundFlushInFlight = false;
2254
2661
  if (this.ptyProcess) {
2255
2662
  try {
2256
2663
  if (typeof this.ptyProcess.detach === 'function') {
@@ -2281,6 +2688,9 @@ export class ProviderCliAdapter implements CliAdapter {
2281
2688
  this.ptyOutputChunks = [];
2282
2689
  if (this.finishRetryTimer) { clearTimeout(this.finishRetryTimer); this.finishRetryTimer = null; }
2283
2690
  this.finishRetryCount = 0;
2691
+ if (this.pendingOutboundFlushTimer) { clearTimeout(this.pendingOutboundFlushTimer); this.pendingOutboundFlushTimer = null; }
2692
+ this.pendingOutboundQueue = [];
2693
+ this.pendingOutboundFlushInFlight = false;
2284
2694
  this.resetTerminalScreen();
2285
2695
  this.ptyProcess?.clearBuffer?.();
2286
2696
  this.onStatusChange?.();
@@ -2369,10 +2779,26 @@ export class ProviderCliAdapter implements CliAdapter {
2369
2779
  getDebugState(): Record<string, any> {
2370
2780
  const screenText = sanitizeTerminalText(this.terminalScreen.getText());
2371
2781
  const startupModal = this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
2372
- const effectiveStatus = this.projectEffectiveStatus(startupModal);
2373
- const effectiveReady = this.ready || !!startupModal;
2782
+ const startupDetectedStatus = this.startupParseGate && !startupModal
2783
+ ? this.runDetectStatus(this.recentOutputBuffer || screenText)
2784
+ : null;
2785
+ const effectiveReady = this.ready || !!startupModal || startupDetectedStatus === 'waiting_approval';
2374
2786
  const parsedDebugState = this.getParsedDebugState();
2375
2787
  const parsedMessages = Array.isArray(parsedDebugState?.messages) ? parsedDebugState.messages : [];
2788
+ let effectiveStatus = this.projectEffectiveStatus(startupModal);
2789
+ if (parsedDebugState?.status === 'error') {
2790
+ effectiveStatus = 'error';
2791
+ }
2792
+ if (startupDetectedStatus === 'waiting_approval') {
2793
+ effectiveStatus = 'waiting_approval';
2794
+ }
2795
+ if (
2796
+ effectiveStatus === 'idle'
2797
+ && parsedDebugState?.status === 'generating'
2798
+ && !this.parsedStatusHasFinalAssistantMessage(parsedDebugState)
2799
+ ) {
2800
+ effectiveStatus = 'generating';
2801
+ }
2376
2802
  return {
2377
2803
  type: this.cliType,
2378
2804
  name: this.cliName,
@@ -2394,6 +2820,8 @@ export class ProviderCliAdapter implements CliAdapter {
2394
2820
  providerSessionId: parsedDebugState.providerSessionId,
2395
2821
  transcriptAuthority: parsedDebugState.transcriptAuthority,
2396
2822
  coverage: parsedDebugState.coverage,
2823
+ errorMessage: parsedDebugState.errorMessage,
2824
+ errorReason: parsedDebugState.errorReason,
2397
2825
  activeModal: parsedDebugState.activeModal,
2398
2826
  messageCount: parsedMessages.length,
2399
2827
  } : null,
@@ -2407,6 +2835,14 @@ export class ProviderCliAdapter implements CliAdapter {
2407
2835
  rawBufferPreview: this.accumulatedRawBuffer.slice(-1000),
2408
2836
  sanitizedRawPreview: sanitizeTerminalText(this.accumulatedRawBuffer).slice(-1000),
2409
2837
  responseBuffer: this.responseBuffer.slice(-1000),
2838
+ pendingOutboundQueue: this.pendingOutboundQueue.map((message) => ({
2839
+ id: message.id,
2840
+ role: message.role,
2841
+ content: message.content,
2842
+ queuedAt: message.queuedAt,
2843
+ source: message.source,
2844
+ })),
2845
+ pendingOutboundCount: this.pendingOutboundQueue.length,
2410
2846
  lastOutputAt: this.lastOutputAt,
2411
2847
  lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
2412
2848
  lastScreenChangeAt: this.lastScreenChangeAt,