@adhdev/daemon-core 0.9.82-rc.12 → 0.9.82-rc.121

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 (80) hide show
  1. package/dist/chat/subscription-updates.d.ts +1 -0
  2. package/dist/cli-adapters/provider-cli-adapter.d.ts +21 -0
  3. package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
  4. package/dist/cli-adapters/provider-cli-shared.d.ts +14 -0
  5. package/dist/commands/router.d.ts +22 -0
  6. package/dist/config/chat-history.d.ts +4 -0
  7. package/dist/config/mesh-config.d.ts +68 -1
  8. package/dist/git/git-commands.d.ts +5 -1
  9. package/dist/index.d.ts +15 -5
  10. package/dist/index.js +7029 -1368
  11. package/dist/index.js.map +1 -1
  12. package/dist/index.mjs +6984 -1351
  13. package/dist/index.mjs.map +1 -1
  14. package/dist/installer.d.ts +1 -4
  15. package/dist/launch.d.ts +1 -1
  16. package/dist/logging/async-batch-writer.d.ts +10 -0
  17. package/dist/mesh/beads-db.d.ts +18 -0
  18. package/dist/mesh/mesh-active-work.d.ts +73 -0
  19. package/dist/mesh/mesh-events.d.ts +26 -5
  20. package/dist/mesh/mesh-fast-forward.d.ts +39 -0
  21. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  22. package/dist/mesh/mesh-ledger.d.ts +38 -1
  23. package/dist/mesh/mesh-refine-status.d.ts +27 -0
  24. package/dist/mesh/mesh-work-queue.d.ts +27 -5
  25. package/dist/mesh/preview-freshness.d.ts +18 -0
  26. package/dist/mesh/refine-config.d.ts +193 -0
  27. package/dist/mesh/worktree-bootstrap-config.d.ts +115 -0
  28. package/dist/providers/chat-message-normalization.d.ts +1 -0
  29. package/dist/providers/cli-provider-instance.d.ts +4 -1
  30. package/dist/repo-mesh-types.d.ts +62 -0
  31. package/dist/status/reporter.d.ts +2 -0
  32. package/package.json +3 -1
  33. package/src/boot/daemon-lifecycle.ts +1 -0
  34. package/src/chat/subscription-updates.ts +5 -1
  35. package/src/cli-adapters/provider-cli-adapter.ts +453 -17
  36. package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
  37. package/src/cli-adapters/provider-cli-parse.ts +4 -0
  38. package/src/cli-adapters/provider-cli-runtime.ts +3 -1
  39. package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
  40. package/src/cli-adapters/provider-cli-shared.ts +32 -10
  41. package/src/commands/chat-commands.ts +730 -30
  42. package/src/commands/cli-manager.ts +129 -1
  43. package/src/commands/handler.ts +8 -1
  44. package/src/commands/mesh-coordinator.ts +13 -143
  45. package/src/commands/router.ts +3239 -430
  46. package/src/config/chat-history.ts +37 -9
  47. package/src/config/mesh-config.ts +249 -2
  48. package/src/config/recent-activity.ts +8 -2
  49. package/src/daemon/dev-cli-debug.ts +10 -1
  50. package/src/detection/ide-detector.ts +26 -16
  51. package/src/git/git-commands.ts +17 -5
  52. package/src/index.ts +41 -4
  53. package/src/installer.d.ts +1 -1
  54. package/src/installer.ts +8 -6
  55. package/src/launch.d.ts +1 -1
  56. package/src/launch.ts +37 -28
  57. package/src/logging/async-batch-writer.ts +55 -0
  58. package/src/logging/logger.ts +2 -1
  59. package/src/mesh/beads-db.ts +176 -0
  60. package/src/mesh/coordinator-prompt.ts +31 -8
  61. package/src/mesh/mesh-active-work.ts +292 -0
  62. package/src/mesh/mesh-events.ts +389 -47
  63. package/src/mesh/mesh-fast-forward.ts +430 -0
  64. package/src/mesh/mesh-host-ownership.ts +73 -0
  65. package/src/mesh/mesh-ledger.ts +138 -1
  66. package/src/mesh/mesh-refine-status.ts +145 -0
  67. package/src/mesh/mesh-work-queue.ts +199 -137
  68. package/src/mesh/preview-freshness.ts +118 -0
  69. package/src/mesh/refine-config.ts +366 -0
  70. package/src/mesh/worktree-bootstrap-config.ts +234 -0
  71. package/src/providers/approval-utils.ts +12 -5
  72. package/src/providers/chat-message-normalization.ts +7 -12
  73. package/src/providers/cli-provider-instance.ts +201 -28
  74. package/src/providers/ide-provider-instance.ts +17 -3
  75. package/src/providers/provider-loader.ts +10 -4
  76. package/src/providers/read-chat-contract.ts +1 -1
  77. package/src/providers/version-archive.ts +38 -20
  78. package/src/repo-mesh-types.ts +67 -0
  79. package/src/status/reporter.ts +15 -0
  80. 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,13 @@ 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
+ // Fallback list for supportsCliNativeTranscript() when the ProviderModule is
30
+ // unavailable (e.g. provider config not yet loaded). The authoritative check is
31
+ // provider.canonicalHistory via isNativeSourceCanonicalHistory(). New providers
32
+ // with native transcripts should set canonicalHistory in their provider.json
33
+ // rather than adding entries here.
34
+ const CLI_NATIVE_TRANSCRIPT_PROVIDERS = new Set(['codex-cli', 'claude-cli', 'hermes-cli', 'antigravity-cli']);
27
35
  const recentSendByTarget = new Map<string, number>();
28
36
 
29
37
  interface ApprovalSelectableInstance extends ProviderInstance {
@@ -48,6 +56,16 @@ function getTargetedCliAdapter(h: CommandHelpers, args: any, providerType?: stri
48
56
  return h.getCliAdapter(args?.targetSessionId || providerType || h.currentSession?.providerType || h.currentManagerKey);
49
57
  }
50
58
 
59
+ function getExplicitHistorySessionId(args: any): string | undefined {
60
+ const explicit = typeof args?.historySessionId === 'string' ? args.historySessionId.trim() : '';
61
+ if (explicit) return explicit;
62
+
63
+ const explicitProviderSessionId = typeof args?.providerSessionId === 'string' ? args.providerSessionId.trim() : '';
64
+ if (explicitProviderSessionId) return explicitProviderSessionId;
65
+
66
+ return undefined;
67
+ }
68
+
51
69
  function getTargetInstance(h: CommandHelpers, args: any): ApprovalSelectableInstance | null {
52
70
  const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
53
71
  const sessionId = targetSessionId || h.currentSession?.sessionId || '';
@@ -139,19 +157,47 @@ async function waitOnceForFreshHermesCliStart(adapter: CliAdapter, log: (msg: st
139
157
  }
140
158
 
141
159
  function getHistorySessionId(h: CommandHelpers, args: any): string | undefined {
142
- const explicit = typeof args?.historySessionId === 'string' ? args.historySessionId.trim() : '';
160
+ const explicit = getExplicitHistorySessionId(args);
143
161
  if (explicit) return explicit;
144
162
 
145
- const explicitProviderSessionId = typeof args?.providerSessionId === 'string' ? args.providerSessionId.trim() : '';
146
- if (explicitProviderSessionId) return explicitProviderSessionId;
147
-
148
163
  const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
149
164
  if (!targetSessionId) return undefined;
150
165
 
151
- const instance = h.ctx.instanceManager?.getInstance(targetSessionId);
166
+ const session = h.ctx.sessionRegistry?.get(targetSessionId) as any;
167
+ const registeredProviderSessionId = typeof session?.providerSessionId === 'string' ? session.providerSessionId.trim() : '';
168
+ if (registeredProviderSessionId) return registeredProviderSessionId;
169
+
170
+ const instance = getTargetInstance(h, args);
152
171
  const state = instance?.getState?.();
153
172
  const providerSessionId = typeof state?.providerSessionId === 'string' ? state.providerSessionId.trim() : '';
154
- return providerSessionId || targetSessionId;
173
+ if (providerSessionId) return providerSessionId;
174
+
175
+ const currentSession = h.currentSession as any;
176
+ if (currentSession?.sessionId === targetSessionId) {
177
+ const currentProviderSessionId = typeof currentSession.providerSessionId === 'string'
178
+ ? currentSession.providerSessionId.trim()
179
+ : '';
180
+ if (currentProviderSessionId) return currentProviderSessionId;
181
+ }
182
+
183
+ return targetSessionId;
184
+ }
185
+
186
+ function resolveCliNativeHistorySessionId(args: any, currentHistorySessionId: string | undefined, parsedProviderSessionId: string | undefined): string | undefined {
187
+ const explicit = getExplicitHistorySessionId(args);
188
+ if (explicit) return explicit;
189
+
190
+ const parsed = typeof parsedProviderSessionId === 'string' ? parsedProviderSessionId.trim() : '';
191
+ const current = typeof currentHistorySessionId === 'string' ? currentHistorySessionId.trim() : '';
192
+ const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
193
+
194
+ // getHistorySessionId falls back to the runtime session id when no native
195
+ // handle has been registered yet. For live CLI adapters the parser may
196
+ // already know the provider-native handle; prefer it over the runtime id so
197
+ // exact native reads do not miss the worker transcript and fall back to PTY
198
+ // or same-workspace history.
199
+ if (parsed && (!current || current === targetSessionId)) return parsed;
200
+ return current || parsed || undefined;
155
201
  }
156
202
 
157
203
  function getInteractionId(args: any): string | undefined {
@@ -221,6 +267,321 @@ function normalizeReadChatMessages(payload: Record<string, any>): ChatMessage[]
221
267
  return normalizeChatMessages(messages);
222
268
  }
223
269
 
270
+ function getMessageNewestReceivedAt(messages: Array<{ receivedAt?: unknown; timestamp?: unknown }>): number {
271
+ let newest = 0;
272
+ for (const message of messages) {
273
+ const receivedAt = Number(message?.receivedAt ?? message?.timestamp ?? 0);
274
+ if (Number.isFinite(receivedAt) && receivedAt > newest) newest = receivedAt;
275
+ }
276
+ return newest;
277
+ }
278
+
279
+ function readHistorySessionIdFromMessages(messages: ChatMessage[]): string | undefined {
280
+ for (const message of messages as Array<ChatMessage & { historySessionId?: unknown }>) {
281
+ const historySessionId = typeof message?.historySessionId === 'string' ? message.historySessionId.trim() : '';
282
+ if (historySessionId) return historySessionId;
283
+ }
284
+ return undefined;
285
+ }
286
+
287
+ function shouldPreserveNativeIdentity(providerType: string, sessionId: string, message: ChatMessage): boolean {
288
+ const providerUnitKey = typeof message.providerUnitKey === 'string' ? message.providerUnitKey.trim() : '';
289
+ const turnKey = typeof message._turnKey === 'string' ? message._turnKey.trim() : '';
290
+ if (!providerUnitKey || !turnKey) return false;
291
+ if (providerType === 'hermes-cli' && sessionId) {
292
+ return providerUnitKey.startsWith(`${providerType}:native:${sessionId}:`)
293
+ && turnKey.startsWith(`${providerType}:native-turn:${sessionId}:`);
294
+ }
295
+ return true;
296
+ }
297
+
298
+ function normalizeNativeHistoryMessages(providerType: string, messages: ChatMessage[], nativeSessionId?: string): ChatMessage[] {
299
+ let turnIndex = 0;
300
+ return normalizeChatMessages(messages).map((message, index) => {
301
+ const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
302
+ const kind = typeof message.kind === 'string' && message.kind.trim() ? message.kind.trim() : (role === 'system' ? 'system' : 'standard');
303
+ if ((role === 'user' || role === 'human') && index > 0) turnIndex += 1;
304
+ const historySessionId = typeof (message as any).historySessionId === 'string'
305
+ ? (message as any).historySessionId.trim()
306
+ : '';
307
+ const contentHash = hashSignatureParts([
308
+ providerType,
309
+ historySessionId,
310
+ String(message.receivedAt || message.timestamp || index),
311
+ role,
312
+ kind,
313
+ flattenContent(message.content),
314
+ ]).slice(0, 12);
315
+ const nativeIdentitySessionId = historySessionId || (typeof nativeSessionId === 'string' ? nativeSessionId.trim() : '');
316
+ const preserveNativeIdentity = shouldPreserveNativeIdentity(providerType, nativeIdentitySessionId, message);
317
+ const existingProviderUnitKey = typeof message.providerUnitKey === 'string' ? message.providerUnitKey.trim() : '';
318
+ const existingTurnKey = typeof message._turnKey === 'string' ? message._turnKey.trim() : '';
319
+ const providerUnitKey = preserveNativeIdentity
320
+ ? existingProviderUnitKey
321
+ : `${providerType}:native:${nativeIdentitySessionId || 'workspace'}:${index}:${role || 'message'}:${kind}:${contentHash}`;
322
+ const meta = message.meta && typeof message.meta === 'object' ? message.meta as Record<string, unknown> : undefined;
323
+ const isSystemSessionStart = role === 'system' || kind === 'system' || kind === 'session_start';
324
+ const isActivity = role === 'assistant' && (kind === 'tool' || kind === 'terminal' || kind === 'thought');
325
+ return {
326
+ ...message,
327
+ role: role === 'human' ? 'user' : (role || 'assistant'),
328
+ kind: isSystemSessionStart ? 'system' : kind,
329
+ providerUnitKey,
330
+ bubbleId: typeof message.bubbleId === 'string' && message.bubbleId.trim()
331
+ && preserveNativeIdentity
332
+ ? message.bubbleId.trim()
333
+ : `bubble:${providerUnitKey}`,
334
+ _turnKey: preserveNativeIdentity
335
+ ? existingTurnKey
336
+ : `${providerType}:native-turn:${nativeIdentitySessionId || 'workspace'}:${turnIndex}`,
337
+ bubbleState: message.bubbleState || 'final',
338
+ ...(isSystemSessionStart ? {
339
+ visibility: message.visibility || 'hidden',
340
+ transcriptVisibility: message.transcriptVisibility || 'hidden',
341
+ audience: message.audience || 'internal',
342
+ source: message.source || 'runtime_status',
343
+ } : isActivity ? {
344
+ source: message.source || (kind === 'terminal' ? 'terminal_command' : 'tool_call'),
345
+ meta: { ...meta, label: message.senderName || meta?.label || (kind === 'terminal' ? 'Terminal' : 'Tool') },
346
+ } : {
347
+ source: message.source || (role === 'assistant' ? 'assistant_text' : undefined),
348
+ }),
349
+ } as ChatMessage;
350
+ });
351
+ }
352
+
353
+ function buildCliMessageSourceProvenance(args: {
354
+ selected: 'native-history' | 'pty-parser';
355
+ provider: string;
356
+ nativeHandle?: string;
357
+ fallbackReason?: string;
358
+ nativeSource?: string;
359
+ sourcePath?: string;
360
+ sourceMtimeMs?: number;
361
+ nativeHistoryCoverage?: string;
362
+ partialReason?: string;
363
+ unavailableReason?: string;
364
+ nativeMessages?: ChatMessage[];
365
+ ptyMessages?: ChatMessage[];
366
+ returnedMessages?: ChatMessage[];
367
+ safeMapping?: boolean;
368
+ freshEnough?: boolean;
369
+ ptyStatusApprovalOnly?: boolean;
370
+ }): Record<string, unknown> {
371
+ const sourceMtimeMs = Number(args.sourceMtimeMs || 0);
372
+ const sourceMtimeAgeMs = sourceMtimeMs > 0 ? Math.max(0, Date.now() - sourceMtimeMs) : undefined;
373
+ const nativeMessages = args.nativeMessages || [];
374
+ const ptyMessages = args.ptyMessages || [];
375
+ const returnedMessages = args.returnedMessages || [];
376
+ return {
377
+ selected: args.selected,
378
+ provider: args.provider,
379
+ providerType: args.provider,
380
+ ...(args.nativeHandle ? { nativeHandle: args.nativeHandle } : {}),
381
+ ...(args.nativeHandle ? { nativeSessionId: args.nativeHandle } : {}),
382
+ ...(args.fallbackReason ? { fallbackReason: args.fallbackReason } : {}),
383
+ ...(args.nativeSource ? { nativeSource: args.nativeSource } : {}),
384
+ ...(args.sourcePath ? { sourcePath: args.sourcePath } : {}),
385
+ ...(args.nativeHistoryCoverage ? { nativeHistoryCoverage: args.nativeHistoryCoverage } : {}),
386
+ ...(args.partialReason ? { partialReason: args.partialReason } : {}),
387
+ ...(args.unavailableReason ? { unavailableReason: args.unavailableReason } : {}),
388
+ ptyStatusApprovalOnly: args.ptyStatusApprovalOnly === true,
389
+ staleness: {
390
+ sourceMtimeMs: sourceMtimeMs || undefined,
391
+ sourceMtimeAgeMs,
392
+ nativeNewestMessageAt: getMessageNewestReceivedAt(nativeMessages),
393
+ ptyNewestMessageAt: getMessageNewestReceivedAt(ptyMessages),
394
+ freshEnough: args.freshEnough === true,
395
+ },
396
+ coverage: {
397
+ nativeMessageCount: nativeMessages.length,
398
+ ptyMessageCount: ptyMessages.length,
399
+ returnedMessageCount: returnedMessages.length,
400
+ safeMapping: args.safeMapping === true,
401
+ },
402
+ };
403
+ }
404
+
405
+ function buildNativeHistoryFallbackReason(args: {
406
+ providerType: string;
407
+ provider?: ProviderModule;
408
+ nativeSource?: string;
409
+ nativeHistoryCoverage?: string;
410
+ unavailableReason?: string;
411
+ nativeMessageCount: number;
412
+ safeMapping: boolean;
413
+ freshEnough: boolean;
414
+ }): string {
415
+ if (!supportsCliNativeTranscript(args.providerType, args.provider)) return 'provider_native_transcript_not_supported';
416
+ if (args.unavailableReason) return `native_history_unavailable:${args.unavailableReason}`;
417
+ if (args.nativeSource === 'native-unavailable') return 'native_history_unavailable';
418
+ if (args.nativeHistoryCoverage === 'partial') return 'native_history_partial';
419
+ if (args.nativeHistoryCoverage === 'unavailable') return 'native_history_unavailable';
420
+ if (args.nativeSource && args.nativeSource !== 'provider-native') return `native_history_source_${args.nativeSource}`;
421
+ if (args.nativeMessageCount <= 0) return 'native_history_empty';
422
+ if (!args.safeMapping) return 'native_history_not_safely_mapped';
423
+ if (!args.freshEnough) return 'native_history_stale';
424
+ return 'native_history_not_selected';
425
+ }
426
+
427
+ function supportsCliNativeTranscript(providerType: string, provider?: ProviderModule): boolean {
428
+ if (CLI_NATIVE_TRANSCRIPT_PROVIDERS.has(providerType)) return true;
429
+ return provider?.category === 'cli' && isNativeSourceCanonicalHistory(provider?.canonicalHistory);
430
+ }
431
+
432
+ function getComparableVisibleText(message: ChatMessage | undefined): string {
433
+ if (!message) return '';
434
+ const role = String((message as any).role || '').trim().toLowerCase();
435
+ if (role !== 'user' && role !== 'assistant') return '';
436
+ const kind = String((message as any).kind || 'standard').trim().toLowerCase();
437
+ if (kind && kind !== 'standard') return '';
438
+ const content = flattenContent((message as any).content).replace(/\s+/g, ' ').trim();
439
+ return content;
440
+ }
441
+
442
+ function hasOverlappingVisibleConversationText(nativeMessages: ChatMessage[], ptyMessages: ChatMessage[]): boolean {
443
+ const nativeTexts = nativeMessages.map(getComparableVisibleText).filter(Boolean);
444
+ const ptyTexts = ptyMessages.map(getComparableVisibleText).filter(Boolean);
445
+ if (nativeTexts.length === 0 || ptyTexts.length === 0) return false;
446
+ for (const nativeText of nativeTexts) {
447
+ for (const ptyText of ptyTexts) {
448
+ if (nativeText === ptyText) return true;
449
+ const shorter = nativeText.length <= ptyText.length ? nativeText : ptyText;
450
+ const longer = nativeText.length <= ptyText.length ? ptyText : nativeText;
451
+ if (shorter.length >= 32 && longer.includes(shorter)) return true;
452
+ }
453
+ }
454
+ return false;
455
+ }
456
+
457
+ function hasSafeNativeHistoryMapping(args: {
458
+ historySessionId?: string;
459
+ providerSessionId?: string;
460
+ workspace?: string;
461
+ nativeMessages: ChatMessage[];
462
+ ptyMessages?: ChatMessage[];
463
+ requireWorkspaceContentOverlap?: boolean;
464
+ }): boolean {
465
+ const isCoordinatorTranscript = args.nativeMessages.some((m: any) => {
466
+ const text = typeof m?.content === 'string' ? m.content : JSON.stringify(m?.content || '');
467
+ return text.includes('mesh_send_task') || text.includes('mesh_status') || text.includes('mesh_read_chat') || text.includes('mesh_launch_session');
468
+ });
469
+
470
+ const explicitSessionId = String(args.historySessionId || args.providerSessionId || '').trim();
471
+ if (explicitSessionId) {
472
+ const messageSessionIds = args.nativeMessages
473
+ .map((message: any) => typeof message?.historySessionId === 'string' ? message.historySessionId.trim() : '')
474
+ .filter(Boolean);
475
+ if (messageSessionIds.length > 0) {
476
+ return messageSessionIds.some((id) => id === explicitSessionId);
477
+ }
478
+
479
+ // Messages carry no historySessionId — cannot confirm they belong to the requested session.
480
+ // Only allow a coordinator transcript that is also confirmed by the PTY side; otherwise
481
+ // fail closed so a same-workspace session's history is never silently accepted.
482
+ if (isCoordinatorTranscript && args.ptyMessages && args.ptyMessages.length > 0) {
483
+ const ptyHasCoordinator = args.ptyMessages.some((m: any) => {
484
+ const text = typeof m?.content === 'string' ? m.content : JSON.stringify(m?.content || '');
485
+ return text.includes('mesh_send_task') || text.includes('mesh_status') || text.includes('mesh_read_chat');
486
+ });
487
+ return ptyHasCoordinator;
488
+ }
489
+
490
+ // No historySessionId in messages and no coordinator cross-check: fail closed.
491
+ // Workspace-only matching must not override an explicit session identity.
492
+ return false;
493
+ }
494
+ const workspace = String(args.workspace || '').trim();
495
+ if (!workspace) return false;
496
+ const workspaceMatches = args.nativeMessages.some((message: any) => String(message?.workspace || '').trim() === workspace);
497
+ if (!workspaceMatches) return false;
498
+
499
+ if (isCoordinatorTranscript && args.ptyMessages && args.ptyMessages.length > 0) {
500
+ const ptyHasCoordinator = args.ptyMessages.some((m: any) => {
501
+ const text = typeof m?.content === 'string' ? m.content : JSON.stringify(m?.content || '');
502
+ return text.includes('mesh_send_task') || text.includes('mesh_status') || text.includes('mesh_read_chat');
503
+ });
504
+ if (!ptyHasCoordinator) {
505
+ return false;
506
+ }
507
+ }
508
+
509
+ if (!args.requireWorkspaceContentOverlap) return true;
510
+ return hasOverlappingVisibleConversationText(args.nativeMessages, args.ptyMessages || []);
511
+ }
512
+
513
+ // Provenance boundary: workspace-only native history lookup is never safe
514
+ // because multiple concurrent sessions sharing the same cwd would alias each
515
+ // other. historySessionId (the provider-native session key) is required to
516
+ // establish ownership. hasSafeNativeHistoryMapping() enforces the same
517
+ // invariant after the read; both guards must hold for native history to be used.
518
+ function readCliProviderNativeHistory(agentStr: string, args: {
519
+ canonicalHistory?: ProviderModule['canonicalHistory'];
520
+ historySessionId?: string;
521
+ workspace?: string;
522
+ offset: number;
523
+ limit: number;
524
+ excludeRecentCount: number;
525
+ historyBehavior?: ProviderModule['historyBehavior'];
526
+ scripts?: ProviderScripts;
527
+ }): ReturnType<typeof readProviderChatHistory> & { lookup: 'session' | 'workspace' } {
528
+ if (!args.historySessionId) {
529
+ return {
530
+ messages: [],
531
+ hasMore: false,
532
+ source: 'native-unavailable',
533
+ unavailableReason: 'native_history_workspace_only_lookup_unsafe',
534
+ lookup: 'session',
535
+ } as ReturnType<typeof readProviderChatHistory> & { lookup: 'session' | 'workspace' };
536
+ }
537
+ const sessionHistory = readProviderChatHistory(agentStr, {
538
+ canonicalHistory: args.canonicalHistory,
539
+ historySessionId: args.historySessionId,
540
+ workspace: args.workspace,
541
+ offset: args.offset,
542
+ limit: args.limit,
543
+ excludeRecentCount: args.excludeRecentCount,
544
+ historyBehavior: args.historyBehavior,
545
+ scripts: args.scripts as any,
546
+ });
547
+ // Native transcripts are keyed by provider/runtime session identity. Falling
548
+ // back to workspace makes concurrent local Codex/Hermes sessions alias each
549
+ // other when they share the same cwd.
550
+ return { ...(sessionHistory as any), lookup: 'session' };
551
+ }
552
+
553
+ function isNativeHistoryFreshEnough(args: {
554
+ sourceMtimeMs?: number;
555
+ nativeMessages: ChatMessage[];
556
+ ptyMessages: ChatMessage[];
557
+ }): boolean {
558
+ const nativeNewest = getMessageNewestReceivedAt(args.nativeMessages);
559
+ const ptyNewest = getMessageNewestReceivedAt(args.ptyMessages);
560
+ if (nativeNewest > 0 && nativeNewest >= ptyNewest) return true;
561
+ const sourceMtimeMs = Number(args.sourceMtimeMs || 0);
562
+ if (sourceMtimeMs > 0 && Date.now() - sourceMtimeMs <= CLI_NATIVE_HISTORY_FRESH_MS) return true;
563
+ return ptyNewest === 0 && nativeNewest > 0;
564
+ }
565
+
566
+ function shouldPreserveReadChatPayloadField(key: string): boolean {
567
+ return key === 'messageSource' || key === 'transcriptProvenance';
568
+ }
569
+
570
+ function updateMessageSourceReturnedCount(value: unknown, returnedMessageCount: number): unknown {
571
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return value;
572
+ const record = value as Record<string, unknown>;
573
+ const coverage = record.coverage && typeof record.coverage === 'object' && !Array.isArray(record.coverage)
574
+ ? record.coverage as Record<string, unknown>
575
+ : undefined;
576
+ if (!coverage) return value;
577
+ return {
578
+ ...record,
579
+ coverage: {
580
+ ...coverage,
581
+ returnedMessageCount,
582
+ },
583
+ };
584
+ }
224
585
 
225
586
  function deriveHistoryDedupKey(message: ChatMessage & { _unitKey?: string; _turnKey?: string }): string | undefined {
226
587
  const unitKey = typeof message._unitKey === 'string' ? message._unitKey.trim() : '';
@@ -281,7 +642,7 @@ function normalizeReadChatCommandStatus(status: unknown, activeModal: unknown):
281
642
  }
282
643
  switch (raw) {
283
644
  case 'starting':
284
- return hasNonEmptyModalButtons(activeModal) ? 'waiting_approval' : 'generating';
645
+ return hasNonEmptyModalButtons(activeModal) ? 'waiting_approval' : 'starting';
285
646
  case 'stopped':
286
647
  case 'disconnected':
287
648
  case 'not_monitored':
@@ -295,6 +656,16 @@ function isGeneratingLikeStatus(status: unknown): boolean {
295
656
  return status === 'generating' || status === 'streaming' || status === 'long_generating' || status === 'starting';
296
657
  }
297
658
 
659
+ function hasVisibleAssistantMessage(messages: unknown[] | undefined): boolean {
660
+ if (!Array.isArray(messages)) return false;
661
+ return messages.some((message: any) => {
662
+ if (!message || message.role !== 'assistant') return false;
663
+ const kind = typeof message.kind === 'string' ? message.kind : 'standard';
664
+ if (kind !== 'standard') return false;
665
+ return String(message.content || '').trim().length > 0;
666
+ });
667
+ }
668
+
298
669
  function shouldTrustCliAdapterTerminalStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any): boolean {
299
670
  if (!isGeneratingLikeStatus(parsedStatus)) return false;
300
671
  if (hasNonEmptyModalButtons(activeModal)) return false;
@@ -304,7 +675,26 @@ function shouldTrustCliAdapterTerminalStatus(parsedStatus: unknown, activeModal:
304
675
  return true;
305
676
  }
306
677
 
307
- function normalizeCliReadChatStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any): string {
678
+ function normalizeCliReadChatStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any, parsedMessages?: unknown[]): string {
679
+ const adapterRawStatus = typeof adapterStatus?.status === 'string' ? adapterStatus.status.trim() : '';
680
+ if (adapterRawStatus === 'starting'
681
+ && isGeneratingLikeStatus(parsedStatus)
682
+ && !hasNonEmptyModalButtons(activeModal)
683
+ && Array.isArray(parsedMessages)
684
+ && parsedMessages.length === 0
685
+ && Array.isArray(adapterStatus?.messages)
686
+ && adapterStatus.messages.length === 0
687
+ && !(typeof adapter.isProcessing === 'function' && adapter.isProcessing())) {
688
+ return 'starting';
689
+ }
690
+ if (
691
+ isGeneratingLikeStatus(adapterRawStatus)
692
+ && parsedStatus === 'idle'
693
+ && !hasNonEmptyModalButtons(activeModal)
694
+ && !hasVisibleAssistantMessage(parsedMessages)
695
+ ) {
696
+ return adapterRawStatus;
697
+ }
308
698
  if (shouldTrustCliAdapterTerminalStatus(parsedStatus, activeModal, adapter, adapterStatus)) return 'idle';
309
699
  return typeof parsedStatus === 'string' && parsedStatus.trim() ? parsedStatus : 'idle';
310
700
  }
@@ -342,6 +732,13 @@ function buildReadChatCommandResult(payload: Record<string, any>, args: any): Co
342
732
  const visibleMessages = filterUserFacingChatMessages(messages);
343
733
  const sync = buildFullTail(visibleMessages, normalizeReadChatTailLimit(args));
344
734
  const hiddenMsgCount = Math.max(0, messages.length - visibleMessages.length);
735
+ const preservedPayloadFields = Object.fromEntries(Object.entries(payload).filter(([key]) => shouldPreserveReadChatPayloadField(key)));
736
+ if (preservedPayloadFields.messageSource) {
737
+ preservedPayloadFields.messageSource = updateMessageSourceReturnedCount(preservedPayloadFields.messageSource, sync.messages.length);
738
+ }
739
+ if (preservedPayloadFields.transcriptProvenance) {
740
+ preservedPayloadFields.transcriptProvenance = updateMessageSourceReturnedCount(preservedPayloadFields.transcriptProvenance, sync.messages.length);
741
+ }
345
742
  const returnedDebugReadChat = debugReadChat
346
743
  ? {
347
744
  ...debugReadChat,
@@ -356,6 +753,7 @@ function buildReadChatCommandResult(payload: Record<string, any>, args: any): Co
356
753
  return {
357
754
  success: true,
358
755
  ...validatedPayload,
756
+ ...preservedPayloadFields,
359
757
  messages: sync.messages,
360
758
  totalMessages: sync.totalMessages,
361
759
  ...(returnedDebugReadChat ? { debugReadChat: returnedDebugReadChat } : {}),
@@ -584,6 +982,8 @@ function buildChatDebugBundleSummary(bundle: Record<string, unknown>): Record<st
584
982
  adapterStatus: debugReadChat.adapterStatus,
585
983
  parsedStatus: debugReadChat.parsedStatus,
586
984
  returnedStatus: debugReadChat.returnedStatus,
985
+ selectedMessageSource: debugReadChat.selectedMessageSource,
986
+ messageSource: debugReadChat.messageSource,
587
987
  parsedMsgCount: debugReadChat.parsedMsgCount,
588
988
  returnedMsgCount: debugReadChat.returnedMsgCount,
589
989
  shouldPreferAdapterMessages: debugReadChat.shouldPreferAdapterMessages,
@@ -646,6 +1046,8 @@ export async function handleGetChatDebugBundle(h: CommandHelpers, args: any): Pr
646
1046
  providerSessionId: readResult.providerSessionId,
647
1047
  transcriptAuthority: readResult.transcriptAuthority,
648
1048
  coverage: readResult.coverage,
1049
+ messageSource: readResult.messageSource,
1050
+ transcriptProvenance: readResult.transcriptProvenance,
649
1051
  activeModal: readResult.activeModal,
650
1052
  messagesTail: Array.isArray(readResult.messages) ? readResult.messages.slice(-20) : [],
651
1053
  debugReadChat: readResult.debugReadChat,
@@ -817,16 +1219,56 @@ export async function handleChatHistory(h: CommandHelpers, args: any): Promise<C
817
1219
  : typeof (h.currentSession as any)?.workspace === 'string'
818
1220
  ? (h.currentSession as any).workspace
819
1221
  : undefined;
820
- const result = readProviderChatHistory(agentStr, {
821
- canonicalHistory: provider?.canonicalHistory,
822
- historySessionId,
823
- workspace,
824
- offset: offset || 0,
825
- limit: limit || 30,
826
- excludeRecentCount,
827
- historyBehavior: provider?.historyBehavior,
828
- scripts: provider?.scripts as any,
829
- });
1222
+ const exactNativeHistoryScope = Boolean(
1223
+ (typeof args?.targetSessionId === 'string' && args.targetSessionId.trim())
1224
+ || (typeof args?.historySessionId === 'string' && args.historySessionId.trim())
1225
+ || (typeof args?.providerSessionId === 'string' && args.providerSessionId.trim())
1226
+ );
1227
+ const result = supportsCliNativeTranscript(agentStr, provider) && isNativeSourceCanonicalHistory(provider?.canonicalHistory)
1228
+ ? readCliProviderNativeHistory(agentStr, {
1229
+ canonicalHistory: provider?.canonicalHistory,
1230
+ historySessionId,
1231
+ workspace,
1232
+ offset: offset || 0,
1233
+ limit: limit || 30,
1234
+ excludeRecentCount,
1235
+ historyBehavior: provider?.historyBehavior,
1236
+ scripts: provider?.scripts as any,
1237
+ })
1238
+ : readProviderChatHistory(agentStr, {
1239
+ canonicalHistory: provider?.canonicalHistory,
1240
+ historySessionId,
1241
+ workspace,
1242
+ offset: offset || 0,
1243
+ limit: limit || 30,
1244
+ excludeRecentCount,
1245
+ historyBehavior: provider?.historyBehavior,
1246
+ scripts: provider?.scripts as any,
1247
+ });
1248
+ if (supportsCliNativeTranscript(agentStr, provider) && isNativeSourceCanonicalHistory(provider?.canonicalHistory)) {
1249
+ const lookup = (result as any).lookup === 'workspace' ? 'workspace' : 'session';
1250
+ const messages = Array.isArray((result as any).messages)
1251
+ ? normalizeNativeHistoryMessages(agentStr, (result as any).messages as ChatMessage[], (result as any)?.providerSessionId)
1252
+ : [];
1253
+ const historyProviderSessionId = typeof (result as any)?.providerSessionId === 'string'
1254
+ ? (result as any).providerSessionId
1255
+ : readHistorySessionIdFromMessages(messages) || historySessionId;
1256
+ const safeMapping = hasSafeNativeHistoryMapping({
1257
+ historySessionId: lookup === 'workspace' ? undefined : historySessionId,
1258
+ providerSessionId: lookup === 'workspace' ? undefined : historyProviderSessionId,
1259
+ workspace,
1260
+ nativeMessages: messages,
1261
+ });
1262
+ if ((result as any).source === 'provider-native' && messages.length > 0 && !safeMapping) {
1263
+ return {
1264
+ success: true,
1265
+ messages: [],
1266
+ hasMore: false,
1267
+ source: 'native-unavailable',
1268
+ agent: agentStr,
1269
+ };
1270
+ }
1271
+ }
830
1272
  return { success: true, ...result, agent: agentStr };
831
1273
  } catch (e: any) {
832
1274
  return { success: false, error: e.message };
@@ -874,7 +1316,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
874
1316
  ? parsedRecord.coverage
875
1317
  : undefined;
876
1318
  const activeModal = parsedRecord.activeModal ?? parsedRecord.modal ?? null;
877
- const returnedStatus = normalizeCliReadChatStatus(parsedRecord.status, activeModal, adapter, adapterStatus);
1319
+ const returnedStatus = normalizeCliReadChatStatus(parsedRecord.status, activeModal, adapter, adapterStatus, parsedRecord.messages);
878
1320
  const runtimeMessageMerger = getTargetInstance(h, args) as RuntimeChatMessageMerger | null;
879
1321
  const parsedMessages = finalizeStreamingMessagesWhenIdle(parsedRecord.messages as ChatMessage[], returnedStatus);
880
1322
  const returnedMessages = runtimeMessageMerger?.category === 'cli'
@@ -882,25 +1324,194 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
882
1324
  && typeof runtimeMessageMerger.mergeRuntimeChatMessages === 'function'
883
1325
  ? runtimeMessageMerger.mergeRuntimeChatMessages(parsedMessages)
884
1326
  : parsedMessages;
1327
+ const providerType = provider?.type || adapter.cliType;
1328
+ let selectedMessages = returnedMessages;
1329
+ let selectedTitle = title;
1330
+ let selectedProviderSessionId = providerSessionId;
1331
+ let selectedTranscriptAuthority = transcriptAuthority;
1332
+ let selectedCoverage = coverage;
1333
+ let messageSource = buildCliMessageSourceProvenance({
1334
+ selected: 'pty-parser',
1335
+ provider: adapter.cliType,
1336
+ fallbackReason: supportsCliNativeTranscript(providerType, provider) ? 'native_history_not_checked' : 'provider_native_transcript_not_supported',
1337
+ ptyMessages: returnedMessages,
1338
+ returnedMessages,
1339
+ ptyStatusApprovalOnly: false,
1340
+ });
1341
+
1342
+ if (supportsCliNativeTranscript(providerType, provider) && isNativeSourceCanonicalHistory(provider?.canonicalHistory)) {
1343
+ const agentStr = provider?.type || args?.agentType || getCurrentProviderType(h, adapter.cliType);
1344
+ const workspace = typeof args?.workspace === 'string'
1345
+ ? args.workspace
1346
+ : typeof (h.currentSession as any)?.workspace === 'string'
1347
+ ? (h.currentSession as any).workspace
1348
+ : typeof adapter.workingDir === 'string'
1349
+ ? adapter.workingDir
1350
+ : undefined;
1351
+ const nativeHistoryLimit = Math.max(
1352
+ normalizeReadChatTailLimit(args) || 0,
1353
+ returnedMessages.length,
1354
+ 200,
1355
+ );
1356
+ const nativeHistorySessionId = resolveCliNativeHistorySessionId(args, historySessionId, providerSessionId);
1357
+ const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
1358
+ const exactNativeHistoryScope = Boolean(
1359
+ (typeof args?.historySessionId === 'string' && args.historySessionId.trim())
1360
+ || (typeof args?.providerSessionId === 'string' && args.providerSessionId.trim())
1361
+ || providerSessionId
1362
+ || (nativeHistorySessionId && nativeHistorySessionId !== targetSessionId)
1363
+ || ((h.currentSession as any)?.sessionId === args?.targetSessionId && typeof (h.currentSession as any)?.providerSessionId === 'string' && (h.currentSession as any).providerSessionId.trim())
1364
+ );
1365
+ let nativeHistory: (ReturnType<typeof readProviderChatHistory> & { lookup?: 'session' | 'workspace' }) | null = null;
1366
+ try {
1367
+ nativeHistory = readCliProviderNativeHistory(agentStr, {
1368
+ canonicalHistory: provider?.canonicalHistory,
1369
+ historySessionId: nativeHistorySessionId,
1370
+ workspace,
1371
+ offset: 0,
1372
+ limit: nativeHistoryLimit,
1373
+ excludeRecentCount: 0,
1374
+ historyBehavior: provider?.historyBehavior,
1375
+ scripts: provider?.scripts as any,
1376
+ });
1377
+ } catch (error: any) {
1378
+ const fallbackReason = `native_history_error:${error?.message || String(error)}`;
1379
+ messageSource = buildCliMessageSourceProvenance({
1380
+ selected: 'pty-parser',
1381
+ provider: adapter.cliType,
1382
+ fallbackReason,
1383
+ ptyMessages: returnedMessages,
1384
+ returnedMessages,
1385
+ ptyStatusApprovalOnly: false,
1386
+ });
1387
+ nativeHistory = null;
1388
+ }
1389
+
1390
+ if (nativeHistory) {
1391
+ const nativeMessages = Array.isArray((nativeHistory as any).messages)
1392
+ ? normalizeNativeHistoryMessages(agentStr, (nativeHistory as any).messages as ChatMessage[], (nativeHistory as any)?.providerSessionId)
1393
+ : [];
1394
+ const historyProviderSessionId = typeof (nativeHistory as any)?.providerSessionId === 'string'
1395
+ ? (nativeHistory as any).providerSessionId
1396
+ : readHistorySessionIdFromMessages(nativeMessages) || nativeHistorySessionId || historySessionId;
1397
+ const nativeHistoryCoverage = typeof (nativeHistory as any)?.nativeHistoryCoverage === 'string'
1398
+ ? (nativeHistory as any).nativeHistoryCoverage
1399
+ : undefined;
1400
+ const partialReason = typeof (nativeHistory as any)?.partialReason === 'string'
1401
+ ? (nativeHistory as any).partialReason
1402
+ : undefined;
1403
+ const unavailableReason = typeof (nativeHistory as any)?.unavailableReason === 'string'
1404
+ ? (nativeHistory as any).unavailableReason
1405
+ : undefined;
1406
+ const lookup = (nativeHistory as any).lookup === 'workspace' ? 'workspace' : 'session';
1407
+ const nativeHistorySessionForMapping = adapter.cliType === 'antigravity-cli'
1408
+ && historyProviderSessionId
1409
+ && nativeHistorySessionId
1410
+ && historyProviderSessionId !== nativeHistorySessionId
1411
+ ? undefined
1412
+ : nativeHistorySessionId;
1413
+ const safeMapping = hasSafeNativeHistoryMapping({
1414
+ historySessionId: lookup === 'workspace' ? undefined : nativeHistorySessionForMapping,
1415
+ providerSessionId: lookup === 'workspace' ? undefined : historyProviderSessionId || providerSessionId,
1416
+ workspace,
1417
+ nativeMessages,
1418
+ ptyMessages: returnedMessages,
1419
+ requireWorkspaceContentOverlap: lookup === 'workspace' && !exactNativeHistoryScope,
1420
+ });
1421
+ const freshEnough = isNativeHistoryFreshEnough({
1422
+ sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
1423
+ nativeMessages,
1424
+ ptyMessages: returnedMessages,
1425
+ });
1426
+ const nativeUsableForChatMessages = (nativeHistory as any).source === 'provider-native'
1427
+ && nativeMessages.length > 0
1428
+ && nativeHistoryCoverage !== 'partial'
1429
+ && nativeHistoryCoverage !== 'unavailable'
1430
+ && safeMapping;
1431
+ const allowStaleNativeChatMessages = adapter.cliType === 'antigravity-cli' && nativeUsableForChatMessages;
1432
+ if (nativeUsableForChatMessages && (freshEnough || allowStaleNativeChatMessages)) {
1433
+ selectedMessages = finalizeStreamingMessagesWhenIdle(nativeMessages, returnedStatus);
1434
+ selectedProviderSessionId = historyProviderSessionId || providerSessionId;
1435
+ selectedTranscriptAuthority = 'provider';
1436
+ selectedCoverage = (nativeHistory as any).hasMore ? 'tail' : 'full';
1437
+ messageSource = buildCliMessageSourceProvenance({
1438
+ selected: 'native-history',
1439
+ provider: adapter.cliType,
1440
+ nativeHandle: selectedProviderSessionId || nativeHistorySessionId || historySessionId,
1441
+ nativeSource: (nativeHistory as any).source,
1442
+ sourcePath: (nativeHistory as any).sourcePath,
1443
+ sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
1444
+ nativeHistoryCoverage,
1445
+ partialReason,
1446
+ unavailableReason,
1447
+ nativeMessages,
1448
+ ptyMessages: returnedMessages,
1449
+ returnedMessages: selectedMessages,
1450
+ safeMapping,
1451
+ freshEnough,
1452
+ ptyStatusApprovalOnly: true,
1453
+ });
1454
+ } else {
1455
+ const fallbackReason = buildNativeHistoryFallbackReason({
1456
+ providerType,
1457
+ provider,
1458
+ nativeSource: (nativeHistory as any).source,
1459
+ nativeHistoryCoverage,
1460
+ unavailableReason,
1461
+ nativeMessageCount: nativeMessages.length,
1462
+ safeMapping,
1463
+ freshEnough,
1464
+ });
1465
+ messageSource = buildCliMessageSourceProvenance({
1466
+ selected: 'pty-parser',
1467
+ provider: adapter.cliType,
1468
+ nativeHandle: historyProviderSessionId || nativeHistorySessionId || historySessionId,
1469
+ fallbackReason,
1470
+ nativeSource: (nativeHistory as any).source,
1471
+ sourcePath: (nativeHistory as any).sourcePath,
1472
+ sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
1473
+ nativeHistoryCoverage,
1474
+ partialReason,
1475
+ unavailableReason,
1476
+ nativeMessages,
1477
+ ptyMessages: returnedMessages,
1478
+ returnedMessages,
1479
+ safeMapping,
1480
+ freshEnough,
1481
+ ptyStatusApprovalOnly: false,
1482
+ });
1483
+ }
1484
+ }
1485
+ }
885
1486
  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
1487
  return buildReadChatCommandResult({
887
- messages: returnedMessages,
1488
+ messages: selectedMessages,
888
1489
  status: returnedStatus,
889
1490
  activeModal,
1491
+ messageSource,
1492
+ transcriptProvenance: messageSource,
890
1493
  debugReadChat: {
891
1494
  provider: adapter.cliType,
892
1495
  targetSessionId: String(args?.targetSessionId || ''),
893
1496
  adapterStatus: String(adapterStatus.status || ''),
894
1497
  parsedStatus: String(parsedRecord.status || ''),
895
1498
  returnedStatus: String(returnedStatus || ''),
896
- shouldPreferAdapterMessages: false,
1499
+ selectedMessageSource: (messageSource as any).selected,
1500
+ messageSource,
1501
+ shouldPreferAdapterMessages: supportsCliNativeTranscript(providerType, provider)
1502
+ && isNativeSourceCanonicalHistory(provider?.canonicalHistory)
1503
+ && (messageSource as any).selected !== 'native-history'
1504
+ && typeof (messageSource as any).fallbackReason === 'string'
1505
+ && (messageSource as any).fallbackReason.startsWith('native_history_')
1506
+ && (messageSource as any).fallbackReason !== 'native_history_not_checked'
1507
+ && !(selectedTranscriptAuthority === 'provider' && selectedCoverage === 'full'),
897
1508
  parsedMsgCount: parsedRecord.messages.length,
898
- returnedMsgCount: returnedMessages.length,
1509
+ returnedMsgCount: selectedMessages.length,
899
1510
  },
900
- ...(title ? { title } : {}),
901
- ...(providerSessionId ? { providerSessionId } : {}),
902
- ...(transcriptAuthority ? { transcriptAuthority } : {}),
903
- ...(coverage ? { coverage } : {}),
1511
+ ...(selectedTitle ? { title: selectedTitle } : {}),
1512
+ ...(selectedProviderSessionId ? { providerSessionId: selectedProviderSessionId } : {}),
1513
+ ...(selectedTranscriptAuthority ? { transcriptAuthority: selectedTranscriptAuthority } : {}),
1514
+ ...(selectedCoverage ? { coverage: selectedCoverage } : {}),
904
1515
  }, args);
905
1516
  }
906
1517
  const historyLimit = normalizeReadChatTailLimit(args);
@@ -911,7 +1522,24 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
911
1522
  : typeof (h.currentSession as any)?.workspace === 'string'
912
1523
  ? (h.currentSession as any).workspace
913
1524
  : undefined;
914
- const history = readProviderChatHistory(agentStr, {
1525
+ const exactNativeHistoryScope = Boolean(
1526
+ (typeof args?.targetSessionId === 'string' && args.targetSessionId.trim())
1527
+ || (typeof args?.historySessionId === 'string' && args.historySessionId.trim())
1528
+ || (typeof args?.providerSessionId === 'string' && args.providerSessionId.trim())
1529
+ || ((h.currentSession as any)?.sessionId === args?.targetSessionId && typeof (h.currentSession as any)?.providerSessionId === 'string' && (h.currentSession as any).providerSessionId.trim())
1530
+ );
1531
+ const history = supportsCliNativeTranscript(agentStr, provider) && isNativeSourceCanonicalHistory(provider?.canonicalHistory)
1532
+ ? readCliProviderNativeHistory(agentStr, {
1533
+ canonicalHistory: provider?.canonicalHistory,
1534
+ historySessionId,
1535
+ workspace,
1536
+ offset: 0,
1537
+ limit: historyLimit,
1538
+ excludeRecentCount: 0,
1539
+ historyBehavior: provider?.historyBehavior,
1540
+ scripts: provider?.scripts as any,
1541
+ })
1542
+ : readProviderChatHistory(agentStr, {
915
1543
  canonicalHistory: provider?.canonicalHistory,
916
1544
  historySessionId,
917
1545
  workspace,
@@ -921,12 +1549,81 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
921
1549
  historyBehavior: provider?.historyBehavior,
922
1550
  scripts: provider?.scripts as any,
923
1551
  });
1552
+ const lookup = (history as any).lookup === 'workspace' ? 'workspace' : 'session';
1553
+ const historyMessages = Array.isArray((history as any)?.messages)
1554
+ ? normalizeNativeHistoryMessages(agentStr, (history as any).messages as ChatMessage[], (history as any)?.providerSessionId)
1555
+ : [];
924
1556
  const historyProviderSessionId = typeof (history as any)?.providerSessionId === 'string'
925
1557
  ? (history as any).providerSessionId
926
- : historySessionId;
1558
+ : readHistorySessionIdFromMessages(historyMessages) || historySessionId;
1559
+ const nativeHistoryCoverage = typeof (history as any)?.nativeHistoryCoverage === 'string'
1560
+ ? (history as any).nativeHistoryCoverage
1561
+ : undefined;
1562
+ const partialReason = typeof (history as any)?.partialReason === 'string'
1563
+ ? (history as any).partialReason
1564
+ : undefined;
1565
+ const unavailableReason = typeof (history as any)?.unavailableReason === 'string'
1566
+ ? (history as any).unavailableReason
1567
+ : undefined;
1568
+ const safeMapping = supportsCliNativeTranscript(agentStr, provider)
1569
+ ? hasSafeNativeHistoryMapping({
1570
+ historySessionId: lookup === 'workspace' ? undefined : historySessionId,
1571
+ providerSessionId: lookup === 'workspace' ? undefined : historyProviderSessionId,
1572
+ workspace,
1573
+ nativeMessages: historyMessages,
1574
+ })
1575
+ : false;
1576
+ const nativeSelected = supportsCliNativeTranscript(agentStr, provider)
1577
+ && (history as any).source === 'provider-native'
1578
+ && historyMessages.length > 0
1579
+ && nativeHistoryCoverage !== 'partial'
1580
+ && nativeHistoryCoverage !== 'unavailable'
1581
+ && safeMapping;
1582
+ const messageSource = buildCliMessageSourceProvenance({
1583
+ selected: nativeSelected ? 'native-history' : 'pty-parser',
1584
+ provider: agentStr,
1585
+ nativeHandle: historyProviderSessionId || historySessionId,
1586
+ fallbackReason: nativeSelected
1587
+ ? undefined
1588
+ : buildNativeHistoryFallbackReason({
1589
+ providerType: agentStr,
1590
+ provider,
1591
+ nativeSource: (history as any).source,
1592
+ nativeHistoryCoverage,
1593
+ unavailableReason,
1594
+ nativeMessageCount: historyMessages.length,
1595
+ safeMapping,
1596
+ freshEnough: true,
1597
+ }),
1598
+ nativeSource: (history as any).source,
1599
+ sourcePath: (history as any).sourcePath,
1600
+ sourceMtimeMs: (history as any).sourceMtimeMs,
1601
+ nativeHistoryCoverage,
1602
+ partialReason,
1603
+ unavailableReason,
1604
+ nativeMessages: historyMessages,
1605
+ returnedMessages: historyMessages,
1606
+ safeMapping,
1607
+ freshEnough: true,
1608
+ ptyStatusApprovalOnly: false,
1609
+ });
1610
+ const requiresNativeSource = supportsCliNativeTranscript(agentStr, provider)
1611
+ && isNativeSourceCanonicalHistory(provider?.canonicalHistory);
1612
+ if (requiresNativeSource && !nativeSelected) {
1613
+ return {
1614
+ success: false,
1615
+ code: 'native_history_not_safely_available',
1616
+ error: 'Provider-native history was not safely available for the requested CLI session.',
1617
+ providerSessionId: historyProviderSessionId,
1618
+ messageSource,
1619
+ transcriptProvenance: messageSource,
1620
+ };
1621
+ }
927
1622
  return buildReadChatCommandResult({
928
- messages: Array.isArray((history as any)?.messages) ? (history as any).messages : [],
1623
+ messages: historyMessages,
929
1624
  status: 'idle',
1625
+ messageSource,
1626
+ transcriptProvenance: messageSource,
930
1627
  ...(typeof (history as any)?.title === 'string' ? { title: (history as any).title } : {}),
931
1628
  ...(historyProviderSessionId ? { providerSessionId: historyProviderSessionId } : {}),
932
1629
  ...(((provider?.historyBehavior as any)?.transcriptAuthority === 'provider' || (provider?.historyBehavior as any)?.transcriptAuthority === 'daemon')
@@ -1742,6 +2439,9 @@ export async function handleResolveAction(h: CommandHelpers, args: any): Promise
1742
2439
  if (buttonIndex < 0 && (action === 'always' || /always/i.test(button))) {
1743
2440
  buttonIndex = buttons.findIndex(b => /always/i.test(b));
1744
2441
  }
2442
+ if (buttonIndex < 0 && (action === 'approve' || action === 'accept')) {
2443
+ buttonIndex = pickApprovalButton(buttons, provider).index;
2444
+ }
1745
2445
  if (buttonIndex < 0) {
1746
2446
  return { success: false, error: 'Approval action did not match any visible button' };
1747
2447
  }