@adhdev/daemon-core 0.9.82-rc.1 → 0.9.82-rc.100

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 (75) hide show
  1. package/dist/boot/daemon-lifecycle.d.ts +2 -0
  2. package/dist/cli-adapters/provider-cli-adapter.d.ts +10 -0
  3. package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
  4. package/dist/cli-adapters/provider-cli-shared.d.ts +10 -0
  5. package/dist/commands/router.d.ts +24 -0
  6. package/dist/config/mesh-config.d.ts +66 -1
  7. package/dist/git/git-commands.d.ts +1 -0
  8. package/dist/git/git-status.d.ts +5 -0
  9. package/dist/git/git-types.d.ts +10 -0
  10. package/dist/index.d.ts +13 -6
  11. package/dist/index.js +6068 -1214
  12. package/dist/index.js.map +1 -1
  13. package/dist/index.mjs +6033 -1201
  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 +60 -0
  20. package/dist/mesh/mesh-events.d.ts +29 -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-work-queue.d.ts +27 -5
  25. package/dist/mesh/refine-config.d.ts +176 -0
  26. package/dist/providers/chat-message-normalization.d.ts +1 -0
  27. package/dist/providers/cli-provider-instance.d.ts +2 -1
  28. package/dist/repo-mesh-types.d.ts +167 -0
  29. package/dist/status/reporter.d.ts +2 -0
  30. package/package.json +3 -1
  31. package/src/boot/daemon-lifecycle.ts +4 -0
  32. package/src/cli-adapters/provider-cli-adapter.ts +255 -14
  33. package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
  34. package/src/cli-adapters/provider-cli-parse.ts +4 -0
  35. package/src/cli-adapters/provider-cli-runtime.ts +3 -1
  36. package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
  37. package/src/cli-adapters/provider-cli-shared.ts +28 -10
  38. package/src/commands/chat-commands.ts +570 -20
  39. package/src/commands/cli-manager.ts +129 -1
  40. package/src/commands/handler.ts +8 -1
  41. package/src/commands/mesh-coordinator.ts +13 -143
  42. package/src/commands/router.ts +3095 -406
  43. package/src/config/chat-history.ts +9 -7
  44. package/src/config/mesh-config.ts +245 -1
  45. package/src/daemon/dev-cli-debug.ts +10 -1
  46. package/src/detection/ide-detector.ts +26 -16
  47. package/src/git/git-commands.ts +3 -3
  48. package/src/git/git-status.ts +97 -6
  49. package/src/git/git-summary.ts +3 -0
  50. package/src/git/git-types.ts +11 -0
  51. package/src/index.ts +39 -5
  52. package/src/installer.d.ts +1 -1
  53. package/src/installer.ts +8 -6
  54. package/src/launch.d.ts +1 -1
  55. package/src/launch.ts +37 -28
  56. package/src/logging/async-batch-writer.ts +55 -0
  57. package/src/logging/logger.ts +2 -1
  58. package/src/mesh/beads-db.ts +176 -0
  59. package/src/mesh/coordinator-prompt.ts +31 -8
  60. package/src/mesh/mesh-active-work.ts +255 -0
  61. package/src/mesh/mesh-events.ts +400 -47
  62. package/src/mesh/mesh-fast-forward.ts +430 -0
  63. package/src/mesh/mesh-host-ownership.ts +73 -0
  64. package/src/mesh/mesh-ledger.ts +138 -1
  65. package/src/mesh/mesh-work-queue.ts +199 -137
  66. package/src/mesh/refine-config.ts +356 -0
  67. package/src/providers/chat-message-normalization.ts +7 -12
  68. package/src/providers/cli-provider-instance.ts +93 -14
  69. package/src/providers/ide-provider-instance.ts +17 -3
  70. package/src/providers/provider-loader.ts +10 -4
  71. package/src/providers/read-chat-contract.ts +1 -1
  72. package/src/providers/version-archive.ts +38 -20
  73. package/src/repo-mesh-types.ts +182 -0
  74. package/src/status/reporter.ts +15 -0
  75. 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 '';
@@ -186,6 +194,9 @@ export class ProviderCliAdapter implements CliAdapter {
186
194
  private idleFinishCandidate: IdleFinishCandidate | null = null;
187
195
  private finishRetryTimer: NodeJS.Timeout | null = null;
188
196
  private finishRetryCount = 0;
197
+ private pendingOutboundQueue: PendingOutboundMessage[] = [];
198
+ private pendingOutboundFlushTimer: NodeJS.Timeout | null = null;
199
+ private pendingOutboundFlushInFlight = false;
189
200
 
190
201
  // Resize redraw suppression
191
202
  private resizeSuppressUntil: number = 0;
@@ -761,6 +772,17 @@ export class ProviderCliAdapter implements CliAdapter {
761
772
  if (stableMs < 2000) return;
762
773
 
763
774
  const startupModal = this.runParseApproval(this.recentOutputBuffer);
775
+ const startupStatus = this.runDetectStatus(screenText || this.recentOutputBuffer);
776
+ if (!startupModal && startupStatus !== 'idle') {
777
+ this.recordTrace('startup_settle_deferred', {
778
+ trigger,
779
+ startupStatus,
780
+ stableMs,
781
+ screenText: summarizeCliTraceText(screenText, 500),
782
+ });
783
+ this.scheduleStartupSettleCheck();
784
+ return;
785
+ }
764
786
  this.startupParseGate = false;
765
787
  if (this.startupSettleTimer) {
766
788
  clearTimeout(this.startupSettleTimer);
@@ -956,6 +978,38 @@ export class ProviderCliAdapter implements CliAdapter {
956
978
  return true;
957
979
  }
958
980
 
981
+ private clearParsedIdleResponseGuard(reason: string, parsedStatus: any): boolean {
982
+ const parsedRawStatus = typeof parsedStatus?.status === 'string' ? parsedStatus.status.trim() : '';
983
+ const parsedModal = parsedStatus?.activeModal ?? parsedStatus?.modal ?? null;
984
+ const blockingModal = this.activeModal || this.runParseApproval(this.recentOutputBuffer);
985
+ if (
986
+ !this.isWaitingForResponse
987
+ || parsedRawStatus !== 'idle'
988
+ || !!parsedModal
989
+ || !!blockingModal
990
+ || !this.parsedStatusHasFinalAssistantMessage(parsedStatus)
991
+ ) {
992
+ return false;
993
+ }
994
+ this.clearAllTimers();
995
+ this.clearIdleFinishCandidate(reason);
996
+ this.responseBuffer = '';
997
+ this.isWaitingForResponse = false;
998
+ this.responseSettleIgnoreUntil = 0;
999
+ this.submitRetryUsed = false;
1000
+ this.submitRetryPromptSnippet = '';
1001
+ this.finishRetryCount = 0;
1002
+ this.currentTurnScope = null;
1003
+ this.activeModal = null;
1004
+ this.setStatus('idle', reason);
1005
+ this.recordTrace('parsed_idle_response_cleared', {
1006
+ reason,
1007
+ parsedStatus: parsedRawStatus,
1008
+ parsedMessageCount: Array.isArray(parsedStatus?.messages) ? parsedStatus.messages.length : 0,
1009
+ });
1010
+ return true;
1011
+ }
1012
+
959
1013
  private hasMeaningfulResponseBuffer(promptSnippet: string): boolean {
960
1014
  const raw = String(this.responseBuffer || '').trim();
961
1015
  if (!raw) return false;
@@ -1372,6 +1426,7 @@ export class ProviderCliAdapter implements CliAdapter {
1372
1426
  this.activeModal = null;
1373
1427
  this.setStatus('idle', 'response_finished');
1374
1428
  this.onStatusChange?.();
1429
+ this.schedulePendingOutboundFlush();
1375
1430
  }
1376
1431
 
1377
1432
  private maybeCommitVisibleIdleTranscript(session: ParsedSession, parsedMessages: CliChatMessage[]): boolean {
@@ -1402,6 +1457,7 @@ export class ProviderCliAdapter implements CliAdapter {
1402
1457
  this.activeModal = null;
1403
1458
  this.setStatus('idle', 'script_idle_commit');
1404
1459
  this.onStatusChange?.();
1460
+ this.schedulePendingOutboundFlush();
1405
1461
  this.recordTrace('script_idle_commit', {
1406
1462
  messageCount: parsedMessages.length,
1407
1463
  lastAssistant: summarizeCliTraceText(visibleAssistant.content, 320),
@@ -1489,6 +1545,7 @@ export class ProviderCliAdapter implements CliAdapter {
1489
1545
  accumulatedRawBuffer: this.accumulatedRawBuffer,
1490
1546
  recentOutputBuffer: this.recentOutputBuffer,
1491
1547
  terminalScreenText: parseScreenText,
1548
+ workingDir: this.workingDir,
1492
1549
  baseMessages: [],
1493
1550
  partialResponse: this.responseBuffer,
1494
1551
  isWaitingForResponse: this.isWaitingForResponse,
@@ -1552,6 +1609,15 @@ export class ProviderCliAdapter implements CliAdapter {
1552
1609
  return !!(startupModal || this.activeModal);
1553
1610
  }
1554
1611
 
1612
+ private parsedStatusHasFinalAssistantMessage(parsed: any): boolean {
1613
+ const messages = Array.isArray(parsed?.messages) ? parsed.messages : [];
1614
+ const lastAssistant = [...messages].reverse().find((message: any) => {
1615
+ if (!message || message.role !== 'assistant') return false;
1616
+ return typeof message.content === 'string' && message.content.trim().length > 0;
1617
+ });
1618
+ return !!lastAssistant;
1619
+ }
1620
+
1555
1621
  private projectEffectiveStatus(startupModal: { message: string; buttons: string[] } | null = null): CliSessionStatus['status'] {
1556
1622
  if (this.parseErrorMessage) return 'error';
1557
1623
  if (this.hasActionableApproval(startupModal)) return 'waiting_approval';
@@ -1564,8 +1630,16 @@ export class ProviderCliAdapter implements CliAdapter {
1564
1630
  getStatus(options: { allowParse?: boolean } = {}): CliSessionStatus {
1565
1631
  const allowParse = options.allowParse !== false;
1566
1632
  const startupModal = allowParse && this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
1633
+ const startupDetectedStatus = allowParse && this.startupParseGate && !startupModal
1634
+ ? this.runDetectStatus(this.recentOutputBuffer || this.terminalScreen.getText())
1635
+ : null;
1567
1636
  let effectiveStatus = this.projectEffectiveStatus(startupModal);
1568
1637
  let effectiveModal = startupModal || this.activeModal;
1638
+ if (startupDetectedStatus === 'waiting_approval') {
1639
+ effectiveStatus = 'waiting_approval';
1640
+ } else if (startupDetectedStatus === 'idle' && !startupModal && !effectiveModal) {
1641
+ effectiveStatus = 'idle';
1642
+ }
1569
1643
  if (allowParse && !startupModal && !effectiveModal) {
1570
1644
  const parsed = this.getFreshParsedStatusCache();
1571
1645
  const parsedModal = parsed?.activeModal && Array.isArray(parsed.activeModal.buttons)
@@ -1575,6 +1649,18 @@ export class ProviderCliAdapter implements CliAdapter {
1575
1649
  if (parsed?.status === 'waiting_approval' && parsedModal) {
1576
1650
  effectiveStatus = 'waiting_approval';
1577
1651
  effectiveModal = parsedModal;
1652
+ } else if (
1653
+ effectiveStatus === 'idle'
1654
+ && parsed?.status === 'generating'
1655
+ && !this.parsedStatusHasFinalAssistantMessage(parsed)
1656
+ ) {
1657
+ effectiveStatus = 'generating';
1658
+ } else if (
1659
+ effectiveStatus === 'generating'
1660
+ && parsed?.status === 'idle'
1661
+ && this.parsedStatusHasFinalAssistantMessage(parsed)
1662
+ ) {
1663
+ effectiveStatus = 'idle';
1578
1664
  }
1579
1665
  }
1580
1666
  const bufferState = this.getBufferState();
@@ -1583,6 +1669,14 @@ export class ProviderCliAdapter implements CliAdapter {
1583
1669
  messages: [],
1584
1670
  workingDir: this.workingDir,
1585
1671
  activeModal: effectiveModal,
1672
+ pendingOutboundCount: this.pendingOutboundQueue.length,
1673
+ pendingOutboundMessages: this.pendingOutboundQueue.map((message) => ({
1674
+ id: message.id,
1675
+ role: message.role,
1676
+ content: message.content,
1677
+ queuedAt: message.queuedAt,
1678
+ source: message.source,
1679
+ })),
1586
1680
  errorMessage: this.parseErrorMessage || undefined,
1587
1681
  errorReason: this.parseErrorMessage ? 'parse_error' : undefined,
1588
1682
  ...(bufferState ? { bufferState } : {}),
@@ -1600,7 +1694,8 @@ export class ProviderCliAdapter implements CliAdapter {
1600
1694
  const cached = this.parsedStatusCache;
1601
1695
  const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
1602
1696
  if (
1603
- cached
1697
+ !this.providerOwnsTranscript()
1698
+ && cached
1604
1699
  && cached.responseBuffer === this.responseBuffer
1605
1700
  && cached.currentTurnScope === this.currentTurnScope
1606
1701
  && cached.recentOutputBuffer === this.recentOutputBuffer
@@ -1665,6 +1760,7 @@ export class ProviderCliAdapter implements CliAdapter {
1665
1760
  accumulatedRawBuffer: this.accumulatedRawBuffer,
1666
1761
  recentOutputBuffer: this.recentOutputBuffer,
1667
1762
  terminalScreenText: this.getParseScreenText(this.terminalScreen.getText()),
1763
+ workingDir: this.workingDir,
1668
1764
  baseMessages: [],
1669
1765
  partialResponse: this.responseBuffer,
1670
1766
  isWaitingForResponse: this.isWaitingForResponse,
@@ -1925,6 +2021,104 @@ export class ProviderCliAdapter implements CliAdapter {
1925
2021
  }
1926
2022
 
1927
2023
  async sendMessage(text: string): Promise<void> {
2024
+ await this.sendMessageNow(text, true);
2025
+ }
2026
+
2027
+ private enqueuePendingOutboundMessage(text: string, reason: string): void {
2028
+ const content = String(text || '');
2029
+ const duplicate = this.pendingOutboundQueue.some((message) => message.content === content);
2030
+ if (duplicate) {
2031
+ this.recordTrace('send_message_queued_duplicate_suppressed', {
2032
+ reason,
2033
+ queueLength: this.pendingOutboundQueue.length,
2034
+ text: summarizeCliTraceText(content, 500),
2035
+ });
2036
+ return;
2037
+ }
2038
+ const queuedAt = Date.now();
2039
+ const message: PendingOutboundMessage = {
2040
+ id: `${queuedAt}:${this.pendingOutboundQueue.length}:${Math.random().toString(36).slice(2, 10)}`,
2041
+ role: 'user',
2042
+ content,
2043
+ queuedAt,
2044
+ source: 'sendMessage',
2045
+ };
2046
+ this.pendingOutboundQueue.push(message);
2047
+ this.recordTrace('send_message_queued', {
2048
+ reason,
2049
+ queueLength: this.pendingOutboundQueue.length,
2050
+ queuedAt,
2051
+ text: summarizeCliTraceText(content, 500),
2052
+ });
2053
+ LOG.info('CLI', `[${this.cliType}] queued outbound message while busy (${reason}); queue=${this.pendingOutboundQueue.length}`);
2054
+ this.onStatusChange?.();
2055
+ }
2056
+
2057
+ private shouldQueuePendingOutboundMessage(parsedStatusBeforeSend: any | null = null): string | null {
2058
+ if (this.provider.allowInputDuringGeneration === true) return null;
2059
+ if (this.hasActionableApproval()) return null;
2060
+ const parsedSessionStatus = typeof parsedStatusBeforeSend?.status === 'string'
2061
+ ? String(parsedStatusBeforeSend.status)
2062
+ : '';
2063
+ if (parsedSessionStatus === 'idle' && this.parsedStatusHasFinalAssistantMessage(parsedStatusBeforeSend)) return null;
2064
+ if (this.currentStatus === 'generating') return 'current_status_generating';
2065
+ if (parsedSessionStatus === 'generating' || parsedSessionStatus === 'long_generating') {
2066
+ const parsedModal = parsedStatusBeforeSend?.activeModal ?? parsedStatusBeforeSend?.modal ?? null;
2067
+ const parsedHasActionableModal = Boolean(
2068
+ parsedModal
2069
+ && Array.isArray(parsedModal.buttons)
2070
+ && parsedModal.buttons.some((candidate: unknown) => typeof candidate === 'string' && candidate.trim()),
2071
+ );
2072
+ const terminalLooksIdle = this.currentStatus === 'idle'
2073
+ && this.runDetectStatus(this.recentOutputBuffer) === 'idle'
2074
+ && !this.isWaitingForResponse
2075
+ && !this.currentTurnScope
2076
+ && !this.hasActionableApproval()
2077
+ && !parsedHasActionableModal;
2078
+ return terminalLooksIdle ? null : `parsed_status_${parsedSessionStatus}`;
2079
+ }
2080
+ if (this.isWaitingForResponse && this.currentTurnScope) return 'active_turn_in_progress';
2081
+ return null;
2082
+ }
2083
+
2084
+ private schedulePendingOutboundFlush(delayMs = 0): void {
2085
+ if (this.pendingOutboundFlushTimer) clearTimeout(this.pendingOutboundFlushTimer);
2086
+ this.pendingOutboundFlushTimer = setTimeout(() => {
2087
+ this.pendingOutboundFlushTimer = null;
2088
+ void this.flushPendingOutboundQueue();
2089
+ }, Math.max(0, delayMs));
2090
+ }
2091
+
2092
+ private async flushPendingOutboundQueue(): Promise<void> {
2093
+ if (this.pendingOutboundFlushInFlight || this.pendingOutboundQueue.length === 0) return;
2094
+ if (this.currentStatus !== 'idle' || this.isWaitingForResponse || this.hasActionableApproval()) return;
2095
+ this.pendingOutboundFlushInFlight = true;
2096
+ try {
2097
+ while (this.pendingOutboundQueue.length > 0) {
2098
+ if (this.currentStatus !== 'idle' || this.isWaitingForResponse || this.hasActionableApproval()) break;
2099
+ const next = this.pendingOutboundQueue[0];
2100
+ this.recordTrace('send_message_queue_flush', {
2101
+ id: next.id,
2102
+ queuedAt: next.queuedAt,
2103
+ queueLength: this.pendingOutboundQueue.length,
2104
+ text: summarizeCliTraceText(next.content, 500),
2105
+ });
2106
+ try {
2107
+ await this.sendMessageNow(next.content, false);
2108
+ this.pendingOutboundQueue.shift();
2109
+ this.onStatusChange?.();
2110
+ } catch (error: any) {
2111
+ LOG.warn('CLI', `[${this.cliType}] queued outbound flush failed: ${error?.message || error}`);
2112
+ this.schedulePendingOutboundFlush(1000);
2113
+ break;
2114
+ }
2115
+ }
2116
+ } finally {
2117
+ this.pendingOutboundFlushInFlight = false;
2118
+ }
2119
+ }
2120
+
2121
+ private async sendMessageNow(text: string, allowQueue: boolean): Promise<void> {
1928
2122
  if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
1929
2123
  const allowInputDuringGeneration = this.provider.allowInputDuringGeneration === true;
1930
2124
  const allowInterventionPrompt = allowInputDuringGeneration
@@ -1937,27 +2131,33 @@ export class ProviderCliAdapter implements CliAdapter {
1937
2131
  await new Promise(resolve => setTimeout(resolve, 50));
1938
2132
  }
1939
2133
  }
2134
+ const parsedStatusBeforeSend = !allowInputDuringGeneration
2135
+ ? (() => {
2136
+ try {
2137
+ return this.getScriptParsedStatus?.() || null;
2138
+ } catch {
2139
+ return null;
2140
+ }
2141
+ })()
2142
+ : null;
2143
+ const queueReason = this.shouldQueuePendingOutboundMessage(parsedStatusBeforeSend);
2144
+ if (allowQueue && queueReason) {
2145
+ this.enqueuePendingOutboundMessage(text, queueReason);
2146
+ return;
2147
+ }
1940
2148
  if (!allowInterventionPrompt) {
1941
2149
  await this.waitForInteractivePrompt();
1942
2150
  }
1943
2151
  if (!this.ready) {
1944
2152
  this.resolveStartupState('send_precheck');
1945
- if (this.runDetectStatus(this.recentOutputBuffer) === 'idle' && this.currentStatus === 'idle') {
2153
+ if (this.runDetectStatus(this.recentOutputBuffer) === 'idle') {
1946
2154
  this.ready = true;
1947
2155
  this.startupParseGate = false;
2156
+ this.setStatus('idle', 'send_message_idle_prompt_recovery');
1948
2157
  LOG.info('CLI', `[${this.cliType}] sendMessage recovered idle prompt readiness`);
1949
2158
  }
1950
2159
  }
1951
2160
  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
2161
  const parsedSessionStatus = typeof parsedStatusBeforeSend?.status === 'string'
1962
2162
  ? String(parsedStatusBeforeSend.status)
1963
2163
  : '';
@@ -1975,11 +2175,22 @@ export class ProviderCliAdapter implements CliAdapter {
1975
2175
  && !this.hasActionableApproval()
1976
2176
  && !parsedHasActionableModal;
1977
2177
  if (!terminalLooksIdle) {
2178
+ if (allowQueue) {
2179
+ this.enqueuePendingOutboundMessage(text, `parsed_status_${parsedSessionStatus}`);
2180
+ return;
2181
+ }
1978
2182
  throw new Error(`${this.cliName} is still processing the previous prompt`);
1979
2183
  }
1980
2184
  }
1981
2185
  if (this.isWaitingForResponse && !allowInputDuringGeneration) {
1982
- if (!this.clearStaleIdleResponseGuard('send_message_guard')) {
2186
+ if (
2187
+ !this.clearStaleIdleResponseGuard('send_message_guard')
2188
+ && !this.clearParsedIdleResponseGuard('send_message_parsed_idle_guard', parsedStatusBeforeSend)
2189
+ ) {
2190
+ if (allowQueue) {
2191
+ this.enqueuePendingOutboundMessage(text, 'waiting_for_response');
2192
+ return;
2193
+ }
1983
2194
  throw new Error(`${this.cliName} is still processing the previous prompt`);
1984
2195
  }
1985
2196
  }
@@ -2230,6 +2441,9 @@ export class ProviderCliAdapter implements CliAdapter {
2230
2441
  this.pendingTerminalQueryTail = '';
2231
2442
  this.ptyOutputChunks = [];
2232
2443
  this.finishRetryCount = 0;
2444
+ if (this.pendingOutboundFlushTimer) { clearTimeout(this.pendingOutboundFlushTimer); this.pendingOutboundFlushTimer = null; }
2445
+ this.pendingOutboundQueue = [];
2446
+ this.pendingOutboundFlushInFlight = false;
2233
2447
  if (this.ptyProcess) {
2234
2448
  this.ptyProcess.write('\x03');
2235
2449
  setTimeout(() => {
@@ -2251,6 +2465,9 @@ export class ProviderCliAdapter implements CliAdapter {
2251
2465
  this.pendingTerminalQueryTail = '';
2252
2466
  this.ptyOutputChunks = [];
2253
2467
  this.finishRetryCount = 0;
2468
+ if (this.pendingOutboundFlushTimer) { clearTimeout(this.pendingOutboundFlushTimer); this.pendingOutboundFlushTimer = null; }
2469
+ this.pendingOutboundQueue = [];
2470
+ this.pendingOutboundFlushInFlight = false;
2254
2471
  if (this.ptyProcess) {
2255
2472
  try {
2256
2473
  if (typeof this.ptyProcess.detach === 'function') {
@@ -2281,6 +2498,9 @@ export class ProviderCliAdapter implements CliAdapter {
2281
2498
  this.ptyOutputChunks = [];
2282
2499
  if (this.finishRetryTimer) { clearTimeout(this.finishRetryTimer); this.finishRetryTimer = null; }
2283
2500
  this.finishRetryCount = 0;
2501
+ if (this.pendingOutboundFlushTimer) { clearTimeout(this.pendingOutboundFlushTimer); this.pendingOutboundFlushTimer = null; }
2502
+ this.pendingOutboundQueue = [];
2503
+ this.pendingOutboundFlushInFlight = false;
2284
2504
  this.resetTerminalScreen();
2285
2505
  this.ptyProcess?.clearBuffer?.();
2286
2506
  this.onStatusChange?.();
@@ -2369,10 +2589,23 @@ export class ProviderCliAdapter implements CliAdapter {
2369
2589
  getDebugState(): Record<string, any> {
2370
2590
  const screenText = sanitizeTerminalText(this.terminalScreen.getText());
2371
2591
  const startupModal = this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
2372
- const effectiveStatus = this.projectEffectiveStatus(startupModal);
2373
- const effectiveReady = this.ready || !!startupModal;
2592
+ const startupDetectedStatus = this.startupParseGate && !startupModal
2593
+ ? this.runDetectStatus(this.recentOutputBuffer || screenText)
2594
+ : null;
2595
+ const effectiveReady = this.ready || !!startupModal || startupDetectedStatus === 'waiting_approval';
2374
2596
  const parsedDebugState = this.getParsedDebugState();
2375
2597
  const parsedMessages = Array.isArray(parsedDebugState?.messages) ? parsedDebugState.messages : [];
2598
+ let effectiveStatus = this.projectEffectiveStatus(startupModal);
2599
+ if (startupDetectedStatus === 'waiting_approval') {
2600
+ effectiveStatus = 'waiting_approval';
2601
+ }
2602
+ if (
2603
+ effectiveStatus === 'idle'
2604
+ && parsedDebugState?.status === 'generating'
2605
+ && !this.parsedStatusHasFinalAssistantMessage(parsedDebugState)
2606
+ ) {
2607
+ effectiveStatus = 'generating';
2608
+ }
2376
2609
  return {
2377
2610
  type: this.cliType,
2378
2611
  name: this.cliName,
@@ -2407,6 +2640,14 @@ export class ProviderCliAdapter implements CliAdapter {
2407
2640
  rawBufferPreview: this.accumulatedRawBuffer.slice(-1000),
2408
2641
  sanitizedRawPreview: sanitizeTerminalText(this.accumulatedRawBuffer).slice(-1000),
2409
2642
  responseBuffer: this.responseBuffer.slice(-1000),
2643
+ pendingOutboundQueue: this.pendingOutboundQueue.map((message) => ({
2644
+ id: message.id,
2645
+ role: message.role,
2646
+ content: message.content,
2647
+ queuedAt: message.queuedAt,
2648
+ source: message.source,
2649
+ })),
2650
+ pendingOutboundCount: this.pendingOutboundQueue.length,
2410
2651
  lastOutputAt: this.lastOutputAt,
2411
2652
  lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
2412
2653
  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;
@@ -28,6 +28,14 @@ export interface CliSessionStatus {
28
28
  messages: CliChatMessage[];
29
29
  workingDir: string;
30
30
  activeModal: { message: string; buttons: string[] } | null;
31
+ pendingOutboundCount?: number;
32
+ pendingOutboundMessages?: Array<{
33
+ id: string;
34
+ role: 'user';
35
+ content: string;
36
+ queuedAt: number;
37
+ source: string;
38
+ }>;
31
39
  errorMessage?: string;
32
40
  errorReason?: string;
33
41
  bufferState?: {
@@ -94,6 +102,8 @@ export interface CliScriptInput {
94
102
  rawBuffer: string;
95
103
  recentBuffer: string;
96
104
  screenText: string;
105
+ workspace?: string;
106
+ workingDir?: string;
97
107
  screen: CliScreenSnapshot;
98
108
  bufferScreen: CliScreenSnapshot;
99
109
  recentScreen: CliScreenSnapshot;
@@ -484,17 +494,25 @@ export function findBinary(name: string): string {
484
494
  return path.isAbsolute(expanded) ? expanded : path.resolve(expanded);
485
495
  }
486
496
  const isWin = os.platform() === 'win32';
487
- try {
488
- const cmd = isWin ? `where ${trimmed}` : `which ${trimmed}`;
489
- return execSync(cmd, {
490
- encoding: 'utf-8',
491
- timeout: 5000,
492
- stdio: ['pipe', 'pipe', 'pipe'],
493
- ...(isWin ? { windowsHide: true } : {}),
494
- }).trim().split('\n')[0].trim();
495
- } catch {
496
- return isWin ? `${trimmed}.cmd` : trimmed;
497
+ const paths = (process.env.PATH || '').split(path.delimiter);
498
+ const exes = isWin ? ['.exe', '.cmd', '.bat', ''] : [''];
499
+
500
+ for (const p of paths) {
501
+ if (!p) continue;
502
+ for (const ext of exes) {
503
+ const fullPath = path.join(p, trimmed + ext);
504
+ try {
505
+ const fs = require('fs');
506
+ if (fs.existsSync(fullPath)) {
507
+ const stat = fs.statSync(fullPath);
508
+ if (stat.isFile() && (isWin || (stat.mode & 0o111))) {
509
+ return fullPath;
510
+ }
511
+ }
512
+ } catch { }
513
+ }
497
514
  }
515
+ return isWin ? `${trimmed}.cmd` : trimmed;
498
516
  }
499
517
 
500
518
  export function isScriptBinary(binaryPath: string): boolean {