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

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 +18 -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 +5936 -1225
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.mjs +5902 -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 +379 -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,9 @@ 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;
189
203
 
190
204
  // Resize redraw suppression
191
205
  private resizeSuppressUntil: number = 0;
@@ -761,6 +775,17 @@ export class ProviderCliAdapter implements CliAdapter {
761
775
  if (stableMs < 2000) return;
762
776
 
763
777
  const startupModal = this.runParseApproval(this.recentOutputBuffer);
778
+ const startupStatus = this.runDetectStatus(screenText || this.recentOutputBuffer);
779
+ if (!startupModal && startupStatus !== 'idle') {
780
+ this.recordTrace('startup_settle_deferred', {
781
+ trigger,
782
+ startupStatus,
783
+ stableMs,
784
+ screenText: summarizeCliTraceText(screenText, 500),
785
+ });
786
+ this.scheduleStartupSettleCheck();
787
+ return;
788
+ }
764
789
  this.startupParseGate = false;
765
790
  if (this.startupSettleTimer) {
766
791
  clearTimeout(this.startupSettleTimer);
@@ -956,6 +981,38 @@ export class ProviderCliAdapter implements CliAdapter {
956
981
  return true;
957
982
  }
958
983
 
984
+ private clearParsedIdleResponseGuard(reason: string, parsedStatus: any): boolean {
985
+ const parsedRawStatus = typeof parsedStatus?.status === 'string' ? parsedStatus.status.trim() : '';
986
+ const parsedModal = parsedStatus?.activeModal ?? parsedStatus?.modal ?? null;
987
+ const blockingModal = this.activeModal || this.runParseApproval(this.recentOutputBuffer);
988
+ if (
989
+ !this.isWaitingForResponse
990
+ || parsedRawStatus !== 'idle'
991
+ || !!parsedModal
992
+ || !!blockingModal
993
+ || !this.parsedStatusHasFinalAssistantMessage(parsedStatus)
994
+ ) {
995
+ return false;
996
+ }
997
+ this.clearAllTimers();
998
+ this.clearIdleFinishCandidate(reason);
999
+ this.responseBuffer = '';
1000
+ this.isWaitingForResponse = false;
1001
+ this.responseSettleIgnoreUntil = 0;
1002
+ this.submitRetryUsed = false;
1003
+ this.submitRetryPromptSnippet = '';
1004
+ this.finishRetryCount = 0;
1005
+ this.currentTurnScope = null;
1006
+ this.activeModal = null;
1007
+ this.setStatus('idle', reason);
1008
+ this.recordTrace('parsed_idle_response_cleared', {
1009
+ reason,
1010
+ parsedStatus: parsedRawStatus,
1011
+ parsedMessageCount: Array.isArray(parsedStatus?.messages) ? parsedStatus.messages.length : 0,
1012
+ });
1013
+ return true;
1014
+ }
1015
+
959
1016
  private hasMeaningfulResponseBuffer(promptSnippet: string): boolean {
960
1017
  const raw = String(this.responseBuffer || '').trim();
961
1018
  if (!raw) return false;
@@ -1097,6 +1154,7 @@ export class ProviderCliAdapter implements CliAdapter {
1097
1154
  && !(parsedStatus === 'idle' && !!lastParsedAssistant);
1098
1155
 
1099
1156
  if (shouldHoldGenerating) { this.applyHoldGenerating(ctx, recentInteractiveActivity); return; }
1157
+ if (status === 'error') { this.applyError(ctx, session); return; }
1100
1158
  if (status === 'waiting_approval') { this.applyWaitingApproval(ctx); return; }
1101
1159
  if (status === 'generating') { this.applyGenerating(ctx); return; }
1102
1160
  if (status === 'idle') { this.applyIdle(ctx, now); }
@@ -1240,6 +1298,41 @@ export class ProviderCliAdapter implements CliAdapter {
1240
1298
  this.onStatusChange?.();
1241
1299
  }
1242
1300
 
1301
+ private applyError(ctx: SettledEvalContext, session: ParsedSession): void {
1302
+ this.clearIdleFinishCandidate('provider_error');
1303
+ if (this.responseTimeout) { clearTimeout(this.responseTimeout); this.responseTimeout = null; }
1304
+ if (this.idleTimeout) { clearTimeout(this.idleTimeout); this.idleTimeout = null; }
1305
+ if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
1306
+ this.isWaitingForResponse = false;
1307
+ this.responseSettleIgnoreUntil = 0;
1308
+ this.submitRetryUsed = false;
1309
+ this.submitRetryPromptSnippet = '';
1310
+ this.finishRetryCount = 0;
1311
+ this.currentTurnScope = null;
1312
+ this.activeModal = null;
1313
+ this.providerErrorMessage = typeof session.errorMessage === 'string' && session.errorMessage.trim()
1314
+ ? session.errorMessage.trim()
1315
+ : 'Provider reported an error';
1316
+ this.providerErrorReason = typeof session.errorReason === 'string' && session.errorReason.trim()
1317
+ ? session.errorReason.trim()
1318
+ : 'provider_error';
1319
+ this.setStatus('error', this.providerErrorReason);
1320
+ this.recordTrace('provider_error', {
1321
+ errorMessage: this.providerErrorMessage,
1322
+ errorReason: this.providerErrorReason,
1323
+ parsedStatus: ctx.parsedStatus || ctx.status,
1324
+ messageCount: ctx.parsedMessages.length,
1325
+ ...buildCliTraceParseSnapshot({
1326
+ accumulatedBuffer: this.accumulatedBuffer,
1327
+ accumulatedRawBuffer: this.accumulatedRawBuffer,
1328
+ responseBuffer: this.responseBuffer,
1329
+ partialResponse: this.responseBuffer,
1330
+ scope: this.currentTurnScope,
1331
+ }),
1332
+ });
1333
+ this.onStatusChange?.();
1334
+ }
1335
+
1243
1336
  private applyIdle(ctx: SettledEvalContext, now: number): void {
1244
1337
  const { modal, lastParsedAssistant, prevStatus } = ctx;
1245
1338
  if (prevStatus === 'waiting_approval') {
@@ -1318,6 +1411,11 @@ export class ProviderCliAdapter implements CliAdapter {
1318
1411
  this.idleTimeout = setTimeout(() => {
1319
1412
  if (this.isWaitingForResponse && !this.hasActionableApproval()) {
1320
1413
  if (this.shouldDeferIdleTimeoutFinish()) return;
1414
+ const parsed = this.runParseSession();
1415
+ if (this.shouldKeepCodexTurnOpenForFinish(parsed)) {
1416
+ this.rescheduleCodexFinishCheck('codex_idle_timeout_not_final');
1417
+ return;
1418
+ }
1321
1419
  this.clearIdleFinishCandidate('idle_timeout_finish');
1322
1420
  this.finishResponse();
1323
1421
  }
@@ -1327,6 +1425,11 @@ export class ProviderCliAdapter implements CliAdapter {
1327
1425
  private finishResponse(): void {
1328
1426
  if (this.submitPendingUntil > Date.now()) return;
1329
1427
  if (this.responseSettleIgnoreUntil > Date.now()) return;
1428
+ const parsedBeforeFinish = this.runParseSession();
1429
+ if (this.shouldKeepCodexTurnOpenForFinish(parsedBeforeFinish)) {
1430
+ this.rescheduleCodexFinishCheck('codex_finish_not_final');
1431
+ return;
1432
+ }
1330
1433
  this.clearIdleFinishCandidate('finish_response_enter');
1331
1434
  this.recordTrace('finish_response', {
1332
1435
  ...buildCliTraceParseSnapshot({
@@ -1372,6 +1475,7 @@ export class ProviderCliAdapter implements CliAdapter {
1372
1475
  this.activeModal = null;
1373
1476
  this.setStatus('idle', 'response_finished');
1374
1477
  this.onStatusChange?.();
1478
+ this.schedulePendingOutboundFlush();
1375
1479
  }
1376
1480
 
1377
1481
  private maybeCommitVisibleIdleTranscript(session: ParsedSession, parsedMessages: CliChatMessage[]): boolean {
@@ -1402,6 +1506,7 @@ export class ProviderCliAdapter implements CliAdapter {
1402
1506
  this.activeModal = null;
1403
1507
  this.setStatus('idle', 'script_idle_commit');
1404
1508
  this.onStatusChange?.();
1509
+ this.schedulePendingOutboundFlush();
1405
1510
  this.recordTrace('script_idle_commit', {
1406
1511
  messageCount: parsedMessages.length,
1407
1512
  lastAssistant: summarizeCliTraceText(visibleAssistant.content, 320),
@@ -1489,6 +1594,7 @@ export class ProviderCliAdapter implements CliAdapter {
1489
1594
  accumulatedRawBuffer: this.accumulatedRawBuffer,
1490
1595
  recentOutputBuffer: this.recentOutputBuffer,
1491
1596
  terminalScreenText: parseScreenText,
1597
+ workingDir: this.workingDir,
1492
1598
  baseMessages: [],
1493
1599
  partialResponse: this.responseBuffer,
1494
1600
  isWaitingForResponse: this.isWaitingForResponse,
@@ -1500,6 +1606,7 @@ export class ProviderCliAdapter implements CliAdapter {
1500
1606
  { ...input, tail, tailScreen: buildCliScreenSnapshot(tail) },
1501
1607
  );
1502
1608
  this.parseErrorMessage = null;
1609
+ if (session && typeof session === 'object') this.applyParsedSessionMetadata(session);
1503
1610
  return session && typeof session === 'object' ? session : null;
1504
1611
  } catch (e: any) {
1505
1612
  const message = e?.message || String(e);
@@ -1552,6 +1659,74 @@ export class ProviderCliAdapter implements CliAdapter {
1552
1659
  return !!(startupModal || this.activeModal);
1553
1660
  }
1554
1661
 
1662
+ private parsedStatusHasFinalAssistantMessage(parsed: any): boolean {
1663
+ const messages = Array.isArray(parsed?.messages) ? parsed.messages : [];
1664
+ const lastAssistant = [...messages].reverse().find((message: any) => {
1665
+ if (!message || message.role !== 'assistant') return false;
1666
+ return typeof message.content === 'string' && message.content.trim().length > 0;
1667
+ });
1668
+ return !!lastAssistant;
1669
+ }
1670
+
1671
+ private applyParsedSessionMetadata(parsed: any): void {
1672
+ const providerSessionId = typeof parsed?.providerSessionId === 'string' && parsed.providerSessionId.trim()
1673
+ ? parsed.providerSessionId.trim()
1674
+ : '';
1675
+ if (providerSessionId) {
1676
+ this.providerSessionId = providerSessionId;
1677
+ this.updateRuntimeMeta({ providerSessionId });
1678
+ }
1679
+ this.providerErrorMessage = typeof parsed?.errorMessage === 'string' && parsed.errorMessage.trim()
1680
+ ? parsed.errorMessage.trim()
1681
+ : null;
1682
+ this.providerErrorReason = typeof parsed?.errorReason === 'string' && parsed.errorReason.trim()
1683
+ ? parsed.errorReason.trim()
1684
+ : null;
1685
+ }
1686
+
1687
+ private parsedStatusHasFinalStandardAssistantMessage(parsed: any): boolean {
1688
+ const messages = Array.isArray(parsed?.messages) ? parsed.messages : [];
1689
+ const lastAssistant = [...messages].reverse().find((message: any) => {
1690
+ if (!message || message.role !== 'assistant') return false;
1691
+ return typeof message.content === 'string' && message.content.trim().length > 0;
1692
+ });
1693
+ if (!lastAssistant) return false;
1694
+ const kind = typeof lastAssistant.kind === 'string' && lastAssistant.kind.trim()
1695
+ ? lastAssistant.kind.trim()
1696
+ : 'standard';
1697
+ return kind === 'standard' && lastAssistant.meta?.streaming !== true;
1698
+ }
1699
+
1700
+ private shouldKeepCodexTurnOpenForFinish(parsed: any): boolean {
1701
+ if (this.cliType !== 'codex-cli') return false;
1702
+ if (!this.isWaitingForResponse || !this.currentTurnScope || this.hasActionableApproval()) return false;
1703
+ const parsedStatus = typeof parsed?.status === 'string' ? parsed.status.trim() : '';
1704
+ if (parsedStatus !== 'idle') return true;
1705
+ if (parsed?.activeModal || parsed?.modal) return true;
1706
+ return !this.parsedStatusHasFinalStandardAssistantMessage(parsed);
1707
+ }
1708
+
1709
+ private rescheduleCodexFinishCheck(reason: string): void {
1710
+ this.clearIdleFinishCandidate(reason);
1711
+ this.setStatus('generating', reason);
1712
+ if (this.idleTimeout) clearTimeout(this.idleTimeout);
1713
+ this.idleTimeout = setTimeout(() => {
1714
+ if (!this.isWaitingForResponse || this.hasActionableApproval()) return;
1715
+ this.settledBuffer = this.recentOutputBuffer;
1716
+ this.evaluateSettled();
1717
+ }, this.getIdleFinishConfirmMs());
1718
+ this.recordTrace('codex_finish_deferred', {
1719
+ reason,
1720
+ ...buildCliTraceParseSnapshot({
1721
+ accumulatedBuffer: this.accumulatedBuffer,
1722
+ accumulatedRawBuffer: this.accumulatedRawBuffer,
1723
+ responseBuffer: this.responseBuffer,
1724
+ partialResponse: this.responseBuffer,
1725
+ scope: this.currentTurnScope,
1726
+ }),
1727
+ });
1728
+ }
1729
+
1555
1730
  private projectEffectiveStatus(startupModal: { message: string; buttons: string[] } | null = null): CliSessionStatus['status'] {
1556
1731
  if (this.parseErrorMessage) return 'error';
1557
1732
  if (this.hasActionableApproval(startupModal)) return 'waiting_approval';
@@ -1564,8 +1739,16 @@ export class ProviderCliAdapter implements CliAdapter {
1564
1739
  getStatus(options: { allowParse?: boolean } = {}): CliSessionStatus {
1565
1740
  const allowParse = options.allowParse !== false;
1566
1741
  const startupModal = allowParse && this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
1742
+ const startupDetectedStatus = allowParse && this.startupParseGate && !startupModal
1743
+ ? this.runDetectStatus(this.recentOutputBuffer || this.terminalScreen.getText())
1744
+ : null;
1567
1745
  let effectiveStatus = this.projectEffectiveStatus(startupModal);
1568
1746
  let effectiveModal = startupModal || this.activeModal;
1747
+ if (startupDetectedStatus === 'waiting_approval') {
1748
+ effectiveStatus = 'waiting_approval';
1749
+ } else if (startupDetectedStatus === 'idle' && !startupModal && !effectiveModal) {
1750
+ effectiveStatus = 'idle';
1751
+ }
1569
1752
  if (allowParse && !startupModal && !effectiveModal) {
1570
1753
  const parsed = this.getFreshParsedStatusCache();
1571
1754
  const parsedModal = parsed?.activeModal && Array.isArray(parsed.activeModal.buttons)
@@ -1575,6 +1758,18 @@ export class ProviderCliAdapter implements CliAdapter {
1575
1758
  if (parsed?.status === 'waiting_approval' && parsedModal) {
1576
1759
  effectiveStatus = 'waiting_approval';
1577
1760
  effectiveModal = parsedModal;
1761
+ } else if (
1762
+ effectiveStatus === 'idle'
1763
+ && parsed?.status === 'generating'
1764
+ && !this.parsedStatusHasFinalAssistantMessage(parsed)
1765
+ ) {
1766
+ effectiveStatus = 'generating';
1767
+ } else if (
1768
+ effectiveStatus === 'generating'
1769
+ && parsed?.status === 'idle'
1770
+ && this.parsedStatusHasFinalAssistantMessage(parsed)
1771
+ ) {
1772
+ effectiveStatus = 'idle';
1578
1773
  }
1579
1774
  }
1580
1775
  const bufferState = this.getBufferState();
@@ -1583,8 +1778,17 @@ export class ProviderCliAdapter implements CliAdapter {
1583
1778
  messages: [],
1584
1779
  workingDir: this.workingDir,
1585
1780
  activeModal: effectiveModal,
1586
- errorMessage: this.parseErrorMessage || undefined,
1587
- errorReason: this.parseErrorMessage ? 'parse_error' : undefined,
1781
+ pendingOutboundCount: this.pendingOutboundQueue.length,
1782
+ pendingOutboundMessages: this.pendingOutboundQueue.map((message) => ({
1783
+ id: message.id,
1784
+ role: message.role,
1785
+ content: message.content,
1786
+ queuedAt: message.queuedAt,
1787
+ source: message.source,
1788
+ })),
1789
+ errorMessage: this.parseErrorMessage || this.providerErrorMessage || undefined,
1790
+ errorReason: this.parseErrorMessage ? 'parse_error' : (this.providerErrorReason || undefined),
1791
+ providerSessionId: this.providerSessionId || undefined,
1588
1792
  ...(bufferState ? { bufferState } : {}),
1589
1793
  };
1590
1794
  }
@@ -1600,7 +1804,8 @@ export class ProviderCliAdapter implements CliAdapter {
1600
1804
  const cached = this.parsedStatusCache;
1601
1805
  const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
1602
1806
  if (
1603
- cached
1807
+ !this.providerOwnsTranscript()
1808
+ && cached
1604
1809
  && cached.responseBuffer === this.responseBuffer
1605
1810
  && cached.currentTurnScope === this.currentTurnScope
1606
1811
  && cached.recentOutputBuffer === this.recentOutputBuffer
@@ -1630,7 +1835,13 @@ export class ProviderCliAdapter implements CliAdapter {
1630
1835
  lastOutputAt: this.lastOutputAt,
1631
1836
  }),
1632
1837
  activeModal,
1633
- providerSessionId: typeof (parsed as any).providerSessionId === 'string' ? (parsed as any).providerSessionId : undefined,
1838
+ providerSessionId: this.providerSessionId || (typeof (parsed as any).providerSessionId === 'string' ? (parsed as any).providerSessionId : undefined),
1839
+ errorMessage: typeof (parsed as any).errorMessage === 'string' && (parsed as any).errorMessage.trim()
1840
+ ? (parsed as any).errorMessage.trim()
1841
+ : undefined,
1842
+ errorReason: typeof (parsed as any).errorReason === 'string' && (parsed as any).errorReason.trim()
1843
+ ? (parsed as any).errorReason.trim()
1844
+ : undefined,
1634
1845
  ...(bufferState ? { bufferState } : {}),
1635
1846
  ...((parsed as any).transcriptAuthority === 'provider' || (parsed as any).transcriptAuthority === 'daemon'
1636
1847
  ? { transcriptAuthority: (parsed as any).transcriptAuthority }
@@ -1665,6 +1876,7 @@ export class ProviderCliAdapter implements CliAdapter {
1665
1876
  accumulatedRawBuffer: this.accumulatedRawBuffer,
1666
1877
  recentOutputBuffer: this.recentOutputBuffer,
1667
1878
  terminalScreenText: this.getParseScreenText(this.terminalScreen.getText()),
1879
+ workingDir: this.workingDir,
1668
1880
  baseMessages: [],
1669
1881
  partialResponse: this.responseBuffer,
1670
1882
  isWaitingForResponse: this.isWaitingForResponse,
@@ -1925,6 +2137,104 @@ export class ProviderCliAdapter implements CliAdapter {
1925
2137
  }
1926
2138
 
1927
2139
  async sendMessage(text: string): Promise<void> {
2140
+ await this.sendMessageNow(text, true);
2141
+ }
2142
+
2143
+ private enqueuePendingOutboundMessage(text: string, reason: string): void {
2144
+ const content = String(text || '');
2145
+ const duplicate = this.pendingOutboundQueue.some((message) => message.content === content);
2146
+ if (duplicate) {
2147
+ this.recordTrace('send_message_queued_duplicate_suppressed', {
2148
+ reason,
2149
+ queueLength: this.pendingOutboundQueue.length,
2150
+ text: summarizeCliTraceText(content, 500),
2151
+ });
2152
+ return;
2153
+ }
2154
+ const queuedAt = Date.now();
2155
+ const message: PendingOutboundMessage = {
2156
+ id: `${queuedAt}:${this.pendingOutboundQueue.length}:${Math.random().toString(36).slice(2, 10)}`,
2157
+ role: 'user',
2158
+ content,
2159
+ queuedAt,
2160
+ source: 'sendMessage',
2161
+ };
2162
+ this.pendingOutboundQueue.push(message);
2163
+ this.recordTrace('send_message_queued', {
2164
+ reason,
2165
+ queueLength: this.pendingOutboundQueue.length,
2166
+ queuedAt,
2167
+ text: summarizeCliTraceText(content, 500),
2168
+ });
2169
+ LOG.info('CLI', `[${this.cliType}] queued outbound message while busy (${reason}); queue=${this.pendingOutboundQueue.length}`);
2170
+ this.onStatusChange?.();
2171
+ }
2172
+
2173
+ private shouldQueuePendingOutboundMessage(parsedStatusBeforeSend: any | null = null): string | null {
2174
+ if (this.provider.allowInputDuringGeneration === true) return null;
2175
+ if (this.hasActionableApproval()) return null;
2176
+ const parsedSessionStatus = typeof parsedStatusBeforeSend?.status === 'string'
2177
+ ? String(parsedStatusBeforeSend.status)
2178
+ : '';
2179
+ if (parsedSessionStatus === 'idle' && this.parsedStatusHasFinalAssistantMessage(parsedStatusBeforeSend)) return null;
2180
+ if (this.currentStatus === 'generating') return 'current_status_generating';
2181
+ if (parsedSessionStatus === 'generating' || parsedSessionStatus === 'long_generating') {
2182
+ const parsedModal = parsedStatusBeforeSend?.activeModal ?? parsedStatusBeforeSend?.modal ?? null;
2183
+ const parsedHasActionableModal = Boolean(
2184
+ parsedModal
2185
+ && Array.isArray(parsedModal.buttons)
2186
+ && parsedModal.buttons.some((candidate: unknown) => typeof candidate === 'string' && candidate.trim()),
2187
+ );
2188
+ const terminalLooksIdle = this.currentStatus === 'idle'
2189
+ && this.runDetectStatus(this.recentOutputBuffer) === 'idle'
2190
+ && !this.isWaitingForResponse
2191
+ && !this.currentTurnScope
2192
+ && !this.hasActionableApproval()
2193
+ && !parsedHasActionableModal;
2194
+ return terminalLooksIdle ? null : `parsed_status_${parsedSessionStatus}`;
2195
+ }
2196
+ if (this.isWaitingForResponse && this.currentTurnScope) return 'active_turn_in_progress';
2197
+ return null;
2198
+ }
2199
+
2200
+ private schedulePendingOutboundFlush(delayMs = 0): void {
2201
+ if (this.pendingOutboundFlushTimer) clearTimeout(this.pendingOutboundFlushTimer);
2202
+ this.pendingOutboundFlushTimer = setTimeout(() => {
2203
+ this.pendingOutboundFlushTimer = null;
2204
+ void this.flushPendingOutboundQueue();
2205
+ }, Math.max(0, delayMs));
2206
+ }
2207
+
2208
+ private async flushPendingOutboundQueue(): Promise<void> {
2209
+ if (this.pendingOutboundFlushInFlight || this.pendingOutboundQueue.length === 0) return;
2210
+ if (this.currentStatus !== 'idle' || this.isWaitingForResponse || this.hasActionableApproval()) return;
2211
+ this.pendingOutboundFlushInFlight = true;
2212
+ try {
2213
+ while (this.pendingOutboundQueue.length > 0) {
2214
+ if (this.currentStatus !== 'idle' || this.isWaitingForResponse || this.hasActionableApproval()) break;
2215
+ const next = this.pendingOutboundQueue[0];
2216
+ this.recordTrace('send_message_queue_flush', {
2217
+ id: next.id,
2218
+ queuedAt: next.queuedAt,
2219
+ queueLength: this.pendingOutboundQueue.length,
2220
+ text: summarizeCliTraceText(next.content, 500),
2221
+ });
2222
+ try {
2223
+ await this.sendMessageNow(next.content, false);
2224
+ this.pendingOutboundQueue.shift();
2225
+ this.onStatusChange?.();
2226
+ } catch (error: any) {
2227
+ LOG.warn('CLI', `[${this.cliType}] queued outbound flush failed: ${error?.message || error}`);
2228
+ this.schedulePendingOutboundFlush(1000);
2229
+ break;
2230
+ }
2231
+ }
2232
+ } finally {
2233
+ this.pendingOutboundFlushInFlight = false;
2234
+ }
2235
+ }
2236
+
2237
+ private async sendMessageNow(text: string, allowQueue: boolean): Promise<void> {
1928
2238
  if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
1929
2239
  const allowInputDuringGeneration = this.provider.allowInputDuringGeneration === true;
1930
2240
  const allowInterventionPrompt = allowInputDuringGeneration
@@ -1937,27 +2247,33 @@ export class ProviderCliAdapter implements CliAdapter {
1937
2247
  await new Promise(resolve => setTimeout(resolve, 50));
1938
2248
  }
1939
2249
  }
2250
+ const parsedStatusBeforeSend = !allowInputDuringGeneration
2251
+ ? (() => {
2252
+ try {
2253
+ return this.getScriptParsedStatus?.() || null;
2254
+ } catch {
2255
+ return null;
2256
+ }
2257
+ })()
2258
+ : null;
2259
+ const queueReason = this.shouldQueuePendingOutboundMessage(parsedStatusBeforeSend);
2260
+ if (allowQueue && queueReason) {
2261
+ this.enqueuePendingOutboundMessage(text, queueReason);
2262
+ return;
2263
+ }
1940
2264
  if (!allowInterventionPrompt) {
1941
2265
  await this.waitForInteractivePrompt();
1942
2266
  }
1943
2267
  if (!this.ready) {
1944
2268
  this.resolveStartupState('send_precheck');
1945
- if (this.runDetectStatus(this.recentOutputBuffer) === 'idle' && this.currentStatus === 'idle') {
2269
+ if (this.runDetectStatus(this.recentOutputBuffer) === 'idle') {
1946
2270
  this.ready = true;
1947
2271
  this.startupParseGate = false;
2272
+ this.setStatus('idle', 'send_message_idle_prompt_recovery');
1948
2273
  LOG.info('CLI', `[${this.cliType}] sendMessage recovered idle prompt readiness`);
1949
2274
  }
1950
2275
  }
1951
2276
  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
2277
  const parsedSessionStatus = typeof parsedStatusBeforeSend?.status === 'string'
1962
2278
  ? String(parsedStatusBeforeSend.status)
1963
2279
  : '';
@@ -1975,11 +2291,22 @@ export class ProviderCliAdapter implements CliAdapter {
1975
2291
  && !this.hasActionableApproval()
1976
2292
  && !parsedHasActionableModal;
1977
2293
  if (!terminalLooksIdle) {
2294
+ if (allowQueue) {
2295
+ this.enqueuePendingOutboundMessage(text, `parsed_status_${parsedSessionStatus}`);
2296
+ return;
2297
+ }
1978
2298
  throw new Error(`${this.cliName} is still processing the previous prompt`);
1979
2299
  }
1980
2300
  }
1981
2301
  if (this.isWaitingForResponse && !allowInputDuringGeneration) {
1982
- if (!this.clearStaleIdleResponseGuard('send_message_guard')) {
2302
+ if (
2303
+ !this.clearStaleIdleResponseGuard('send_message_guard')
2304
+ && !this.clearParsedIdleResponseGuard('send_message_parsed_idle_guard', parsedStatusBeforeSend)
2305
+ ) {
2306
+ if (allowQueue) {
2307
+ this.enqueuePendingOutboundMessage(text, 'waiting_for_response');
2308
+ return;
2309
+ }
1983
2310
  throw new Error(`${this.cliName} is still processing the previous prompt`);
1984
2311
  }
1985
2312
  }
@@ -2230,6 +2557,9 @@ export class ProviderCliAdapter implements CliAdapter {
2230
2557
  this.pendingTerminalQueryTail = '';
2231
2558
  this.ptyOutputChunks = [];
2232
2559
  this.finishRetryCount = 0;
2560
+ if (this.pendingOutboundFlushTimer) { clearTimeout(this.pendingOutboundFlushTimer); this.pendingOutboundFlushTimer = null; }
2561
+ this.pendingOutboundQueue = [];
2562
+ this.pendingOutboundFlushInFlight = false;
2233
2563
  if (this.ptyProcess) {
2234
2564
  this.ptyProcess.write('\x03');
2235
2565
  setTimeout(() => {
@@ -2251,6 +2581,9 @@ export class ProviderCliAdapter implements CliAdapter {
2251
2581
  this.pendingTerminalQueryTail = '';
2252
2582
  this.ptyOutputChunks = [];
2253
2583
  this.finishRetryCount = 0;
2584
+ if (this.pendingOutboundFlushTimer) { clearTimeout(this.pendingOutboundFlushTimer); this.pendingOutboundFlushTimer = null; }
2585
+ this.pendingOutboundQueue = [];
2586
+ this.pendingOutboundFlushInFlight = false;
2254
2587
  if (this.ptyProcess) {
2255
2588
  try {
2256
2589
  if (typeof this.ptyProcess.detach === 'function') {
@@ -2281,6 +2614,9 @@ export class ProviderCliAdapter implements CliAdapter {
2281
2614
  this.ptyOutputChunks = [];
2282
2615
  if (this.finishRetryTimer) { clearTimeout(this.finishRetryTimer); this.finishRetryTimer = null; }
2283
2616
  this.finishRetryCount = 0;
2617
+ if (this.pendingOutboundFlushTimer) { clearTimeout(this.pendingOutboundFlushTimer); this.pendingOutboundFlushTimer = null; }
2618
+ this.pendingOutboundQueue = [];
2619
+ this.pendingOutboundFlushInFlight = false;
2284
2620
  this.resetTerminalScreen();
2285
2621
  this.ptyProcess?.clearBuffer?.();
2286
2622
  this.onStatusChange?.();
@@ -2369,10 +2705,26 @@ export class ProviderCliAdapter implements CliAdapter {
2369
2705
  getDebugState(): Record<string, any> {
2370
2706
  const screenText = sanitizeTerminalText(this.terminalScreen.getText());
2371
2707
  const startupModal = this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
2372
- const effectiveStatus = this.projectEffectiveStatus(startupModal);
2373
- const effectiveReady = this.ready || !!startupModal;
2708
+ const startupDetectedStatus = this.startupParseGate && !startupModal
2709
+ ? this.runDetectStatus(this.recentOutputBuffer || screenText)
2710
+ : null;
2711
+ const effectiveReady = this.ready || !!startupModal || startupDetectedStatus === 'waiting_approval';
2374
2712
  const parsedDebugState = this.getParsedDebugState();
2375
2713
  const parsedMessages = Array.isArray(parsedDebugState?.messages) ? parsedDebugState.messages : [];
2714
+ let effectiveStatus = this.projectEffectiveStatus(startupModal);
2715
+ if (parsedDebugState?.status === 'error') {
2716
+ effectiveStatus = 'error';
2717
+ }
2718
+ if (startupDetectedStatus === 'waiting_approval') {
2719
+ effectiveStatus = 'waiting_approval';
2720
+ }
2721
+ if (
2722
+ effectiveStatus === 'idle'
2723
+ && parsedDebugState?.status === 'generating'
2724
+ && !this.parsedStatusHasFinalAssistantMessage(parsedDebugState)
2725
+ ) {
2726
+ effectiveStatus = 'generating';
2727
+ }
2376
2728
  return {
2377
2729
  type: this.cliType,
2378
2730
  name: this.cliName,
@@ -2394,6 +2746,8 @@ export class ProviderCliAdapter implements CliAdapter {
2394
2746
  providerSessionId: parsedDebugState.providerSessionId,
2395
2747
  transcriptAuthority: parsedDebugState.transcriptAuthority,
2396
2748
  coverage: parsedDebugState.coverage,
2749
+ errorMessage: parsedDebugState.errorMessage,
2750
+ errorReason: parsedDebugState.errorReason,
2397
2751
  activeModal: parsedDebugState.activeModal,
2398
2752
  messageCount: parsedMessages.length,
2399
2753
  } : null,
@@ -2407,6 +2761,14 @@ export class ProviderCliAdapter implements CliAdapter {
2407
2761
  rawBufferPreview: this.accumulatedRawBuffer.slice(-1000),
2408
2762
  sanitizedRawPreview: sanitizeTerminalText(this.accumulatedRawBuffer).slice(-1000),
2409
2763
  responseBuffer: this.responseBuffer.slice(-1000),
2764
+ pendingOutboundQueue: this.pendingOutboundQueue.map((message) => ({
2765
+ id: message.id,
2766
+ role: message.role,
2767
+ content: message.content,
2768
+ queuedAt: message.queuedAt,
2769
+ source: message.source,
2770
+ })),
2771
+ pendingOutboundCount: this.pendingOutboundQueue.length,
2410
2772
  lastOutputAt: this.lastOutputAt,
2411
2773
  lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
2412
2774
  lastScreenChangeAt: this.lastScreenChangeAt,
@@ -15,6 +15,7 @@ export declare function buildCliParseInput(options: {
15
15
  accumulatedRawBuffer: string;
16
16
  recentOutputBuffer: string;
17
17
  terminalScreenText: string;
18
+ workingDir?: string;
18
19
  baseMessages: CliChatMessage[];
19
20
  partialResponse: string;
20
21
  isWaitingForResponse?: boolean;
@@ -35,6 +35,7 @@ export function buildCliParseInput(options: {
35
35
  accumulatedRawBuffer: string;
36
36
  recentOutputBuffer: string;
37
37
  terminalScreenText: string;
38
+ workingDir?: string;
38
39
  baseMessages: CliChatMessage[];
39
40
  partialResponse: string;
40
41
  isWaitingForResponse?: boolean;
@@ -46,6 +47,7 @@ export function buildCliParseInput(options: {
46
47
  accumulatedRawBuffer,
47
48
  recentOutputBuffer,
48
49
  terminalScreenText,
50
+ workingDir,
49
51
  baseMessages,
50
52
  partialResponse,
51
53
  isWaitingForResponse,
@@ -66,6 +68,8 @@ export function buildCliParseInput(options: {
66
68
  rawBuffer,
67
69
  recentBuffer,
68
70
  screenText,
71
+ workspace: workingDir,
72
+ workingDir,
69
73
  screen: buildCliScreenSnapshot(screenText),
70
74
  bufferScreen: buildCliScreenSnapshot(buffer),
71
75
  recentScreen: buildCliScreenSnapshot(recentBuffer),
@@ -36,7 +36,9 @@ export function resolveCliSpawnPlan(options: {
36
36
  : spawnConfig.command;
37
37
  const binaryPath = findBinary(configuredCommand);
38
38
  const isWin = os.platform() === 'win32';
39
- const allArgs = [...spawnConfig.args, ...extraArgs];
39
+ const allArgs = [...spawnConfig.args, ...extraArgs].map((arg) =>
40
+ typeof arg === 'string' ? arg.replace(/\{\{workingDir\}\}/g, workingDir) : arg,
41
+ );
40
42
 
41
43
  let shellCmd: string;
42
44
  let shellArgs: string[];
@@ -55,6 +55,8 @@ export interface CliScriptInput {
55
55
  rawBuffer: string;
56
56
  recentBuffer: string;
57
57
  screenText: string;
58
+ workspace?: string;
59
+ workingDir?: string;
58
60
  screen: CliScreenSnapshot;
59
61
  bufferScreen: CliScreenSnapshot;
60
62
  recentScreen: CliScreenSnapshot;