@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
@@ -12,8 +12,9 @@ import type { CliAdapter } from '../cli-adapter-types.js';
12
12
  import { flattenContent, normalizeInputEnvelope, type InputEnvelope, type ProviderModule, type ProviderScripts } from '../providers/contracts.js';
13
13
  import { assertProviderSupportsDeclaredInput, assertTextOnlyInput } from '../providers/provider-input-support.js';
14
14
  import { validateReadChatResultPayload } from '../providers/read-chat-contract.js';
15
+ import { pickApprovalButton } from '../providers/approval-utils.js';
15
16
  import type { ProviderInstance } from '../providers/provider-instance.js';
16
- import { readProviderChatHistory } from '../config/chat-history.js';
17
+ import { isNativeSourceCanonicalHistory, readProviderChatHistory } from '../config/chat-history.js';
17
18
  import { LOG, getRecentLogs } from '../logging/logger.js';
18
19
  import { getRecentDebugTrace, recordDebugTrace } from '../logging/debug-trace.js';
19
20
  import { buildChatMessageSignature, hashSignatureParts } from '../chat/chat-signatures.js';
@@ -24,6 +25,8 @@ import { filterUserFacingChatMessages, normalizeChatMessages } from '../provider
24
25
  const RECENT_SEND_WINDOW_MS = 1200;
25
26
  export const READ_CHAT_PROVIDER_EVAL_TIMEOUT_MS = 25_000;
26
27
  const HERMES_CLI_STARTING_SEND_SETTLE_MS = 2_000;
28
+ const CLI_NATIVE_HISTORY_FRESH_MS = 5 * 60_000;
29
+ const CLI_NATIVE_TRANSCRIPT_PROVIDERS = new Set(['codex-cli', 'claude-cli', 'hermes-cli', 'antigravity-cli']);
27
30
  const recentSendByTarget = new Map<string, number>();
28
31
 
29
32
  interface ApprovalSelectableInstance extends ProviderInstance {
@@ -48,6 +51,16 @@ function getTargetedCliAdapter(h: CommandHelpers, args: any, providerType?: stri
48
51
  return h.getCliAdapter(args?.targetSessionId || providerType || h.currentSession?.providerType || h.currentManagerKey);
49
52
  }
50
53
 
54
+ function getExplicitHistorySessionId(args: any): string | undefined {
55
+ const explicit = typeof args?.historySessionId === 'string' ? args.historySessionId.trim() : '';
56
+ if (explicit) return explicit;
57
+
58
+ const explicitProviderSessionId = typeof args?.providerSessionId === 'string' ? args.providerSessionId.trim() : '';
59
+ if (explicitProviderSessionId) return explicitProviderSessionId;
60
+
61
+ return undefined;
62
+ }
63
+
51
64
  function getTargetInstance(h: CommandHelpers, args: any): ApprovalSelectableInstance | null {
52
65
  const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
53
66
  const sessionId = targetSessionId || h.currentSession?.sessionId || '';
@@ -139,19 +152,47 @@ async function waitOnceForFreshHermesCliStart(adapter: CliAdapter, log: (msg: st
139
152
  }
140
153
 
141
154
  function getHistorySessionId(h: CommandHelpers, args: any): string | undefined {
142
- const explicit = typeof args?.historySessionId === 'string' ? args.historySessionId.trim() : '';
155
+ const explicit = getExplicitHistorySessionId(args);
143
156
  if (explicit) return explicit;
144
157
 
145
- const explicitProviderSessionId = typeof args?.providerSessionId === 'string' ? args.providerSessionId.trim() : '';
146
- if (explicitProviderSessionId) return explicitProviderSessionId;
147
-
148
158
  const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
149
159
  if (!targetSessionId) return undefined;
150
160
 
151
- const instance = h.ctx.instanceManager?.getInstance(targetSessionId);
161
+ const session = h.ctx.sessionRegistry?.get(targetSessionId) as any;
162
+ const registeredProviderSessionId = typeof session?.providerSessionId === 'string' ? session.providerSessionId.trim() : '';
163
+ if (registeredProviderSessionId) return registeredProviderSessionId;
164
+
165
+ const instance = getTargetInstance(h, args);
152
166
  const state = instance?.getState?.();
153
167
  const providerSessionId = typeof state?.providerSessionId === 'string' ? state.providerSessionId.trim() : '';
154
- return providerSessionId || targetSessionId;
168
+ if (providerSessionId) return providerSessionId;
169
+
170
+ const currentSession = h.currentSession as any;
171
+ if (currentSession?.sessionId === targetSessionId) {
172
+ const currentProviderSessionId = typeof currentSession.providerSessionId === 'string'
173
+ ? currentSession.providerSessionId.trim()
174
+ : '';
175
+ if (currentProviderSessionId) return currentProviderSessionId;
176
+ }
177
+
178
+ return targetSessionId;
179
+ }
180
+
181
+ function resolveCliNativeHistorySessionId(args: any, currentHistorySessionId: string | undefined, parsedProviderSessionId: string | undefined): string | undefined {
182
+ const explicit = getExplicitHistorySessionId(args);
183
+ if (explicit) return explicit;
184
+
185
+ const parsed = typeof parsedProviderSessionId === 'string' ? parsedProviderSessionId.trim() : '';
186
+ const current = typeof currentHistorySessionId === 'string' ? currentHistorySessionId.trim() : '';
187
+ const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
188
+
189
+ // getHistorySessionId falls back to the runtime session id when no native
190
+ // handle has been registered yet. For live CLI adapters the parser may
191
+ // already know the provider-native handle; prefer it over the runtime id so
192
+ // exact native reads do not miss the worker transcript and fall back to PTY
193
+ // or same-workspace history.
194
+ if (parsed && (!current || current === targetSessionId)) return parsed;
195
+ return current || parsed || undefined;
155
196
  }
156
197
 
157
198
  function getInteractionId(args: any): string | undefined {
@@ -221,6 +262,272 @@ function normalizeReadChatMessages(payload: Record<string, any>): ChatMessage[]
221
262
  return normalizeChatMessages(messages);
222
263
  }
223
264
 
265
+ function getMessageNewestReceivedAt(messages: Array<{ receivedAt?: unknown; timestamp?: unknown }>): number {
266
+ let newest = 0;
267
+ for (const message of messages) {
268
+ const receivedAt = Number(message?.receivedAt ?? message?.timestamp ?? 0);
269
+ if (Number.isFinite(receivedAt) && receivedAt > newest) newest = receivedAt;
270
+ }
271
+ return newest;
272
+ }
273
+
274
+ function readHistorySessionIdFromMessages(messages: ChatMessage[]): string | undefined {
275
+ for (const message of messages as Array<ChatMessage & { historySessionId?: unknown }>) {
276
+ const historySessionId = typeof message?.historySessionId === 'string' ? message.historySessionId.trim() : '';
277
+ if (historySessionId) return historySessionId;
278
+ }
279
+ return undefined;
280
+ }
281
+
282
+ function normalizeNativeHistoryMessages(providerType: string, messages: ChatMessage[]): ChatMessage[] {
283
+ let turnIndex = 0;
284
+ return normalizeChatMessages(messages).map((message, index) => {
285
+ const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
286
+ const kind = typeof message.kind === 'string' && message.kind.trim() ? message.kind.trim() : (role === 'system' ? 'system' : 'standard');
287
+ if ((role === 'user' || role === 'human') && index > 0) turnIndex += 1;
288
+ const historySessionId = typeof (message as any).historySessionId === 'string'
289
+ ? (message as any).historySessionId.trim()
290
+ : '';
291
+ const contentHash = hashSignatureParts([
292
+ providerType,
293
+ historySessionId,
294
+ String(message.receivedAt || message.timestamp || index),
295
+ role,
296
+ kind,
297
+ flattenContent(message.content),
298
+ ]).slice(0, 12);
299
+ const providerUnitKey = typeof message.providerUnitKey === 'string' && message.providerUnitKey.trim()
300
+ ? message.providerUnitKey.trim()
301
+ : `${providerType}:native:${historySessionId || 'workspace'}:${index}:${role || 'message'}:${kind}:${contentHash}`;
302
+ const meta = message.meta && typeof message.meta === 'object' ? message.meta as Record<string, unknown> : undefined;
303
+ const isSystemSessionStart = role === 'system' || kind === 'system' || kind === 'session_start';
304
+ const isActivity = role === 'assistant' && (kind === 'tool' || kind === 'terminal' || kind === 'thought');
305
+ return {
306
+ ...message,
307
+ role: role === 'human' ? 'user' : (role || 'assistant'),
308
+ kind: isSystemSessionStart ? 'system' : kind,
309
+ providerUnitKey,
310
+ bubbleId: typeof message.bubbleId === 'string' && message.bubbleId.trim()
311
+ ? message.bubbleId.trim()
312
+ : `bubble:${providerUnitKey}`,
313
+ _turnKey: typeof message._turnKey === 'string' && message._turnKey.trim()
314
+ ? message._turnKey.trim()
315
+ : `${providerType}:native-turn:${historySessionId || 'workspace'}:${turnIndex}`,
316
+ bubbleState: message.bubbleState || 'final',
317
+ ...(isSystemSessionStart ? {
318
+ visibility: message.visibility || 'hidden',
319
+ transcriptVisibility: message.transcriptVisibility || 'hidden',
320
+ audience: message.audience || 'internal',
321
+ source: message.source || 'runtime_status',
322
+ } : isActivity ? {
323
+ source: message.source || (kind === 'terminal' ? 'terminal_command' : 'tool_call'),
324
+ meta: { ...meta, label: message.senderName || meta?.label || (kind === 'terminal' ? 'Terminal' : 'Tool') },
325
+ } : {
326
+ source: message.source || (role === 'assistant' ? 'assistant_text' : undefined),
327
+ }),
328
+ } as ChatMessage;
329
+ });
330
+ }
331
+
332
+ function buildCliMessageSourceProvenance(args: {
333
+ selected: 'native-history' | 'pty-parser';
334
+ provider: string;
335
+ nativeHandle?: string;
336
+ fallbackReason?: string;
337
+ nativeSource?: string;
338
+ sourcePath?: string;
339
+ sourceMtimeMs?: number;
340
+ nativeHistoryCoverage?: string;
341
+ partialReason?: string;
342
+ unavailableReason?: string;
343
+ nativeMessages?: ChatMessage[];
344
+ ptyMessages?: ChatMessage[];
345
+ returnedMessages?: ChatMessage[];
346
+ safeMapping?: boolean;
347
+ freshEnough?: boolean;
348
+ ptyStatusApprovalOnly?: boolean;
349
+ }): Record<string, unknown> {
350
+ const sourceMtimeMs = Number(args.sourceMtimeMs || 0);
351
+ const sourceMtimeAgeMs = sourceMtimeMs > 0 ? Math.max(0, Date.now() - sourceMtimeMs) : undefined;
352
+ const nativeMessages = args.nativeMessages || [];
353
+ const ptyMessages = args.ptyMessages || [];
354
+ const returnedMessages = args.returnedMessages || [];
355
+ return {
356
+ selected: args.selected,
357
+ provider: args.provider,
358
+ providerType: args.provider,
359
+ ...(args.nativeHandle ? { nativeHandle: args.nativeHandle } : {}),
360
+ ...(args.nativeHandle ? { nativeSessionId: args.nativeHandle } : {}),
361
+ ...(args.fallbackReason ? { fallbackReason: args.fallbackReason } : {}),
362
+ ...(args.nativeSource ? { nativeSource: args.nativeSource } : {}),
363
+ ...(args.sourcePath ? { sourcePath: args.sourcePath } : {}),
364
+ ...(args.nativeHistoryCoverage ? { nativeHistoryCoverage: args.nativeHistoryCoverage } : {}),
365
+ ...(args.partialReason ? { partialReason: args.partialReason } : {}),
366
+ ...(args.unavailableReason ? { unavailableReason: args.unavailableReason } : {}),
367
+ ptyStatusApprovalOnly: args.ptyStatusApprovalOnly === true,
368
+ staleness: {
369
+ sourceMtimeMs: sourceMtimeMs || undefined,
370
+ sourceMtimeAgeMs,
371
+ nativeNewestMessageAt: getMessageNewestReceivedAt(nativeMessages),
372
+ ptyNewestMessageAt: getMessageNewestReceivedAt(ptyMessages),
373
+ freshEnough: args.freshEnough === true,
374
+ },
375
+ coverage: {
376
+ nativeMessageCount: nativeMessages.length,
377
+ ptyMessageCount: ptyMessages.length,
378
+ returnedMessageCount: returnedMessages.length,
379
+ safeMapping: args.safeMapping === true,
380
+ },
381
+ };
382
+ }
383
+
384
+ function buildNativeHistoryFallbackReason(args: {
385
+ providerType: string;
386
+ provider?: ProviderModule;
387
+ nativeSource?: string;
388
+ nativeHistoryCoverage?: string;
389
+ nativeMessageCount: number;
390
+ safeMapping: boolean;
391
+ freshEnough: boolean;
392
+ }): string {
393
+ if (!supportsCliNativeTranscript(args.providerType, args.provider)) return 'provider_native_transcript_not_supported';
394
+ if (args.nativeSource === 'native-unavailable') return 'native_history_unavailable';
395
+ if (args.nativeHistoryCoverage === 'partial') return 'native_history_partial';
396
+ if (args.nativeHistoryCoverage === 'unavailable') return 'native_history_unavailable';
397
+ if (args.nativeSource && args.nativeSource !== 'provider-native') return `native_history_source_${args.nativeSource}`;
398
+ if (args.nativeMessageCount <= 0) return 'native_history_empty';
399
+ if (!args.safeMapping) return 'native_history_not_safely_mapped';
400
+ if (!args.freshEnough) return 'native_history_stale';
401
+ return 'native_history_not_selected';
402
+ }
403
+
404
+ function supportsCliNativeTranscript(providerType: string, provider?: ProviderModule): boolean {
405
+ if (CLI_NATIVE_TRANSCRIPT_PROVIDERS.has(providerType)) return true;
406
+ return provider?.category === 'cli' && isNativeSourceCanonicalHistory(provider?.canonicalHistory);
407
+ }
408
+
409
+ function getComparableVisibleText(message: ChatMessage | undefined): string {
410
+ if (!message) return '';
411
+ const role = String((message as any).role || '').trim().toLowerCase();
412
+ if (role !== 'user' && role !== 'assistant') return '';
413
+ const kind = String((message as any).kind || 'standard').trim().toLowerCase();
414
+ if (kind && kind !== 'standard') return '';
415
+ const content = flattenContent((message as any).content).replace(/\s+/g, ' ').trim();
416
+ return content;
417
+ }
418
+
419
+ function hasOverlappingVisibleConversationText(nativeMessages: ChatMessage[], ptyMessages: ChatMessage[]): boolean {
420
+ const nativeTexts = nativeMessages.map(getComparableVisibleText).filter(Boolean);
421
+ const ptyTexts = ptyMessages.map(getComparableVisibleText).filter(Boolean);
422
+ if (nativeTexts.length === 0 || ptyTexts.length === 0) return false;
423
+ for (const nativeText of nativeTexts) {
424
+ for (const ptyText of ptyTexts) {
425
+ if (nativeText === ptyText) return true;
426
+ const shorter = nativeText.length <= ptyText.length ? nativeText : ptyText;
427
+ const longer = nativeText.length <= ptyText.length ? ptyText : nativeText;
428
+ if (shorter.length >= 32 && longer.includes(shorter)) return true;
429
+ }
430
+ }
431
+ return false;
432
+ }
433
+
434
+ function hasSafeNativeHistoryMapping(args: {
435
+ historySessionId?: string;
436
+ providerSessionId?: string;
437
+ workspace?: string;
438
+ nativeMessages: ChatMessage[];
439
+ ptyMessages?: ChatMessage[];
440
+ requireWorkspaceContentOverlap?: boolean;
441
+ }): boolean {
442
+ const explicitSessionId = String(args.historySessionId || args.providerSessionId || '').trim();
443
+ if (explicitSessionId) {
444
+ const messageSessionIds = args.nativeMessages
445
+ .map((message: any) => typeof message?.historySessionId === 'string' ? message.historySessionId.trim() : '')
446
+ .filter(Boolean);
447
+ if (messageSessionIds.length === 0) return true;
448
+ return messageSessionIds.some((id) => id === explicitSessionId);
449
+ }
450
+ const workspace = String(args.workspace || '').trim();
451
+ if (!workspace) return false;
452
+ const workspaceMatches = args.nativeMessages.some((message: any) => String(message?.workspace || '').trim() === workspace);
453
+ if (!workspaceMatches) return false;
454
+ if (!args.requireWorkspaceContentOverlap) return true;
455
+ return hasOverlappingVisibleConversationText(args.nativeMessages, args.ptyMessages || []);
456
+ }
457
+
458
+ function readCliProviderNativeHistory(agentStr: string, args: {
459
+ canonicalHistory?: ProviderModule['canonicalHistory'];
460
+ historySessionId?: string;
461
+ workspace?: string;
462
+ offset: number;
463
+ limit: number;
464
+ excludeRecentCount: number;
465
+ historyBehavior?: ProviderModule['historyBehavior'];
466
+ scripts?: ProviderScripts;
467
+ exactSessionScoped?: boolean;
468
+ }): ReturnType<typeof readProviderChatHistory> & { lookup: 'session' | 'workspace' } {
469
+ const sessionHistory = readProviderChatHistory(agentStr, {
470
+ canonicalHistory: args.canonicalHistory,
471
+ historySessionId: args.historySessionId,
472
+ workspace: args.workspace,
473
+ offset: args.offset,
474
+ limit: args.limit,
475
+ excludeRecentCount: args.excludeRecentCount,
476
+ historyBehavior: args.historyBehavior,
477
+ scripts: args.scripts as any,
478
+ });
479
+ // Exact runtime/provider transcript reads must not silently fall back to the
480
+ // workspace's active or most recent native transcript: multiple Hermes/Gemini/
481
+ // Codex sessions can run in the same workspace, and workspace fallback can make
482
+ // read_chat/completion evidence point at a different runtime's prompt.
483
+ if ((sessionHistory as any).source !== 'native-unavailable' || args.exactSessionScoped || !args.historySessionId || !args.workspace) {
484
+ return { ...(sessionHistory as any), lookup: args.historySessionId ? 'session' : 'workspace' };
485
+ }
486
+ const workspaceHistory = readProviderChatHistory(agentStr, {
487
+ canonicalHistory: args.canonicalHistory,
488
+ historySessionId: undefined,
489
+ workspace: args.workspace,
490
+ offset: args.offset,
491
+ limit: args.limit,
492
+ excludeRecentCount: args.excludeRecentCount,
493
+ historyBehavior: args.historyBehavior,
494
+ scripts: args.scripts as any,
495
+ });
496
+ return { ...(workspaceHistory as any), lookup: 'workspace' };
497
+ }
498
+
499
+ function isNativeHistoryFreshEnough(args: {
500
+ sourceMtimeMs?: number;
501
+ nativeMessages: ChatMessage[];
502
+ ptyMessages: ChatMessage[];
503
+ }): boolean {
504
+ const nativeNewest = getMessageNewestReceivedAt(args.nativeMessages);
505
+ const ptyNewest = getMessageNewestReceivedAt(args.ptyMessages);
506
+ if (nativeNewest > 0 && nativeNewest >= ptyNewest) return true;
507
+ const sourceMtimeMs = Number(args.sourceMtimeMs || 0);
508
+ if (sourceMtimeMs > 0 && Date.now() - sourceMtimeMs <= CLI_NATIVE_HISTORY_FRESH_MS) return true;
509
+ return ptyNewest === 0 && nativeNewest > 0;
510
+ }
511
+
512
+ function shouldPreserveReadChatPayloadField(key: string): boolean {
513
+ return key === 'messageSource' || key === 'transcriptProvenance';
514
+ }
515
+
516
+ function updateMessageSourceReturnedCount(value: unknown, returnedMessageCount: number): unknown {
517
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return value;
518
+ const record = value as Record<string, unknown>;
519
+ const coverage = record.coverage && typeof record.coverage === 'object' && !Array.isArray(record.coverage)
520
+ ? record.coverage as Record<string, unknown>
521
+ : undefined;
522
+ if (!coverage) return value;
523
+ return {
524
+ ...record,
525
+ coverage: {
526
+ ...coverage,
527
+ returnedMessageCount,
528
+ },
529
+ };
530
+ }
224
531
 
225
532
  function deriveHistoryDedupKey(message: ChatMessage & { _unitKey?: string; _turnKey?: string }): string | undefined {
226
533
  const unitKey = typeof message._unitKey === 'string' ? message._unitKey.trim() : '';
@@ -281,7 +588,7 @@ function normalizeReadChatCommandStatus(status: unknown, activeModal: unknown):
281
588
  }
282
589
  switch (raw) {
283
590
  case 'starting':
284
- return hasNonEmptyModalButtons(activeModal) ? 'waiting_approval' : 'generating';
591
+ return hasNonEmptyModalButtons(activeModal) ? 'waiting_approval' : 'starting';
285
592
  case 'stopped':
286
593
  case 'disconnected':
287
594
  case 'not_monitored':
@@ -295,6 +602,16 @@ function isGeneratingLikeStatus(status: unknown): boolean {
295
602
  return status === 'generating' || status === 'streaming' || status === 'long_generating' || status === 'starting';
296
603
  }
297
604
 
605
+ function hasVisibleAssistantMessage(messages: unknown[] | undefined): boolean {
606
+ if (!Array.isArray(messages)) return false;
607
+ return messages.some((message: any) => {
608
+ if (!message || message.role !== 'assistant') return false;
609
+ const kind = typeof message.kind === 'string' ? message.kind : 'standard';
610
+ if (kind !== 'standard') return false;
611
+ return String(message.content || '').trim().length > 0;
612
+ });
613
+ }
614
+
298
615
  function shouldTrustCliAdapterTerminalStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any): boolean {
299
616
  if (!isGeneratingLikeStatus(parsedStatus)) return false;
300
617
  if (hasNonEmptyModalButtons(activeModal)) return false;
@@ -304,7 +621,26 @@ function shouldTrustCliAdapterTerminalStatus(parsedStatus: unknown, activeModal:
304
621
  return true;
305
622
  }
306
623
 
307
- function normalizeCliReadChatStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any): string {
624
+ function normalizeCliReadChatStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any, parsedMessages?: unknown[]): string {
625
+ const adapterRawStatus = typeof adapterStatus?.status === 'string' ? adapterStatus.status.trim() : '';
626
+ if (adapterRawStatus === 'starting'
627
+ && isGeneratingLikeStatus(parsedStatus)
628
+ && !hasNonEmptyModalButtons(activeModal)
629
+ && Array.isArray(parsedMessages)
630
+ && parsedMessages.length === 0
631
+ && Array.isArray(adapterStatus?.messages)
632
+ && adapterStatus.messages.length === 0
633
+ && !(typeof adapter.isProcessing === 'function' && adapter.isProcessing())) {
634
+ return 'starting';
635
+ }
636
+ if (
637
+ isGeneratingLikeStatus(adapterRawStatus)
638
+ && parsedStatus === 'idle'
639
+ && !hasNonEmptyModalButtons(activeModal)
640
+ && !hasVisibleAssistantMessage(parsedMessages)
641
+ ) {
642
+ return adapterRawStatus;
643
+ }
308
644
  if (shouldTrustCliAdapterTerminalStatus(parsedStatus, activeModal, adapter, adapterStatus)) return 'idle';
309
645
  return typeof parsedStatus === 'string' && parsedStatus.trim() ? parsedStatus : 'idle';
310
646
  }
@@ -342,6 +678,13 @@ function buildReadChatCommandResult(payload: Record<string, any>, args: any): Co
342
678
  const visibleMessages = filterUserFacingChatMessages(messages);
343
679
  const sync = buildFullTail(visibleMessages, normalizeReadChatTailLimit(args));
344
680
  const hiddenMsgCount = Math.max(0, messages.length - visibleMessages.length);
681
+ const preservedPayloadFields = Object.fromEntries(Object.entries(payload).filter(([key]) => shouldPreserveReadChatPayloadField(key)));
682
+ if (preservedPayloadFields.messageSource) {
683
+ preservedPayloadFields.messageSource = updateMessageSourceReturnedCount(preservedPayloadFields.messageSource, sync.messages.length);
684
+ }
685
+ if (preservedPayloadFields.transcriptProvenance) {
686
+ preservedPayloadFields.transcriptProvenance = updateMessageSourceReturnedCount(preservedPayloadFields.transcriptProvenance, sync.messages.length);
687
+ }
345
688
  const returnedDebugReadChat = debugReadChat
346
689
  ? {
347
690
  ...debugReadChat,
@@ -356,6 +699,7 @@ function buildReadChatCommandResult(payload: Record<string, any>, args: any): Co
356
699
  return {
357
700
  success: true,
358
701
  ...validatedPayload,
702
+ ...preservedPayloadFields,
359
703
  messages: sync.messages,
360
704
  totalMessages: sync.totalMessages,
361
705
  ...(returnedDebugReadChat ? { debugReadChat: returnedDebugReadChat } : {}),
@@ -584,6 +928,8 @@ function buildChatDebugBundleSummary(bundle: Record<string, unknown>): Record<st
584
928
  adapterStatus: debugReadChat.adapterStatus,
585
929
  parsedStatus: debugReadChat.parsedStatus,
586
930
  returnedStatus: debugReadChat.returnedStatus,
931
+ selectedMessageSource: debugReadChat.selectedMessageSource,
932
+ messageSource: debugReadChat.messageSource,
587
933
  parsedMsgCount: debugReadChat.parsedMsgCount,
588
934
  returnedMsgCount: debugReadChat.returnedMsgCount,
589
935
  shouldPreferAdapterMessages: debugReadChat.shouldPreferAdapterMessages,
@@ -646,6 +992,8 @@ export async function handleGetChatDebugBundle(h: CommandHelpers, args: any): Pr
646
992
  providerSessionId: readResult.providerSessionId,
647
993
  transcriptAuthority: readResult.transcriptAuthority,
648
994
  coverage: readResult.coverage,
995
+ messageSource: readResult.messageSource,
996
+ transcriptProvenance: readResult.transcriptProvenance,
649
997
  activeModal: readResult.activeModal,
650
998
  messagesTail: Array.isArray(readResult.messages) ? readResult.messages.slice(-20) : [],
651
999
  debugReadChat: readResult.debugReadChat,
@@ -874,7 +1222,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
874
1222
  ? parsedRecord.coverage
875
1223
  : undefined;
876
1224
  const activeModal = parsedRecord.activeModal ?? parsedRecord.modal ?? null;
877
- const returnedStatus = normalizeCliReadChatStatus(parsedRecord.status, activeModal, adapter, adapterStatus);
1225
+ const returnedStatus = normalizeCliReadChatStatus(parsedRecord.status, activeModal, adapter, adapterStatus, parsedRecord.messages);
878
1226
  const runtimeMessageMerger = getTargetInstance(h, args) as RuntimeChatMessageMerger | null;
879
1227
  const parsedMessages = finalizeStreamingMessagesWhenIdle(parsedRecord.messages as ChatMessage[], returnedStatus);
880
1228
  const returnedMessages = runtimeMessageMerger?.category === 'cli'
@@ -882,25 +1230,194 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
882
1230
  && typeof runtimeMessageMerger.mergeRuntimeChatMessages === 'function'
883
1231
  ? runtimeMessageMerger.mergeRuntimeChatMessages(parsedMessages)
884
1232
  : parsedMessages;
1233
+ const providerType = provider?.type || adapter.cliType;
1234
+ let selectedMessages = returnedMessages;
1235
+ let selectedTitle = title;
1236
+ let selectedProviderSessionId = providerSessionId;
1237
+ let selectedTranscriptAuthority = transcriptAuthority;
1238
+ let selectedCoverage = coverage;
1239
+ let messageSource = buildCliMessageSourceProvenance({
1240
+ selected: 'pty-parser',
1241
+ provider: adapter.cliType,
1242
+ fallbackReason: supportsCliNativeTranscript(providerType, provider) ? 'native_history_not_checked' : 'provider_native_transcript_not_supported',
1243
+ ptyMessages: returnedMessages,
1244
+ returnedMessages,
1245
+ ptyStatusApprovalOnly: false,
1246
+ });
1247
+
1248
+ if (supportsCliNativeTranscript(providerType, provider) && isNativeSourceCanonicalHistory(provider?.canonicalHistory)) {
1249
+ const agentStr = provider?.type || args?.agentType || getCurrentProviderType(h, adapter.cliType);
1250
+ const workspace = typeof args?.workspace === 'string'
1251
+ ? args.workspace
1252
+ : typeof (h.currentSession as any)?.workspace === 'string'
1253
+ ? (h.currentSession as any).workspace
1254
+ : typeof adapter.workingDir === 'string'
1255
+ ? adapter.workingDir
1256
+ : undefined;
1257
+ const nativeHistoryLimit = Math.max(
1258
+ normalizeReadChatTailLimit(args) || 0,
1259
+ returnedMessages.length,
1260
+ 200,
1261
+ );
1262
+ const nativeHistorySessionId = resolveCliNativeHistorySessionId(args, historySessionId, providerSessionId);
1263
+ const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
1264
+ const exactNativeHistoryScope = Boolean(
1265
+ (typeof args?.historySessionId === 'string' && args.historySessionId.trim())
1266
+ || (typeof args?.providerSessionId === 'string' && args.providerSessionId.trim())
1267
+ || providerSessionId
1268
+ || (nativeHistorySessionId && nativeHistorySessionId !== targetSessionId)
1269
+ || ((h.currentSession as any)?.sessionId === args?.targetSessionId && typeof (h.currentSession as any)?.providerSessionId === 'string' && (h.currentSession as any).providerSessionId.trim())
1270
+ );
1271
+ let nativeHistory: (ReturnType<typeof readProviderChatHistory> & { lookup?: 'session' | 'workspace' }) | null = null;
1272
+ try {
1273
+ nativeHistory = readCliProviderNativeHistory(agentStr, {
1274
+ canonicalHistory: provider?.canonicalHistory,
1275
+ historySessionId: nativeHistorySessionId,
1276
+ workspace,
1277
+ offset: 0,
1278
+ limit: nativeHistoryLimit,
1279
+ excludeRecentCount: 0,
1280
+ historyBehavior: provider?.historyBehavior,
1281
+ scripts: provider?.scripts as any,
1282
+ exactSessionScoped: exactNativeHistoryScope,
1283
+ });
1284
+ } catch (error: any) {
1285
+ const fallbackReason = `native_history_error:${error?.message || String(error)}`;
1286
+ messageSource = buildCliMessageSourceProvenance({
1287
+ selected: 'pty-parser',
1288
+ provider: adapter.cliType,
1289
+ fallbackReason,
1290
+ ptyMessages: returnedMessages,
1291
+ returnedMessages,
1292
+ ptyStatusApprovalOnly: false,
1293
+ });
1294
+ nativeHistory = null;
1295
+ }
1296
+
1297
+ if (nativeHistory) {
1298
+ const nativeMessages = Array.isArray((nativeHistory as any).messages)
1299
+ ? normalizeNativeHistoryMessages(agentStr, (nativeHistory as any).messages as ChatMessage[])
1300
+ : [];
1301
+ const historyProviderSessionId = typeof (nativeHistory as any)?.providerSessionId === 'string'
1302
+ ? (nativeHistory as any).providerSessionId
1303
+ : readHistorySessionIdFromMessages(nativeMessages) || nativeHistorySessionId || historySessionId;
1304
+ const nativeHistoryCoverage = typeof (nativeHistory as any)?.nativeHistoryCoverage === 'string'
1305
+ ? (nativeHistory as any).nativeHistoryCoverage
1306
+ : undefined;
1307
+ const partialReason = typeof (nativeHistory as any)?.partialReason === 'string'
1308
+ ? (nativeHistory as any).partialReason
1309
+ : undefined;
1310
+ const unavailableReason = typeof (nativeHistory as any)?.unavailableReason === 'string'
1311
+ ? (nativeHistory as any).unavailableReason
1312
+ : undefined;
1313
+ const lookup = (nativeHistory as any).lookup === 'workspace' ? 'workspace' : 'session';
1314
+ const nativeHistorySessionForMapping = adapter.cliType === 'antigravity-cli'
1315
+ && historyProviderSessionId
1316
+ && nativeHistorySessionId
1317
+ && historyProviderSessionId !== nativeHistorySessionId
1318
+ ? undefined
1319
+ : nativeHistorySessionId;
1320
+ const safeMapping = hasSafeNativeHistoryMapping({
1321
+ historySessionId: lookup === 'workspace' ? undefined : nativeHistorySessionForMapping,
1322
+ providerSessionId: lookup === 'workspace' ? undefined : historyProviderSessionId || providerSessionId,
1323
+ workspace,
1324
+ nativeMessages,
1325
+ ptyMessages: returnedMessages,
1326
+ requireWorkspaceContentOverlap: lookup === 'workspace' && !exactNativeHistoryScope,
1327
+ });
1328
+ const freshEnough = isNativeHistoryFreshEnough({
1329
+ sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
1330
+ nativeMessages,
1331
+ ptyMessages: returnedMessages,
1332
+ });
1333
+ const nativeUsableForChatMessages = (nativeHistory as any).source === 'provider-native'
1334
+ && nativeMessages.length > 0
1335
+ && nativeHistoryCoverage !== 'partial'
1336
+ && nativeHistoryCoverage !== 'unavailable'
1337
+ && safeMapping;
1338
+ const allowStaleNativeChatMessages = adapter.cliType === 'antigravity-cli' && nativeUsableForChatMessages;
1339
+ if (nativeUsableForChatMessages && (freshEnough || allowStaleNativeChatMessages)) {
1340
+ selectedMessages = finalizeStreamingMessagesWhenIdle(nativeMessages, returnedStatus);
1341
+ selectedProviderSessionId = historyProviderSessionId || providerSessionId;
1342
+ selectedTranscriptAuthority = 'provider';
1343
+ selectedCoverage = (nativeHistory as any).hasMore ? 'tail' : 'full';
1344
+ messageSource = buildCliMessageSourceProvenance({
1345
+ selected: 'native-history',
1346
+ provider: adapter.cliType,
1347
+ nativeHandle: selectedProviderSessionId || nativeHistorySessionId || historySessionId,
1348
+ nativeSource: (nativeHistory as any).source,
1349
+ sourcePath: (nativeHistory as any).sourcePath,
1350
+ sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
1351
+ nativeHistoryCoverage,
1352
+ partialReason,
1353
+ unavailableReason,
1354
+ nativeMessages,
1355
+ ptyMessages: returnedMessages,
1356
+ returnedMessages: selectedMessages,
1357
+ safeMapping,
1358
+ freshEnough,
1359
+ ptyStatusApprovalOnly: true,
1360
+ });
1361
+ } else {
1362
+ const fallbackReason = buildNativeHistoryFallbackReason({
1363
+ providerType,
1364
+ provider,
1365
+ nativeSource: (nativeHistory as any).source,
1366
+ nativeHistoryCoverage,
1367
+ nativeMessageCount: nativeMessages.length,
1368
+ safeMapping,
1369
+ freshEnough,
1370
+ });
1371
+ messageSource = buildCliMessageSourceProvenance({
1372
+ selected: 'pty-parser',
1373
+ provider: adapter.cliType,
1374
+ nativeHandle: historyProviderSessionId || nativeHistorySessionId || historySessionId,
1375
+ fallbackReason,
1376
+ nativeSource: (nativeHistory as any).source,
1377
+ sourcePath: (nativeHistory as any).sourcePath,
1378
+ sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
1379
+ nativeHistoryCoverage,
1380
+ partialReason,
1381
+ unavailableReason,
1382
+ nativeMessages,
1383
+ ptyMessages: returnedMessages,
1384
+ returnedMessages,
1385
+ safeMapping,
1386
+ freshEnough,
1387
+ ptyStatusApprovalOnly: false,
1388
+ });
1389
+ }
1390
+ }
1391
+ }
885
1392
  LOG.debug('Command', `[read_chat] cli-like parsed provider=${adapter.cliType} target=${String(args?.targetSessionId || '')} adapterStatus=${String(adapterStatus.status || '')} parsedStatus=${String(parsedRecord.status || '')} parsedMsgCount=${parsedRecord.messages.length} returnedMsgCount=${returnedMessages.length}`);
886
1393
  return buildReadChatCommandResult({
887
- messages: returnedMessages,
1394
+ messages: selectedMessages,
888
1395
  status: returnedStatus,
889
1396
  activeModal,
1397
+ messageSource,
1398
+ transcriptProvenance: messageSource,
890
1399
  debugReadChat: {
891
1400
  provider: adapter.cliType,
892
1401
  targetSessionId: String(args?.targetSessionId || ''),
893
1402
  adapterStatus: String(adapterStatus.status || ''),
894
1403
  parsedStatus: String(parsedRecord.status || ''),
895
1404
  returnedStatus: String(returnedStatus || ''),
896
- shouldPreferAdapterMessages: false,
1405
+ selectedMessageSource: (messageSource as any).selected,
1406
+ messageSource,
1407
+ shouldPreferAdapterMessages: supportsCliNativeTranscript(providerType, provider)
1408
+ && isNativeSourceCanonicalHistory(provider?.canonicalHistory)
1409
+ && (messageSource as any).selected !== 'native-history'
1410
+ && typeof (messageSource as any).fallbackReason === 'string'
1411
+ && (messageSource as any).fallbackReason.startsWith('native_history_')
1412
+ && (messageSource as any).fallbackReason !== 'native_history_not_checked'
1413
+ && !(selectedTranscriptAuthority === 'provider' && selectedCoverage === 'full'),
897
1414
  parsedMsgCount: parsedRecord.messages.length,
898
- returnedMsgCount: returnedMessages.length,
1415
+ returnedMsgCount: selectedMessages.length,
899
1416
  },
900
- ...(title ? { title } : {}),
901
- ...(providerSessionId ? { providerSessionId } : {}),
902
- ...(transcriptAuthority ? { transcriptAuthority } : {}),
903
- ...(coverage ? { coverage } : {}),
1417
+ ...(selectedTitle ? { title: selectedTitle } : {}),
1418
+ ...(selectedProviderSessionId ? { providerSessionId: selectedProviderSessionId } : {}),
1419
+ ...(selectedTranscriptAuthority ? { transcriptAuthority: selectedTranscriptAuthority } : {}),
1420
+ ...(selectedCoverage ? { coverage: selectedCoverage } : {}),
904
1421
  }, args);
905
1422
  }
906
1423
  const historyLimit = normalizeReadChatTailLimit(args);
@@ -911,7 +1428,25 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
911
1428
  : typeof (h.currentSession as any)?.workspace === 'string'
912
1429
  ? (h.currentSession as any).workspace
913
1430
  : undefined;
914
- const history = readProviderChatHistory(agentStr, {
1431
+ const exactNativeHistoryScope = Boolean(
1432
+ (typeof args?.targetSessionId === 'string' && args.targetSessionId.trim())
1433
+ || (typeof args?.historySessionId === 'string' && args.historySessionId.trim())
1434
+ || (typeof args?.providerSessionId === 'string' && args.providerSessionId.trim())
1435
+ || ((h.currentSession as any)?.sessionId === args?.targetSessionId && typeof (h.currentSession as any)?.providerSessionId === 'string' && (h.currentSession as any).providerSessionId.trim())
1436
+ );
1437
+ const history = supportsCliNativeTranscript(agentStr, provider) && isNativeSourceCanonicalHistory(provider?.canonicalHistory)
1438
+ ? readCliProviderNativeHistory(agentStr, {
1439
+ canonicalHistory: provider?.canonicalHistory,
1440
+ historySessionId,
1441
+ workspace,
1442
+ offset: 0,
1443
+ limit: historyLimit,
1444
+ excludeRecentCount: 0,
1445
+ historyBehavior: provider?.historyBehavior,
1446
+ scripts: provider?.scripts as any,
1447
+ exactSessionScoped: exactNativeHistoryScope,
1448
+ })
1449
+ : readProviderChatHistory(agentStr, {
915
1450
  canonicalHistory: provider?.canonicalHistory,
916
1451
  historySessionId,
917
1452
  workspace,
@@ -921,12 +1456,80 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
921
1456
  historyBehavior: provider?.historyBehavior,
922
1457
  scripts: provider?.scripts as any,
923
1458
  });
1459
+ const lookup = (history as any).lookup === 'workspace' ? 'workspace' : 'session';
1460
+ const historyMessages = Array.isArray((history as any)?.messages)
1461
+ ? normalizeNativeHistoryMessages(agentStr, (history as any).messages as ChatMessage[])
1462
+ : [];
924
1463
  const historyProviderSessionId = typeof (history as any)?.providerSessionId === 'string'
925
1464
  ? (history as any).providerSessionId
926
- : historySessionId;
1465
+ : readHistorySessionIdFromMessages(historyMessages) || historySessionId;
1466
+ const nativeHistoryCoverage = typeof (history as any)?.nativeHistoryCoverage === 'string'
1467
+ ? (history as any).nativeHistoryCoverage
1468
+ : undefined;
1469
+ const partialReason = typeof (history as any)?.partialReason === 'string'
1470
+ ? (history as any).partialReason
1471
+ : undefined;
1472
+ const unavailableReason = typeof (history as any)?.unavailableReason === 'string'
1473
+ ? (history as any).unavailableReason
1474
+ : undefined;
1475
+ const safeMapping = supportsCliNativeTranscript(agentStr, provider)
1476
+ ? hasSafeNativeHistoryMapping({
1477
+ historySessionId: lookup === 'workspace' ? undefined : historySessionId,
1478
+ providerSessionId: lookup === 'workspace' ? undefined : historyProviderSessionId,
1479
+ workspace,
1480
+ nativeMessages: historyMessages,
1481
+ })
1482
+ : false;
1483
+ const nativeSelected = supportsCliNativeTranscript(agentStr, provider)
1484
+ && (history as any).source === 'provider-native'
1485
+ && historyMessages.length > 0
1486
+ && nativeHistoryCoverage !== 'partial'
1487
+ && nativeHistoryCoverage !== 'unavailable'
1488
+ && safeMapping;
1489
+ const messageSource = buildCliMessageSourceProvenance({
1490
+ selected: nativeSelected ? 'native-history' : 'pty-parser',
1491
+ provider: agentStr,
1492
+ nativeHandle: historyProviderSessionId || historySessionId,
1493
+ fallbackReason: nativeSelected
1494
+ ? undefined
1495
+ : buildNativeHistoryFallbackReason({
1496
+ providerType: agentStr,
1497
+ provider,
1498
+ nativeSource: (history as any).source,
1499
+ nativeHistoryCoverage,
1500
+ nativeMessageCount: historyMessages.length,
1501
+ safeMapping,
1502
+ freshEnough: true,
1503
+ }),
1504
+ nativeSource: (history as any).source,
1505
+ sourcePath: (history as any).sourcePath,
1506
+ sourceMtimeMs: (history as any).sourceMtimeMs,
1507
+ nativeHistoryCoverage,
1508
+ partialReason,
1509
+ unavailableReason,
1510
+ nativeMessages: historyMessages,
1511
+ returnedMessages: historyMessages,
1512
+ safeMapping,
1513
+ freshEnough: true,
1514
+ ptyStatusApprovalOnly: false,
1515
+ });
1516
+ const requiresNativeSource = supportsCliNativeTranscript(agentStr, provider)
1517
+ && isNativeSourceCanonicalHistory(provider?.canonicalHistory);
1518
+ if (requiresNativeSource && !nativeSelected) {
1519
+ return {
1520
+ success: false,
1521
+ code: 'native_history_not_safely_available',
1522
+ error: 'Provider-native history was not safely available for the requested CLI session.',
1523
+ providerSessionId: historyProviderSessionId,
1524
+ messageSource,
1525
+ transcriptProvenance: messageSource,
1526
+ };
1527
+ }
927
1528
  return buildReadChatCommandResult({
928
- messages: Array.isArray((history as any)?.messages) ? (history as any).messages : [],
1529
+ messages: historyMessages,
929
1530
  status: 'idle',
1531
+ messageSource,
1532
+ transcriptProvenance: messageSource,
930
1533
  ...(typeof (history as any)?.title === 'string' ? { title: (history as any).title } : {}),
931
1534
  ...(historyProviderSessionId ? { providerSessionId: historyProviderSessionId } : {}),
932
1535
  ...(((provider?.historyBehavior as any)?.transcriptAuthority === 'provider' || (provider?.historyBehavior as any)?.transcriptAuthority === 'daemon')
@@ -1742,6 +2345,9 @@ export async function handleResolveAction(h: CommandHelpers, args: any): Promise
1742
2345
  if (buttonIndex < 0 && (action === 'always' || /always/i.test(button))) {
1743
2346
  buttonIndex = buttons.findIndex(b => /always/i.test(b));
1744
2347
  }
2348
+ if (buttonIndex < 0 && (action === 'approve' || action === 'accept')) {
2349
+ buttonIndex = pickApprovalButton(buttons, provider).index;
2350
+ }
1745
2351
  if (buttonIndex < 0) {
1746
2352
  return { success: false, error: 'Approval action did not match any visible button' };
1747
2353
  }