@adhdev/daemon-core 0.9.82-rc.13 → 0.9.82-rc.131

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 (82) hide show
  1. package/dist/chat/subscription-updates.d.ts +1 -0
  2. package/dist/cli-adapter-types.d.ts +4 -1
  3. package/dist/cli-adapters/provider-cli-adapter.d.ts +25 -1
  4. package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
  5. package/dist/cli-adapters/provider-cli-shared.d.ts +14 -0
  6. package/dist/commands/router.d.ts +22 -0
  7. package/dist/config/chat-history.d.ts +4 -0
  8. package/dist/config/mesh-config.d.ts +68 -1
  9. package/dist/git/git-commands.d.ts +5 -1
  10. package/dist/index.d.ts +15 -5
  11. package/dist/index.js +7538 -1380
  12. package/dist/index.js.map +1 -1
  13. package/dist/index.mjs +7494 -1364
  14. package/dist/index.mjs.map +1 -1
  15. package/dist/installer.d.ts +1 -4
  16. package/dist/launch.d.ts +1 -1
  17. package/dist/logging/async-batch-writer.d.ts +10 -0
  18. package/dist/mesh/beads-db.d.ts +18 -0
  19. package/dist/mesh/mesh-active-work.d.ts +73 -0
  20. package/dist/mesh/mesh-events.d.ts +54 -5
  21. package/dist/mesh/mesh-fast-forward.d.ts +39 -0
  22. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  23. package/dist/mesh/mesh-ledger.d.ts +38 -1
  24. package/dist/mesh/mesh-refine-status.d.ts +27 -0
  25. package/dist/mesh/mesh-work-queue.d.ts +27 -5
  26. package/dist/mesh/preview-freshness.d.ts +18 -0
  27. package/dist/mesh/refine-config.d.ts +193 -0
  28. package/dist/mesh/worktree-bootstrap-config.d.ts +115 -0
  29. package/dist/providers/chat-message-normalization.d.ts +1 -0
  30. package/dist/providers/cli-provider-instance.d.ts +6 -1
  31. package/dist/repo-mesh-types.d.ts +62 -0
  32. package/dist/status/reporter.d.ts +2 -0
  33. package/package.json +3 -1
  34. package/src/boot/daemon-lifecycle.ts +1 -0
  35. package/src/chat/subscription-updates.ts +5 -1
  36. package/src/cli-adapter-types.ts +2 -1
  37. package/src/cli-adapters/provider-cli-adapter.ts +473 -18
  38. package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
  39. package/src/cli-adapters/provider-cli-parse.ts +4 -0
  40. package/src/cli-adapters/provider-cli-runtime.ts +3 -1
  41. package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
  42. package/src/cli-adapters/provider-cli-shared.ts +32 -10
  43. package/src/commands/chat-commands.ts +1065 -40
  44. package/src/commands/cli-manager.ts +138 -2
  45. package/src/commands/handler.ts +8 -1
  46. package/src/commands/mesh-coordinator.ts +13 -143
  47. package/src/commands/router.ts +3238 -423
  48. package/src/config/chat-history.ts +37 -9
  49. package/src/config/mesh-config.ts +249 -2
  50. package/src/config/recent-activity.ts +8 -2
  51. package/src/daemon/dev-cli-debug.ts +10 -1
  52. package/src/detection/ide-detector.ts +26 -16
  53. package/src/git/git-commands.ts +17 -5
  54. package/src/index.ts +41 -4
  55. package/src/installer.d.ts +1 -1
  56. package/src/installer.ts +8 -6
  57. package/src/launch.d.ts +1 -1
  58. package/src/launch.ts +37 -28
  59. package/src/logging/async-batch-writer.ts +55 -0
  60. package/src/logging/logger.ts +2 -1
  61. package/src/mesh/beads-db.ts +176 -0
  62. package/src/mesh/coordinator-prompt.ts +31 -8
  63. package/src/mesh/mesh-active-work.ts +295 -0
  64. package/src/mesh/mesh-events.ts +595 -48
  65. package/src/mesh/mesh-fast-forward.ts +430 -0
  66. package/src/mesh/mesh-host-ownership.ts +73 -0
  67. package/src/mesh/mesh-ledger.ts +138 -1
  68. package/src/mesh/mesh-refine-status.ts +145 -0
  69. package/src/mesh/mesh-work-queue.ts +199 -137
  70. package/src/mesh/preview-freshness.ts +118 -0
  71. package/src/mesh/refine-config.ts +366 -0
  72. package/src/mesh/worktree-bootstrap-config.ts +234 -0
  73. package/src/providers/approval-utils.ts +12 -5
  74. package/src/providers/chat-message-normalization.ts +7 -12
  75. package/src/providers/cli-provider-instance.ts +289 -36
  76. package/src/providers/ide-provider-instance.ts +17 -3
  77. package/src/providers/provider-loader.ts +10 -4
  78. package/src/providers/read-chat-contract.ts +1 -1
  79. package/src/providers/version-archive.ts +38 -20
  80. package/src/repo-mesh-types.ts +67 -0
  81. package/src/status/reporter.ts +15 -0
  82. 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, readChatHistory, 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 {
@@ -32,6 +40,7 @@ interface ApprovalSelectableInstance extends ProviderInstance {
32
40
 
33
41
  interface RuntimeChatMessageMerger extends ProviderInstance {
34
42
  mergeRuntimeChatMessages?(messages: ChatMessage[]): ChatMessage[];
43
+ recordAcknowledgedUserInput?(input: InputEnvelope | string): void;
35
44
  }
36
45
 
37
46
  type LegacyStringScript = (params?: Record<string, unknown> | string) => string;
@@ -48,6 +57,16 @@ function getTargetedCliAdapter(h: CommandHelpers, args: any, providerType?: stri
48
57
  return h.getCliAdapter(args?.targetSessionId || providerType || h.currentSession?.providerType || h.currentManagerKey);
49
58
  }
50
59
 
60
+ function getExplicitHistorySessionId(args: any): string | undefined {
61
+ const explicit = typeof args?.historySessionId === 'string' ? args.historySessionId.trim() : '';
62
+ if (explicit) return explicit;
63
+
64
+ const explicitProviderSessionId = typeof args?.providerSessionId === 'string' ? args.providerSessionId.trim() : '';
65
+ if (explicitProviderSessionId) return explicitProviderSessionId;
66
+
67
+ return undefined;
68
+ }
69
+
51
70
  function getTargetInstance(h: CommandHelpers, args: any): ApprovalSelectableInstance | null {
52
71
  const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
53
72
  const sessionId = targetSessionId || h.currentSession?.sessionId || '';
@@ -139,19 +158,47 @@ async function waitOnceForFreshHermesCliStart(adapter: CliAdapter, log: (msg: st
139
158
  }
140
159
 
141
160
  function getHistorySessionId(h: CommandHelpers, args: any): string | undefined {
142
- const explicit = typeof args?.historySessionId === 'string' ? args.historySessionId.trim() : '';
161
+ const explicit = getExplicitHistorySessionId(args);
143
162
  if (explicit) return explicit;
144
163
 
145
- const explicitProviderSessionId = typeof args?.providerSessionId === 'string' ? args.providerSessionId.trim() : '';
146
- if (explicitProviderSessionId) return explicitProviderSessionId;
147
-
148
164
  const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
149
165
  if (!targetSessionId) return undefined;
150
166
 
151
- const instance = h.ctx.instanceManager?.getInstance(targetSessionId);
167
+ const session = h.ctx.sessionRegistry?.get(targetSessionId) as any;
168
+ const registeredProviderSessionId = typeof session?.providerSessionId === 'string' ? session.providerSessionId.trim() : '';
169
+ if (registeredProviderSessionId) return registeredProviderSessionId;
170
+
171
+ const instance = getTargetInstance(h, args);
152
172
  const state = instance?.getState?.();
153
173
  const providerSessionId = typeof state?.providerSessionId === 'string' ? state.providerSessionId.trim() : '';
154
- return providerSessionId || targetSessionId;
174
+ if (providerSessionId) return providerSessionId;
175
+
176
+ const currentSession = h.currentSession as any;
177
+ if (currentSession?.sessionId === targetSessionId) {
178
+ const currentProviderSessionId = typeof currentSession.providerSessionId === 'string'
179
+ ? currentSession.providerSessionId.trim()
180
+ : '';
181
+ if (currentProviderSessionId) return currentProviderSessionId;
182
+ }
183
+
184
+ return targetSessionId;
185
+ }
186
+
187
+ function resolveCliNativeHistorySessionId(args: any, currentHistorySessionId: string | undefined, parsedProviderSessionId: string | undefined): string | undefined {
188
+ const explicit = getExplicitHistorySessionId(args);
189
+ if (explicit) return explicit;
190
+
191
+ const parsed = typeof parsedProviderSessionId === 'string' ? parsedProviderSessionId.trim() : '';
192
+ const current = typeof currentHistorySessionId === 'string' ? currentHistorySessionId.trim() : '';
193
+ const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
194
+
195
+ // getHistorySessionId falls back to the runtime session id when no native
196
+ // handle has been registered yet. For live CLI adapters the parser may
197
+ // already know the provider-native handle; prefer it over the runtime id so
198
+ // exact native reads do not miss the worker transcript and fall back to PTY
199
+ // or same-workspace history.
200
+ if (parsed && (!current || current === targetSessionId)) return parsed;
201
+ return current || parsed || undefined;
155
202
  }
156
203
 
157
204
  function getInteractionId(args: any): string | undefined {
@@ -221,6 +268,470 @@ function normalizeReadChatMessages(payload: Record<string, any>): ChatMessage[]
221
268
  return normalizeChatMessages(messages);
222
269
  }
223
270
 
271
+ function getMessageNewestReceivedAt(messages: Array<{ receivedAt?: unknown; timestamp?: unknown }>): number {
272
+ let newest = 0;
273
+ for (const message of messages) {
274
+ const receivedAt = Number(message?.receivedAt ?? message?.timestamp ?? 0);
275
+ if (Number.isFinite(receivedAt) && receivedAt > newest) newest = receivedAt;
276
+ }
277
+ return newest;
278
+ }
279
+
280
+ function readHistorySessionIdFromMessages(messages: ChatMessage[]): string | undefined {
281
+ for (const message of messages as Array<ChatMessage & { historySessionId?: unknown }>) {
282
+ const historySessionId = typeof message?.historySessionId === 'string' ? message.historySessionId.trim() : '';
283
+ if (historySessionId) return historySessionId;
284
+ }
285
+ return undefined;
286
+ }
287
+
288
+ function shouldPreserveNativeIdentity(providerType: string, sessionId: string, message: ChatMessage): boolean {
289
+ const providerUnitKey = typeof message.providerUnitKey === 'string' ? message.providerUnitKey.trim() : '';
290
+ const turnKey = typeof message._turnKey === 'string' ? message._turnKey.trim() : '';
291
+ if (!providerUnitKey || !turnKey) return false;
292
+ if (providerType === 'hermes-cli' && sessionId) {
293
+ return providerUnitKey.startsWith(`${providerType}:native:${sessionId}:`)
294
+ && turnKey.startsWith(`${providerType}:native-turn:${sessionId}:`);
295
+ }
296
+ return true;
297
+ }
298
+
299
+ function normalizeNativeHistoryMessages(providerType: string, messages: ChatMessage[], nativeSessionId?: string): ChatMessage[] {
300
+ let turnIndex = 0;
301
+ return normalizeChatMessages(messages).map((message, index) => {
302
+ const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
303
+ const kind = typeof message.kind === 'string' && message.kind.trim() ? message.kind.trim() : (role === 'system' ? 'system' : 'standard');
304
+ if ((role === 'user' || role === 'human') && index > 0) turnIndex += 1;
305
+ const historySessionId = typeof (message as any).historySessionId === 'string'
306
+ ? (message as any).historySessionId.trim()
307
+ : '';
308
+ const contentHash = hashSignatureParts([
309
+ providerType,
310
+ historySessionId,
311
+ String(message.receivedAt || message.timestamp || index),
312
+ role,
313
+ kind,
314
+ flattenContent(message.content),
315
+ ]).slice(0, 12);
316
+ const nativeIdentitySessionId = historySessionId || (typeof nativeSessionId === 'string' ? nativeSessionId.trim() : '');
317
+ const preserveNativeIdentity = shouldPreserveNativeIdentity(providerType, nativeIdentitySessionId, message);
318
+ const existingProviderUnitKey = typeof message.providerUnitKey === 'string' ? message.providerUnitKey.trim() : '';
319
+ const existingTurnKey = typeof message._turnKey === 'string' ? message._turnKey.trim() : '';
320
+ const providerUnitKey = preserveNativeIdentity
321
+ ? existingProviderUnitKey
322
+ : `${providerType}:native:${nativeIdentitySessionId || 'workspace'}:${index}:${role || 'message'}:${kind}:${contentHash}`;
323
+ const meta = message.meta && typeof message.meta === 'object' ? message.meta as Record<string, unknown> : undefined;
324
+ const isSystemSessionStart = role === 'system' || kind === 'system' || kind === 'session_start';
325
+ const isActivity = role === 'assistant' && (kind === 'tool' || kind === 'terminal' || kind === 'thought');
326
+ return {
327
+ ...message,
328
+ role: role === 'human' ? 'user' : (role || 'assistant'),
329
+ kind: isSystemSessionStart ? 'system' : kind,
330
+ providerUnitKey,
331
+ bubbleId: typeof message.bubbleId === 'string' && message.bubbleId.trim()
332
+ && preserveNativeIdentity
333
+ ? message.bubbleId.trim()
334
+ : `bubble:${providerUnitKey}`,
335
+ _turnKey: preserveNativeIdentity
336
+ ? existingTurnKey
337
+ : `${providerType}:native-turn:${nativeIdentitySessionId || 'workspace'}:${turnIndex}`,
338
+ bubbleState: message.bubbleState || 'final',
339
+ ...(isSystemSessionStart ? {
340
+ visibility: message.visibility || 'hidden',
341
+ transcriptVisibility: message.transcriptVisibility || 'hidden',
342
+ audience: message.audience || 'internal',
343
+ source: message.source || 'runtime_status',
344
+ } : isActivity ? {
345
+ source: message.source || (kind === 'terminal' ? 'terminal_command' : 'tool_call'),
346
+ meta: { ...meta, label: message.senderName || meta?.label || (kind === 'terminal' ? 'Terminal' : 'Tool') },
347
+ } : {
348
+ source: message.source || (role === 'assistant' ? 'assistant_text' : undefined),
349
+ }),
350
+ } as ChatMessage;
351
+ });
352
+ }
353
+
354
+ function buildCliMessageSourceProvenance(args: {
355
+ selected: 'native-history' | 'pty-parser';
356
+ provider: string;
357
+ nativeHandle?: string;
358
+ sessionWorkspace?: string;
359
+ intendedWorkspace?: string;
360
+ transcriptWorkspace?: string;
361
+ fallbackReason?: string;
362
+ nativeSource?: string;
363
+ sourcePath?: string;
364
+ sourceMtimeMs?: number;
365
+ nativeHistoryCoverage?: string;
366
+ partialReason?: string;
367
+ unavailableReason?: string;
368
+ nativeMessages?: ChatMessage[];
369
+ ptyMessages?: ChatMessage[];
370
+ returnedMessages?: ChatMessage[];
371
+ safeMapping?: boolean;
372
+ freshEnough?: boolean;
373
+ ptyStatusApprovalOnly?: boolean;
374
+ }): Record<string, unknown> {
375
+ const sourceMtimeMs = Number(args.sourceMtimeMs || 0);
376
+ const sourceMtimeAgeMs = sourceMtimeMs > 0 ? Math.max(0, Date.now() - sourceMtimeMs) : undefined;
377
+ const nativeMessages = args.nativeMessages || [];
378
+ const ptyMessages = args.ptyMessages || [];
379
+ const returnedMessages = args.returnedMessages || [];
380
+ const identityStatus = args.selected === 'native-history'
381
+ ? 'safe'
382
+ : args.fallbackReason === 'native_history_not_safely_mapped'
383
+ ? 'ambiguous_session_identity'
384
+ : args.fallbackReason?.startsWith('native_history_unavailable')
385
+ ? 'transcript_unmapped'
386
+ : undefined;
387
+ return {
388
+ selected: args.selected,
389
+ provider: args.provider,
390
+ providerType: args.provider,
391
+ ...(identityStatus ? { identityStatus } : {}),
392
+ ...(args.nativeHandle ? { nativeHandle: args.nativeHandle } : {}),
393
+ ...(args.nativeHandle ? { nativeSessionId: args.nativeHandle } : {}),
394
+ ...(args.sessionWorkspace ? { sessionWorkspace: args.sessionWorkspace } : {}),
395
+ ...(args.intendedWorkspace ? { intendedWorkspace: args.intendedWorkspace } : {}),
396
+ ...(args.transcriptWorkspace ? { transcriptWorkspace: args.transcriptWorkspace } : {}),
397
+ ...(args.fallbackReason ? { fallbackReason: args.fallbackReason } : {}),
398
+ ...(args.nativeSource ? { nativeSource: args.nativeSource } : {}),
399
+ ...(args.sourcePath ? { sourcePath: args.sourcePath } : {}),
400
+ ...(args.nativeHistoryCoverage ? { nativeHistoryCoverage: args.nativeHistoryCoverage } : {}),
401
+ ...(args.partialReason ? { partialReason: args.partialReason } : {}),
402
+ ...(args.unavailableReason ? { unavailableReason: args.unavailableReason } : {}),
403
+ ptyStatusApprovalOnly: args.ptyStatusApprovalOnly === true,
404
+ staleness: {
405
+ sourceMtimeMs: sourceMtimeMs || undefined,
406
+ sourceMtimeAgeMs,
407
+ nativeNewestMessageAt: getMessageNewestReceivedAt(nativeMessages),
408
+ ptyNewestMessageAt: getMessageNewestReceivedAt(ptyMessages),
409
+ freshEnough: args.freshEnough === true,
410
+ },
411
+ coverage: {
412
+ nativeMessageCount: nativeMessages.length,
413
+ ptyMessageCount: ptyMessages.length,
414
+ returnedMessageCount: returnedMessages.length,
415
+ safeMapping: args.safeMapping === true,
416
+ // true when PTY message bodies are suppressed and must not be treated as
417
+ // chat content. PTY may still contribute status/approval/screen evidence.
418
+ ptyMessagesSuppressed: args.selected === 'native-history' || args.ptyStatusApprovalOnly === true,
419
+ },
420
+ };
421
+ }
422
+
423
+ function buildNativeHistoryFallbackReason(args: {
424
+ providerType: string;
425
+ provider?: ProviderModule;
426
+ nativeSource?: string;
427
+ nativeHistoryCoverage?: string;
428
+ unavailableReason?: string;
429
+ nativeMessageCount: number;
430
+ safeMapping: boolean;
431
+ freshEnough: boolean;
432
+ }): string {
433
+ if (!supportsCliNativeTranscript(args.providerType, args.provider)) return 'provider_native_transcript_not_supported';
434
+ if (args.unavailableReason) return `native_history_unavailable:${args.unavailableReason}`;
435
+ if (args.nativeSource === 'native-unavailable') return 'native_history_unavailable';
436
+ if (args.nativeHistoryCoverage === 'partial') return 'native_history_partial';
437
+ if (args.nativeHistoryCoverage === 'unavailable') return 'native_history_unavailable';
438
+ if (args.nativeSource && args.nativeSource !== 'provider-native') return `native_history_source_${args.nativeSource}`;
439
+ if (args.nativeMessageCount <= 0) return 'native_history_empty';
440
+ if (!args.safeMapping) return 'native_history_not_safely_mapped';
441
+ if (!args.freshEnough) return 'native_history_stale';
442
+ return 'native_history_not_selected';
443
+ }
444
+
445
+ function isUnsafeNativeTranscriptFallback(reason?: string): boolean {
446
+ const value = String(reason || '').trim();
447
+ return value.startsWith('native_history_unavailable')
448
+ || value === 'native_history_not_safely_mapped'
449
+ || value === 'native_history_stale'
450
+ || value === 'native_history_partial';
451
+ }
452
+
453
+ function coerceUnsafeNativeFallbackStatus(status: string, activeModal: unknown): string {
454
+ if (status === 'waiting_approval' && activeModal) return status;
455
+ return 'idle';
456
+ }
457
+
458
+ function isRuntimeInputAckMessage(message: ChatMessage | undefined): boolean {
459
+ if (!message || typeof message !== 'object') return false;
460
+ const role = String((message as any).role || '').trim().toLowerCase();
461
+ if (role !== 'user' && role !== 'human') return false;
462
+ const meta = (message as any).meta;
463
+ return !!meta && typeof meta === 'object' && !Array.isArray(meta) && meta.runtimeInputAck === true;
464
+ }
465
+
466
+ function selectRuntimeInputAckMessages(messages: ChatMessage[]): ChatMessage[] {
467
+ return messages.filter((message) => isRuntimeInputAckMessage(message));
468
+ }
469
+
470
+ function readExactRuntimeMirrorMessages(args: {
471
+ providerType: string;
472
+ targetSessionId?: string;
473
+ currentSessionId?: string;
474
+ tailLimit: number;
475
+ historyBehavior?: ProviderModule['historyBehavior'];
476
+ }): ChatMessage[] {
477
+ const targetSessionId = String(args.targetSessionId || '').trim();
478
+ const currentSessionId = String(args.currentSessionId || '').trim();
479
+ if (!targetSessionId || targetSessionId !== currentSessionId) return [];
480
+
481
+ const history = readChatHistory(
482
+ args.providerType,
483
+ 0,
484
+ Math.max(args.tailLimit || 0, 200),
485
+ targetSessionId,
486
+ 0,
487
+ args.historyBehavior,
488
+ );
489
+ return normalizeChatMessages((history.messages || []) as ChatMessage[])
490
+ .filter((message) => {
491
+ const historySessionId = String((message as any).historySessionId || '').trim();
492
+ const instanceId = String((message as any).instanceId || '').trim();
493
+ return historySessionId === targetSessionId || instanceId === targetSessionId;
494
+ });
495
+ }
496
+
497
+ function normalizeComparableWorkspace(value: unknown): string {
498
+ const text = typeof value === 'string' ? value.trim() : '';
499
+ if (!text) return '';
500
+ return path.resolve(text);
501
+ }
502
+
503
+ function isCurrentRuntimePtySafelyAttributed(args: {
504
+ adapter: CliAdapter;
505
+ helpers: CommandHelpers;
506
+ readChatArgs: any;
507
+ sessionWorkspace?: string;
508
+ intendedWorkspace?: string;
509
+ ptyMessages: ChatMessage[];
510
+ }): boolean {
511
+ if (args.adapter.cliType !== 'codex-cli') return false;
512
+ if (!Array.isArray(args.ptyMessages) || args.ptyMessages.length === 0) return false;
513
+ const targetSessionId = typeof args.readChatArgs?.targetSessionId === 'string'
514
+ ? args.readChatArgs.targetSessionId.trim()
515
+ : '';
516
+ const currentSession = args.helpers.currentSession as any;
517
+ const currentSessionId = typeof currentSession?.sessionId === 'string'
518
+ ? currentSession.sessionId.trim()
519
+ : '';
520
+ if (!targetSessionId || !currentSessionId || targetSessionId !== currentSessionId) return false;
521
+
522
+ const runtimeMeta = typeof (args.adapter as any).getRuntimeMetadata === 'function'
523
+ ? (args.adapter as any).getRuntimeMetadata()
524
+ : null;
525
+ const runtimeId = typeof runtimeMeta?.runtimeId === 'string' ? runtimeMeta.runtimeId.trim() : '';
526
+ if (!runtimeId || runtimeId !== targetSessionId) return false;
527
+ const surfaceKind = typeof runtimeMeta?.surfaceKind === 'string' ? runtimeMeta.surfaceKind : '';
528
+ if (surfaceKind === 'inactive_record' || surfaceKind === 'recovery_snapshot') return false;
529
+
530
+ const sessionWorkspace = normalizeComparableWorkspace(args.sessionWorkspace);
531
+ const adapterWorkspace = normalizeComparableWorkspace(args.adapter.workingDir);
532
+ if (!sessionWorkspace || !adapterWorkspace || sessionWorkspace !== adapterWorkspace) return false;
533
+ const intendedWorkspace = normalizeComparableWorkspace(args.intendedWorkspace);
534
+ if (intendedWorkspace && intendedWorkspace !== sessionWorkspace) return false;
535
+
536
+ const registryEntry = args.helpers.ctx?.sessionRegistry?.get?.(targetSessionId) as any;
537
+ const registryInstanceKey = typeof registryEntry?.adapterKey === 'string' && registryEntry.adapterKey.trim()
538
+ ? registryEntry.adapterKey.trim()
539
+ : typeof registryEntry?.instanceKey === 'string' && registryEntry.instanceKey.trim()
540
+ ? registryEntry.instanceKey.trim()
541
+ : '';
542
+ if (registryInstanceKey) {
543
+ const targetInstance = args.helpers.ctx?.instanceManager?.getInstance?.(registryInstanceKey);
544
+ if (targetInstance) {
545
+ const instanceType = typeof (targetInstance as any).type === 'string' ? (targetInstance as any).type : '';
546
+ if (instanceType && instanceType !== args.adapter.cliType) return false;
547
+ }
548
+ }
549
+
550
+ return true;
551
+ }
552
+
553
+ function supportsCliNativeTranscript(providerType: string, provider?: ProviderModule): boolean {
554
+ if (CLI_NATIVE_TRANSCRIPT_PROVIDERS.has(providerType)) return true;
555
+ return provider?.category === 'cli' && isNativeSourceCanonicalHistory(provider?.canonicalHistory);
556
+ }
557
+
558
+ function getComparableVisibleText(message: ChatMessage | undefined): string {
559
+ if (!message) return '';
560
+ const role = String((message as any).role || '').trim().toLowerCase();
561
+ if (role !== 'user' && role !== 'assistant') return '';
562
+ const kind = String((message as any).kind || 'standard').trim().toLowerCase();
563
+ if (kind && kind !== 'standard') return '';
564
+ const content = flattenContent((message as any).content).replace(/\s+/g, ' ').trim();
565
+ return content;
566
+ }
567
+
568
+ function hasOverlappingVisibleConversationText(nativeMessages: ChatMessage[], ptyMessages: ChatMessage[]): boolean {
569
+ const nativeTexts = nativeMessages.map(getComparableVisibleText).filter(Boolean);
570
+ const ptyTexts = ptyMessages.map(getComparableVisibleText).filter(Boolean);
571
+ if (nativeTexts.length === 0 || ptyTexts.length === 0) return false;
572
+ for (const nativeText of nativeTexts) {
573
+ for (const ptyText of ptyTexts) {
574
+ if (nativeText === ptyText) return true;
575
+ const shorter = nativeText.length <= ptyText.length ? nativeText : ptyText;
576
+ const longer = nativeText.length <= ptyText.length ? ptyText : nativeText;
577
+ if (shorter.length >= 32 && longer.includes(shorter)) return true;
578
+ }
579
+ }
580
+ return false;
581
+ }
582
+
583
+ function hasSafeNativeHistoryMapping(args: {
584
+ historySessionId?: string;
585
+ providerSessionId?: string;
586
+ workspace?: string;
587
+ nativeMessages: ChatMessage[];
588
+ ptyMessages?: ChatMessage[];
589
+ requireWorkspaceContentOverlap?: boolean;
590
+ }): boolean {
591
+ const isCoordinatorTranscript = args.nativeMessages.some((m: any) => {
592
+ const text = typeof m?.content === 'string' ? m.content : JSON.stringify(m?.content || '');
593
+ return text.includes('mesh_send_task') || text.includes('mesh_status') || text.includes('mesh_read_chat') || text.includes('mesh_launch_session');
594
+ });
595
+
596
+ const explicitSessionId = String(args.historySessionId || args.providerSessionId || '').trim();
597
+ if (explicitSessionId) {
598
+ const messageSessionIds = args.nativeMessages
599
+ .map((message: any) => typeof message?.historySessionId === 'string' ? message.historySessionId.trim() : '')
600
+ .filter(Boolean);
601
+ if (messageSessionIds.length > 0) {
602
+ return messageSessionIds.some((id) => id === explicitSessionId);
603
+ }
604
+
605
+ // Messages carry no historySessionId — cannot confirm they belong to the requested session.
606
+ // Only allow a coordinator transcript that is also confirmed by the PTY side; otherwise
607
+ // fail closed so a same-workspace session's history is never silently accepted.
608
+ if (isCoordinatorTranscript && args.ptyMessages && args.ptyMessages.length > 0) {
609
+ const ptyHasCoordinator = args.ptyMessages.some((m: any) => {
610
+ const text = typeof m?.content === 'string' ? m.content : JSON.stringify(m?.content || '');
611
+ return text.includes('mesh_send_task') || text.includes('mesh_status') || text.includes('mesh_read_chat');
612
+ });
613
+ return ptyHasCoordinator;
614
+ }
615
+
616
+ // No historySessionId in messages and no coordinator cross-check: fail closed.
617
+ // Workspace-only matching must not override an explicit session identity.
618
+ return false;
619
+ }
620
+ const workspace = String(args.workspace || '').trim();
621
+ if (!workspace) return false;
622
+ const workspaceMatches = args.nativeMessages.some((message: any) => String(message?.workspace || '').trim() === workspace);
623
+ if (!workspaceMatches) return false;
624
+
625
+ if (isCoordinatorTranscript && args.ptyMessages && args.ptyMessages.length > 0) {
626
+ const ptyHasCoordinator = args.ptyMessages.some((m: any) => {
627
+ const text = typeof m?.content === 'string' ? m.content : JSON.stringify(m?.content || '');
628
+ return text.includes('mesh_send_task') || text.includes('mesh_status') || text.includes('mesh_read_chat');
629
+ });
630
+ if (!ptyHasCoordinator) {
631
+ return false;
632
+ }
633
+ }
634
+
635
+ if (!args.requireWorkspaceContentOverlap) return true;
636
+ return hasOverlappingVisibleConversationText(args.nativeMessages, args.ptyMessages || []);
637
+ }
638
+
639
+ // Provenance boundary: workspace-only native history lookup is never safe
640
+ // because multiple concurrent sessions sharing the same cwd would alias each
641
+ // other. historySessionId (the provider-native session key) is required to
642
+ // establish ownership. hasSafeNativeHistoryMapping() enforces the same
643
+ // invariant after the read; both guards must hold for native history to be used.
644
+ function readCliProviderNativeHistory(agentStr: string, args: {
645
+ canonicalHistory?: ProviderModule['canonicalHistory'];
646
+ historySessionId?: string;
647
+ workspace?: string;
648
+ offset: number;
649
+ limit: number;
650
+ excludeRecentCount: number;
651
+ historyBehavior?: ProviderModule['historyBehavior'];
652
+ scripts?: ProviderScripts;
653
+ }): ReturnType<typeof readProviderChatHistory> & { lookup: 'session' | 'workspace' } {
654
+ if (!args.historySessionId) {
655
+ return {
656
+ messages: [],
657
+ hasMore: false,
658
+ source: 'native-unavailable',
659
+ unavailableReason: 'native_history_workspace_only_lookup_unsafe',
660
+ lookup: 'session',
661
+ } as ReturnType<typeof readProviderChatHistory> & { lookup: 'session' | 'workspace' };
662
+ }
663
+ const sessionHistory = readProviderChatHistory(agentStr, {
664
+ canonicalHistory: args.canonicalHistory,
665
+ historySessionId: args.historySessionId,
666
+ workspace: args.workspace,
667
+ offset: args.offset,
668
+ limit: args.limit,
669
+ excludeRecentCount: args.excludeRecentCount,
670
+ historyBehavior: args.historyBehavior,
671
+ scripts: args.scripts as any,
672
+ });
673
+ // Native transcripts are keyed by provider/runtime session identity. Falling
674
+ // back to workspace makes concurrent local Codex/Hermes sessions alias each
675
+ // other when they share the same cwd.
676
+ return { ...(sessionHistory as any), lookup: 'session' };
677
+ }
678
+
679
+ function readLiveCodexWorkspaceNativeHistory(agentStr: string, args: {
680
+ canonicalHistory?: ProviderModule['canonicalHistory'];
681
+ workspace?: string;
682
+ offset: number;
683
+ limit: number;
684
+ excludeRecentCount: number;
685
+ historyBehavior?: ProviderModule['historyBehavior'];
686
+ scripts?: ProviderScripts;
687
+ }): (ReturnType<typeof readProviderChatHistory> & { lookup: 'workspace' }) | null {
688
+ if (agentStr !== 'codex-cli') return null;
689
+ const workspace = typeof args.workspace === 'string' ? args.workspace.trim() : '';
690
+ if (!workspace) return null;
691
+ const history = readProviderChatHistory(agentStr, {
692
+ canonicalHistory: args.canonicalHistory,
693
+ workspace,
694
+ offset: args.offset,
695
+ limit: args.limit,
696
+ excludeRecentCount: args.excludeRecentCount,
697
+ historyBehavior: args.historyBehavior,
698
+ scripts: args.scripts as any,
699
+ });
700
+ return { ...(history as any), lookup: 'workspace' };
701
+ }
702
+
703
+ function isNativeHistoryFreshEnough(args: {
704
+ sourceMtimeMs?: number;
705
+ nativeMessages: ChatMessage[];
706
+ ptyMessages: ChatMessage[];
707
+ }): boolean {
708
+ const nativeNewest = getMessageNewestReceivedAt(args.nativeMessages);
709
+ const ptyNewest = getMessageNewestReceivedAt(args.ptyMessages);
710
+ if (nativeNewest > 0 && nativeNewest >= ptyNewest) return true;
711
+ const sourceMtimeMs = Number(args.sourceMtimeMs || 0);
712
+ if (sourceMtimeMs > 0 && Date.now() - sourceMtimeMs <= CLI_NATIVE_HISTORY_FRESH_MS) return true;
713
+ return ptyNewest === 0 && nativeNewest > 0;
714
+ }
715
+
716
+ function shouldPreserveReadChatPayloadField(key: string): boolean {
717
+ return key === 'messageSource' || key === 'transcriptProvenance';
718
+ }
719
+
720
+ function updateMessageSourceReturnedCount(value: unknown, returnedMessageCount: number): unknown {
721
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return value;
722
+ const record = value as Record<string, unknown>;
723
+ const coverage = record.coverage && typeof record.coverage === 'object' && !Array.isArray(record.coverage)
724
+ ? record.coverage as Record<string, unknown>
725
+ : undefined;
726
+ if (!coverage) return value;
727
+ return {
728
+ ...record,
729
+ coverage: {
730
+ ...coverage,
731
+ returnedMessageCount,
732
+ },
733
+ };
734
+ }
224
735
 
225
736
  function deriveHistoryDedupKey(message: ChatMessage & { _unitKey?: string; _turnKey?: string }): string | undefined {
226
737
  const unitKey = typeof message._unitKey === 'string' ? message._unitKey.trim() : '';
@@ -281,7 +792,7 @@ function normalizeReadChatCommandStatus(status: unknown, activeModal: unknown):
281
792
  }
282
793
  switch (raw) {
283
794
  case 'starting':
284
- return hasNonEmptyModalButtons(activeModal) ? 'waiting_approval' : 'generating';
795
+ return hasNonEmptyModalButtons(activeModal) ? 'waiting_approval' : 'starting';
285
796
  case 'stopped':
286
797
  case 'disconnected':
287
798
  case 'not_monitored':
@@ -295,6 +806,16 @@ function isGeneratingLikeStatus(status: unknown): boolean {
295
806
  return status === 'generating' || status === 'streaming' || status === 'long_generating' || status === 'starting';
296
807
  }
297
808
 
809
+ function hasVisibleAssistantMessage(messages: unknown[] | undefined): boolean {
810
+ if (!Array.isArray(messages)) return false;
811
+ return messages.some((message: any) => {
812
+ if (!message || message.role !== 'assistant') return false;
813
+ const kind = typeof message.kind === 'string' ? message.kind : 'standard';
814
+ if (kind !== 'standard') return false;
815
+ return String(message.content || '').trim().length > 0;
816
+ });
817
+ }
818
+
298
819
  function shouldTrustCliAdapterTerminalStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any): boolean {
299
820
  if (!isGeneratingLikeStatus(parsedStatus)) return false;
300
821
  if (hasNonEmptyModalButtons(activeModal)) return false;
@@ -304,7 +825,26 @@ function shouldTrustCliAdapterTerminalStatus(parsedStatus: unknown, activeModal:
304
825
  return true;
305
826
  }
306
827
 
307
- function normalizeCliReadChatStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any): string {
828
+ function normalizeCliReadChatStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any, parsedMessages?: unknown[]): string {
829
+ const adapterRawStatus = typeof adapterStatus?.status === 'string' ? adapterStatus.status.trim() : '';
830
+ if (adapterRawStatus === 'starting'
831
+ && isGeneratingLikeStatus(parsedStatus)
832
+ && !hasNonEmptyModalButtons(activeModal)
833
+ && Array.isArray(parsedMessages)
834
+ && parsedMessages.length === 0
835
+ && Array.isArray(adapterStatus?.messages)
836
+ && adapterStatus.messages.length === 0
837
+ && !(typeof adapter.isProcessing === 'function' && adapter.isProcessing())) {
838
+ return 'starting';
839
+ }
840
+ if (
841
+ isGeneratingLikeStatus(adapterRawStatus)
842
+ && parsedStatus === 'idle'
843
+ && !hasNonEmptyModalButtons(activeModal)
844
+ && !hasVisibleAssistantMessage(parsedMessages)
845
+ ) {
846
+ return adapterRawStatus;
847
+ }
308
848
  if (shouldTrustCliAdapterTerminalStatus(parsedStatus, activeModal, adapter, adapterStatus)) return 'idle';
309
849
  return typeof parsedStatus === 'string' && parsedStatus.trim() ? parsedStatus : 'idle';
310
850
  }
@@ -342,6 +882,13 @@ function buildReadChatCommandResult(payload: Record<string, any>, args: any): Co
342
882
  const visibleMessages = filterUserFacingChatMessages(messages);
343
883
  const sync = buildFullTail(visibleMessages, normalizeReadChatTailLimit(args));
344
884
  const hiddenMsgCount = Math.max(0, messages.length - visibleMessages.length);
885
+ const preservedPayloadFields = Object.fromEntries(Object.entries(payload).filter(([key]) => shouldPreserveReadChatPayloadField(key)));
886
+ if (preservedPayloadFields.messageSource) {
887
+ preservedPayloadFields.messageSource = updateMessageSourceReturnedCount(preservedPayloadFields.messageSource, sync.messages.length);
888
+ }
889
+ if (preservedPayloadFields.transcriptProvenance) {
890
+ preservedPayloadFields.transcriptProvenance = updateMessageSourceReturnedCount(preservedPayloadFields.transcriptProvenance, sync.messages.length);
891
+ }
345
892
  const returnedDebugReadChat = debugReadChat
346
893
  ? {
347
894
  ...debugReadChat,
@@ -356,6 +903,7 @@ function buildReadChatCommandResult(payload: Record<string, any>, args: any): Co
356
903
  return {
357
904
  success: true,
358
905
  ...validatedPayload,
906
+ ...preservedPayloadFields,
359
907
  messages: sync.messages,
360
908
  totalMessages: sync.totalMessages,
361
909
  ...(returnedDebugReadChat ? { debugReadChat: returnedDebugReadChat } : {}),
@@ -584,6 +1132,8 @@ function buildChatDebugBundleSummary(bundle: Record<string, unknown>): Record<st
584
1132
  adapterStatus: debugReadChat.adapterStatus,
585
1133
  parsedStatus: debugReadChat.parsedStatus,
586
1134
  returnedStatus: debugReadChat.returnedStatus,
1135
+ selectedMessageSource: debugReadChat.selectedMessageSource,
1136
+ messageSource: debugReadChat.messageSource,
587
1137
  parsedMsgCount: debugReadChat.parsedMsgCount,
588
1138
  returnedMsgCount: debugReadChat.returnedMsgCount,
589
1139
  shouldPreferAdapterMessages: debugReadChat.shouldPreferAdapterMessages,
@@ -646,11 +1196,20 @@ export async function handleGetChatDebugBundle(h: CommandHelpers, args: any): Pr
646
1196
  providerSessionId: readResult.providerSessionId,
647
1197
  transcriptAuthority: readResult.transcriptAuthority,
648
1198
  coverage: readResult.coverage,
1199
+ messageSource: readResult.messageSource,
1200
+ transcriptProvenance: readResult.transcriptProvenance,
649
1201
  activeModal: readResult.activeModal,
650
1202
  messagesTail: Array.isArray(readResult.messages) ? readResult.messages.slice(-20) : [],
651
1203
  debugReadChat: readResult.debugReadChat,
652
1204
  }
653
- : { success: false, error: readResult.error };
1205
+ : {
1206
+ success: false,
1207
+ error: readResult.error,
1208
+ code: readResult.code,
1209
+ messageSource: readResult.messageSource,
1210
+ transcriptProvenance: readResult.transcriptProvenance,
1211
+ debugReadChat: readResult.debugReadChat,
1212
+ };
654
1213
  } catch (error: any) {
655
1214
  readChat = { success: false, error: error?.message || String(error) };
656
1215
  }
@@ -817,16 +1376,56 @@ export async function handleChatHistory(h: CommandHelpers, args: any): Promise<C
817
1376
  : typeof (h.currentSession as any)?.workspace === 'string'
818
1377
  ? (h.currentSession as any).workspace
819
1378
  : 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
- });
1379
+ const exactNativeHistoryScope = Boolean(
1380
+ (typeof args?.targetSessionId === 'string' && args.targetSessionId.trim())
1381
+ || (typeof args?.historySessionId === 'string' && args.historySessionId.trim())
1382
+ || (typeof args?.providerSessionId === 'string' && args.providerSessionId.trim())
1383
+ );
1384
+ const result = supportsCliNativeTranscript(agentStr, provider) && isNativeSourceCanonicalHistory(provider?.canonicalHistory)
1385
+ ? readCliProviderNativeHistory(agentStr, {
1386
+ canonicalHistory: provider?.canonicalHistory,
1387
+ historySessionId,
1388
+ workspace,
1389
+ offset: offset || 0,
1390
+ limit: limit || 30,
1391
+ excludeRecentCount,
1392
+ historyBehavior: provider?.historyBehavior,
1393
+ scripts: provider?.scripts as any,
1394
+ })
1395
+ : readProviderChatHistory(agentStr, {
1396
+ canonicalHistory: provider?.canonicalHistory,
1397
+ historySessionId,
1398
+ workspace,
1399
+ offset: offset || 0,
1400
+ limit: limit || 30,
1401
+ excludeRecentCount,
1402
+ historyBehavior: provider?.historyBehavior,
1403
+ scripts: provider?.scripts as any,
1404
+ });
1405
+ if (supportsCliNativeTranscript(agentStr, provider) && isNativeSourceCanonicalHistory(provider?.canonicalHistory)) {
1406
+ const lookup = (result as any).lookup === 'workspace' ? 'workspace' : 'session';
1407
+ const messages = Array.isArray((result as any).messages)
1408
+ ? normalizeNativeHistoryMessages(agentStr, (result as any).messages as ChatMessage[], (result as any)?.providerSessionId)
1409
+ : [];
1410
+ const historyProviderSessionId = typeof (result as any)?.providerSessionId === 'string'
1411
+ ? (result as any).providerSessionId
1412
+ : readHistorySessionIdFromMessages(messages) || historySessionId;
1413
+ const safeMapping = hasSafeNativeHistoryMapping({
1414
+ historySessionId: lookup === 'workspace' ? undefined : historySessionId,
1415
+ providerSessionId: lookup === 'workspace' ? undefined : historyProviderSessionId,
1416
+ workspace,
1417
+ nativeMessages: messages,
1418
+ });
1419
+ if ((result as any).source === 'provider-native' && messages.length > 0 && !safeMapping) {
1420
+ return {
1421
+ success: true,
1422
+ messages: [],
1423
+ hasMore: false,
1424
+ source: 'native-unavailable',
1425
+ agent: agentStr,
1426
+ };
1427
+ }
1428
+ }
830
1429
  return { success: true, ...result, agent: agentStr };
831
1430
  } catch (e: any) {
832
1431
  return { success: false, error: e.message };
@@ -874,7 +1473,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
874
1473
  ? parsedRecord.coverage
875
1474
  : undefined;
876
1475
  const activeModal = parsedRecord.activeModal ?? parsedRecord.modal ?? null;
877
- const returnedStatus = normalizeCliReadChatStatus(parsedRecord.status, activeModal, adapter, adapterStatus);
1476
+ const returnedStatus = normalizeCliReadChatStatus(parsedRecord.status, activeModal, adapter, adapterStatus, parsedRecord.messages);
878
1477
  const runtimeMessageMerger = getTargetInstance(h, args) as RuntimeChatMessageMerger | null;
879
1478
  const parsedMessages = finalizeStreamingMessagesWhenIdle(parsedRecord.messages as ChatMessage[], returnedStatus);
880
1479
  const returnedMessages = runtimeMessageMerger?.category === 'cli'
@@ -882,36 +1481,368 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
882
1481
  && typeof runtimeMessageMerger.mergeRuntimeChatMessages === 'function'
883
1482
  ? runtimeMessageMerger.mergeRuntimeChatMessages(parsedMessages)
884
1483
  : parsedMessages;
1484
+ const providerType = provider?.type || adapter.cliType;
1485
+ let selectedMessages = returnedMessages;
1486
+ let selectedTitle = title;
1487
+ let selectedProviderSessionId = providerSessionId;
1488
+ let selectedTranscriptAuthority = transcriptAuthority;
1489
+ let selectedCoverage = coverage;
1490
+ let selectedStatus = returnedStatus;
1491
+ const sessionWorkspace = typeof (h.currentSession as any)?.workspace === 'string'
1492
+ ? (h.currentSession as any).workspace
1493
+ : typeof adapter.workingDir === 'string'
1494
+ ? adapter.workingDir
1495
+ : undefined;
1496
+ const intendedWorkspace = typeof args?.workspace === 'string' ? args.workspace : undefined;
1497
+ let messageSource = buildCliMessageSourceProvenance({
1498
+ selected: 'pty-parser',
1499
+ provider: adapter.cliType,
1500
+ fallbackReason: supportsCliNativeTranscript(providerType, provider) ? 'native_history_not_checked' : 'provider_native_transcript_not_supported',
1501
+ sessionWorkspace,
1502
+ intendedWorkspace,
1503
+ ptyMessages: returnedMessages,
1504
+ returnedMessages,
1505
+ ptyStatusApprovalOnly: false,
1506
+ });
1507
+
1508
+ if (supportsCliNativeTranscript(providerType, provider) && isNativeSourceCanonicalHistory(provider?.canonicalHistory)) {
1509
+ const agentStr = provider?.type || args?.agentType || getCurrentProviderType(h, adapter.cliType);
1510
+ const workspace = sessionWorkspace;
1511
+ const nativeHistoryLimit = Math.max(
1512
+ normalizeReadChatTailLimit(args) || 0,
1513
+ returnedMessages.length,
1514
+ 200,
1515
+ );
1516
+ const nativeHistorySessionId = resolveCliNativeHistorySessionId(args, historySessionId, providerSessionId);
1517
+ const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
1518
+ const exactNativeHistoryScope = Boolean(
1519
+ (typeof args?.historySessionId === 'string' && args.historySessionId.trim())
1520
+ || (typeof args?.providerSessionId === 'string' && args.providerSessionId.trim())
1521
+ || providerSessionId
1522
+ || (nativeHistorySessionId && nativeHistorySessionId !== targetSessionId)
1523
+ || ((h.currentSession as any)?.sessionId === args?.targetSessionId && typeof (h.currentSession as any)?.providerSessionId === 'string' && (h.currentSession as any).providerSessionId.trim())
1524
+ );
1525
+ let nativeHistory: (ReturnType<typeof readProviderChatHistory> & { lookup?: 'session' | 'workspace' }) | null = null;
1526
+ try {
1527
+ nativeHistory = readCliProviderNativeHistory(agentStr, {
1528
+ canonicalHistory: provider?.canonicalHistory,
1529
+ historySessionId: nativeHistorySessionId,
1530
+ workspace,
1531
+ offset: 0,
1532
+ limit: nativeHistoryLimit,
1533
+ excludeRecentCount: 0,
1534
+ historyBehavior: provider?.historyBehavior,
1535
+ scripts: provider?.scripts as any,
1536
+ });
1537
+ } catch (error: any) {
1538
+ const fallbackReason = `native_history_error:${error?.message || String(error)}`;
1539
+ messageSource = buildCliMessageSourceProvenance({
1540
+ selected: 'pty-parser',
1541
+ provider: adapter.cliType,
1542
+ fallbackReason,
1543
+ sessionWorkspace,
1544
+ intendedWorkspace,
1545
+ ptyMessages: returnedMessages,
1546
+ returnedMessages,
1547
+ ptyStatusApprovalOnly: false,
1548
+ });
1549
+ nativeHistory = null;
1550
+ }
1551
+
1552
+ if (nativeHistory) {
1553
+ const nativeMessages = Array.isArray((nativeHistory as any).messages)
1554
+ ? normalizeNativeHistoryMessages(agentStr, (nativeHistory as any).messages as ChatMessage[], (nativeHistory as any)?.providerSessionId)
1555
+ : [];
1556
+ const historyProviderSessionId = typeof (nativeHistory as any)?.providerSessionId === 'string'
1557
+ ? (nativeHistory as any).providerSessionId
1558
+ : readHistorySessionIdFromMessages(nativeMessages) || nativeHistorySessionId || historySessionId;
1559
+ const nativeHistoryCoverage = typeof (nativeHistory as any)?.nativeHistoryCoverage === 'string'
1560
+ ? (nativeHistory as any).nativeHistoryCoverage
1561
+ : undefined;
1562
+ const partialReason = typeof (nativeHistory as any)?.partialReason === 'string'
1563
+ ? (nativeHistory as any).partialReason
1564
+ : undefined;
1565
+ const unavailableReason = typeof (nativeHistory as any)?.unavailableReason === 'string'
1566
+ ? (nativeHistory as any).unavailableReason
1567
+ : undefined;
1568
+ const lookup = (nativeHistory as any).lookup === 'workspace' ? 'workspace' : 'session';
1569
+ const transcriptWorkspace = typeof (nativeHistory as any)?.workspace === 'string'
1570
+ ? (nativeHistory as any).workspace
1571
+ : nativeMessages.map((message: any) => typeof message?.workspace === 'string' ? message.workspace.trim() : '').find(Boolean);
1572
+ const nativeHistorySessionForMapping = adapter.cliType === 'antigravity-cli'
1573
+ && historyProviderSessionId
1574
+ && nativeHistorySessionId
1575
+ && historyProviderSessionId !== nativeHistorySessionId
1576
+ ? undefined
1577
+ : nativeHistorySessionId;
1578
+ const safeMapping = hasSafeNativeHistoryMapping({
1579
+ historySessionId: lookup === 'workspace' ? undefined : nativeHistorySessionForMapping,
1580
+ providerSessionId: lookup === 'workspace' ? undefined : historyProviderSessionId || providerSessionId,
1581
+ workspace,
1582
+ nativeMessages,
1583
+ ptyMessages: returnedMessages,
1584
+ requireWorkspaceContentOverlap: lookup === 'workspace' && !exactNativeHistoryScope,
1585
+ });
1586
+ const freshEnough = isNativeHistoryFreshEnough({
1587
+ sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
1588
+ nativeMessages,
1589
+ ptyMessages: returnedMessages,
1590
+ });
1591
+ const nativeUsableForChatMessages = (nativeHistory as any).source === 'provider-native'
1592
+ && nativeMessages.length > 0
1593
+ && nativeHistoryCoverage !== 'partial'
1594
+ && nativeHistoryCoverage !== 'unavailable'
1595
+ && safeMapping;
1596
+ const allowStaleNativeChatMessages = adapter.cliType === 'antigravity-cli' && nativeUsableForChatMessages;
1597
+ if (nativeUsableForChatMessages && (freshEnough || allowStaleNativeChatMessages)) {
1598
+ selectedMessages = finalizeStreamingMessagesWhenIdle(nativeMessages, returnedStatus);
1599
+ selectedProviderSessionId = historyProviderSessionId || providerSessionId;
1600
+ selectedTranscriptAuthority = 'provider';
1601
+ selectedCoverage = (nativeHistory as any).hasMore ? 'tail' : 'full';
1602
+ messageSource = buildCliMessageSourceProvenance({
1603
+ selected: 'native-history',
1604
+ provider: adapter.cliType,
1605
+ nativeHandle: selectedProviderSessionId || nativeHistorySessionId || historySessionId,
1606
+ sessionWorkspace,
1607
+ intendedWorkspace,
1608
+ transcriptWorkspace,
1609
+ nativeSource: (nativeHistory as any).source,
1610
+ sourcePath: (nativeHistory as any).sourcePath,
1611
+ sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
1612
+ nativeHistoryCoverage,
1613
+ partialReason,
1614
+ unavailableReason,
1615
+ nativeMessages,
1616
+ ptyMessages: returnedMessages,
1617
+ returnedMessages: selectedMessages,
1618
+ safeMapping,
1619
+ freshEnough,
1620
+ ptyStatusApprovalOnly: true,
1621
+ });
1622
+ } else {
1623
+ const liveCurrentRuntimePtySafe = isCurrentRuntimePtySafelyAttributed({
1624
+ adapter,
1625
+ helpers: h,
1626
+ readChatArgs: args,
1627
+ sessionWorkspace,
1628
+ intendedWorkspace,
1629
+ ptyMessages: returnedMessages,
1630
+ });
1631
+ const mayProbeLiveCodexWorkspaceNative = adapter.cliType === 'codex-cli'
1632
+ && liveCurrentRuntimePtySafe
1633
+ && !(typeof args?.providerSessionId === 'string' && args.providerSessionId.trim())
1634
+ && !(providerSessionId && providerSessionId.trim())
1635
+ && (!historyProviderSessionId || historyProviderSessionId === nativeHistorySessionId || historyProviderSessionId === historySessionId);
1636
+ const liveWorkspaceNativeHistory = mayProbeLiveCodexWorkspaceNative
1637
+ ? readLiveCodexWorkspaceNativeHistory(agentStr, {
1638
+ canonicalHistory: provider?.canonicalHistory,
1639
+ workspace,
1640
+ offset: 0,
1641
+ limit: nativeHistoryLimit,
1642
+ excludeRecentCount: 0,
1643
+ historyBehavior: provider?.historyBehavior,
1644
+ scripts: provider?.scripts as any,
1645
+ })
1646
+ : null;
1647
+ const liveWorkspaceNativeMessages = Array.isArray((liveWorkspaceNativeHistory as any)?.messages)
1648
+ ? normalizeNativeHistoryMessages(agentStr, (liveWorkspaceNativeHistory as any).messages as ChatMessage[], (liveWorkspaceNativeHistory as any)?.providerSessionId)
1649
+ : [];
1650
+ const liveWorkspaceNativeProviderSessionId = typeof (liveWorkspaceNativeHistory as any)?.providerSessionId === 'string'
1651
+ ? (liveWorkspaceNativeHistory as any).providerSessionId
1652
+ : readHistorySessionIdFromMessages(liveWorkspaceNativeMessages);
1653
+ const liveWorkspaceTranscriptWorkspace = typeof (liveWorkspaceNativeHistory as any)?.workspace === 'string'
1654
+ ? (liveWorkspaceNativeHistory as any).workspace
1655
+ : liveWorkspaceNativeMessages.map((message: any) => typeof message?.workspace === 'string' ? message.workspace.trim() : '').find(Boolean);
1656
+ const liveWorkspaceNativeSafeMapping = liveWorkspaceNativeMessages.length > 0
1657
+ && hasSafeNativeHistoryMapping({
1658
+ workspace,
1659
+ nativeMessages: liveWorkspaceNativeMessages,
1660
+ ptyMessages: returnedMessages,
1661
+ requireWorkspaceContentOverlap: true,
1662
+ });
1663
+ const liveWorkspaceNativeFreshEnough = liveWorkspaceNativeMessages.length > 0
1664
+ && isNativeHistoryFreshEnough({
1665
+ sourceMtimeMs: (liveWorkspaceNativeHistory as any)?.sourceMtimeMs,
1666
+ nativeMessages: liveWorkspaceNativeMessages,
1667
+ ptyMessages: returnedMessages,
1668
+ });
1669
+ const liveWorkspaceNativeUsable = (liveWorkspaceNativeHistory as any)?.source === 'provider-native'
1670
+ && liveWorkspaceNativeMessages.length > 0
1671
+ && (liveWorkspaceNativeHistory as any)?.nativeHistoryCoverage !== 'partial'
1672
+ && (liveWorkspaceNativeHistory as any)?.nativeHistoryCoverage !== 'unavailable'
1673
+ && liveWorkspaceNativeSafeMapping
1674
+ && liveWorkspaceNativeFreshEnough;
1675
+ if (liveWorkspaceNativeUsable) {
1676
+ selectedMessages = finalizeStreamingMessagesWhenIdle(liveWorkspaceNativeMessages, returnedStatus);
1677
+ selectedProviderSessionId = liveWorkspaceNativeProviderSessionId || providerSessionId;
1678
+ selectedTranscriptAuthority = 'provider';
1679
+ selectedCoverage = (liveWorkspaceNativeHistory as any).hasMore ? 'tail' : 'full';
1680
+ messageSource = buildCliMessageSourceProvenance({
1681
+ selected: 'native-history',
1682
+ provider: adapter.cliType,
1683
+ nativeHandle: selectedProviderSessionId || nativeHistorySessionId || historySessionId,
1684
+ sessionWorkspace,
1685
+ intendedWorkspace,
1686
+ transcriptWorkspace: liveWorkspaceTranscriptWorkspace,
1687
+ nativeSource: (liveWorkspaceNativeHistory as any).source,
1688
+ sourcePath: (liveWorkspaceNativeHistory as any).sourcePath,
1689
+ sourceMtimeMs: (liveWorkspaceNativeHistory as any).sourceMtimeMs,
1690
+ nativeHistoryCoverage: (liveWorkspaceNativeHistory as any).nativeHistoryCoverage,
1691
+ partialReason: (liveWorkspaceNativeHistory as any).partialReason,
1692
+ unavailableReason: (liveWorkspaceNativeHistory as any).unavailableReason,
1693
+ nativeMessages: liveWorkspaceNativeMessages,
1694
+ ptyMessages: returnedMessages,
1695
+ returnedMessages: selectedMessages,
1696
+ safeMapping: true,
1697
+ freshEnough: true,
1698
+ ptyStatusApprovalOnly: true,
1699
+ });
1700
+ (messageSource as any).selectedDaemonSource = 'live-workspace-native-history';
1701
+ (messageSource as any).runtimeMappingSafe = true;
1702
+ } else {
1703
+ const fallbackReason = buildNativeHistoryFallbackReason({
1704
+ providerType,
1705
+ provider,
1706
+ nativeSource: (nativeHistory as any).source,
1707
+ nativeHistoryCoverage,
1708
+ unavailableReason,
1709
+ nativeMessageCount: nativeMessages.length,
1710
+ safeMapping,
1711
+ freshEnough,
1712
+ });
1713
+ const unsafeNativeFallback = adapter.cliType === 'codex-cli'
1714
+ && isUnsafeNativeTranscriptFallback(fallbackReason);
1715
+ const safeCurrentRuntimePtyMessages = unsafeNativeFallback
1716
+ && isCurrentRuntimePtySafelyAttributed({
1717
+ adapter,
1718
+ helpers: h,
1719
+ readChatArgs: args,
1720
+ sessionWorkspace,
1721
+ intendedWorkspace,
1722
+ ptyMessages: returnedMessages,
1723
+ });
1724
+ const safeRuntimeAckMessages = unsafeNativeFallback
1725
+ && !safeCurrentRuntimePtyMessages
1726
+ ? selectRuntimeInputAckMessages(returnedMessages)
1727
+ : [];
1728
+ const exactRuntimeMirrorMessages = unsafeNativeFallback
1729
+ && !safeCurrentRuntimePtyMessages
1730
+ && safeRuntimeAckMessages.length === 0
1731
+ ? readExactRuntimeMirrorMessages({
1732
+ providerType,
1733
+ targetSessionId: typeof args?.targetSessionId === 'string' ? args.targetSessionId : undefined,
1734
+ currentSessionId: typeof (h.currentSession as any)?.sessionId === 'string' ? (h.currentSession as any).sessionId : undefined,
1735
+ tailLimit: nativeHistoryLimit,
1736
+ historyBehavior: provider?.historyBehavior,
1737
+ })
1738
+ : [];
1739
+ const safeDaemonMessages = safeRuntimeAckMessages.length > 0
1740
+ ? safeRuntimeAckMessages
1741
+ : exactRuntimeMirrorMessages;
1742
+ if (unsafeNativeFallback) {
1743
+ if (safeCurrentRuntimePtyMessages) {
1744
+ selectedMessages = returnedMessages;
1745
+ selectedTranscriptAuthority = 'daemon';
1746
+ selectedCoverage = coverage || 'current-turn';
1747
+ selectedStatus = returnedStatus;
1748
+ } else {
1749
+ selectedMessages = safeDaemonMessages;
1750
+ selectedTranscriptAuthority = safeDaemonMessages.length > 0 ? 'daemon' : undefined;
1751
+ selectedCoverage = safeDaemonMessages.length > 0 ? 'tail' : undefined;
1752
+ selectedStatus = coerceUnsafeNativeFallbackStatus(returnedStatus, activeModal);
1753
+ }
1754
+ }
1755
+ messageSource = buildCliMessageSourceProvenance({
1756
+ selected: 'pty-parser',
1757
+ provider: adapter.cliType,
1758
+ nativeHandle: historyProviderSessionId || nativeHistorySessionId || historySessionId,
1759
+ sessionWorkspace,
1760
+ intendedWorkspace,
1761
+ transcriptWorkspace,
1762
+ fallbackReason,
1763
+ nativeSource: (nativeHistory as any).source,
1764
+ sourcePath: (nativeHistory as any).sourcePath,
1765
+ sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
1766
+ nativeHistoryCoverage,
1767
+ partialReason,
1768
+ unavailableReason,
1769
+ nativeMessages,
1770
+ ptyMessages: returnedMessages,
1771
+ returnedMessages: unsafeNativeFallback && !safeCurrentRuntimePtyMessages ? safeDaemonMessages : returnedMessages,
1772
+ safeMapping,
1773
+ freshEnough,
1774
+ ptyStatusApprovalOnly: unsafeNativeFallback && !safeCurrentRuntimePtyMessages,
1775
+ });
1776
+ if (safeCurrentRuntimePtyMessages) {
1777
+ (messageSource as any).selectedDaemonSource = 'current-runtime-pty';
1778
+ (messageSource as any).transcriptAuthority = 'daemon';
1779
+ (messageSource as any).runtimeMappingSafe = true;
1780
+ }
1781
+ if (unsafeNativeFallback && exactRuntimeMirrorMessages.length > 0) {
1782
+ (messageSource as any).selectedDaemonSource = 'exact-runtime-mirror';
1783
+ (messageSource as any).transcriptAuthority = 'daemon';
1784
+ }
1785
+ }
1786
+ }
1787
+ }
1788
+ }
885
1789
  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
1790
  return buildReadChatCommandResult({
887
- messages: returnedMessages,
888
- status: returnedStatus,
1791
+ messages: selectedMessages,
1792
+ status: selectedStatus,
889
1793
  activeModal,
1794
+ messageSource,
1795
+ transcriptProvenance: messageSource,
890
1796
  debugReadChat: {
891
1797
  provider: adapter.cliType,
892
1798
  targetSessionId: String(args?.targetSessionId || ''),
893
1799
  adapterStatus: String(adapterStatus.status || ''),
894
1800
  parsedStatus: String(parsedRecord.status || ''),
895
- returnedStatus: String(returnedStatus || ''),
896
- shouldPreferAdapterMessages: false,
1801
+ returnedStatus: String(selectedStatus || ''),
1802
+ selectedMessageSource: (messageSource as any).selected,
1803
+ messageSource,
1804
+ shouldPreferAdapterMessages: supportsCliNativeTranscript(providerType, provider)
1805
+ && isNativeSourceCanonicalHistory(provider?.canonicalHistory)
1806
+ && (messageSource as any).selected !== 'native-history'
1807
+ && typeof (messageSource as any).fallbackReason === 'string'
1808
+ && (messageSource as any).fallbackReason.startsWith('native_history_')
1809
+ && (messageSource as any).fallbackReason !== 'native_history_not_checked'
1810
+ && !isUnsafeNativeTranscriptFallback((messageSource as any).fallbackReason)
1811
+ && !(selectedTranscriptAuthority === 'provider' && selectedCoverage === 'full'),
897
1812
  parsedMsgCount: parsedRecord.messages.length,
898
- returnedMsgCount: returnedMessages.length,
1813
+ returnedMsgCount: selectedMessages.length,
899
1814
  },
900
- ...(title ? { title } : {}),
901
- ...(providerSessionId ? { providerSessionId } : {}),
902
- ...(transcriptAuthority ? { transcriptAuthority } : {}),
903
- ...(coverage ? { coverage } : {}),
1815
+ ...(selectedTitle ? { title: selectedTitle } : {}),
1816
+ ...(selectedProviderSessionId ? { providerSessionId: selectedProviderSessionId } : {}),
1817
+ ...(selectedTranscriptAuthority ? { transcriptAuthority: selectedTranscriptAuthority } : {}),
1818
+ ...(selectedCoverage ? { coverage: selectedCoverage } : {}),
904
1819
  }, args);
905
1820
  }
906
1821
  const historyLimit = normalizeReadChatTailLimit(args);
907
1822
  try {
908
1823
  const agentStr = provider?.type || args?.agentType || getCurrentProviderType(h);
909
- const workspace = typeof args?.workspace === 'string'
910
- ? args.workspace
911
- : typeof (h.currentSession as any)?.workspace === 'string'
912
- ? (h.currentSession as any).workspace
913
- : undefined;
914
- const history = readProviderChatHistory(agentStr, {
1824
+ const workspace = typeof (h.currentSession as any)?.workspace === 'string'
1825
+ ? (h.currentSession as any).workspace
1826
+ : undefined;
1827
+ const intendedWorkspace = typeof args?.workspace === 'string' ? args.workspace : undefined;
1828
+ const exactNativeHistoryScope = Boolean(
1829
+ (typeof args?.targetSessionId === 'string' && args.targetSessionId.trim())
1830
+ || (typeof args?.historySessionId === 'string' && args.historySessionId.trim())
1831
+ || (typeof args?.providerSessionId === 'string' && args.providerSessionId.trim())
1832
+ || ((h.currentSession as any)?.sessionId === args?.targetSessionId && typeof (h.currentSession as any)?.providerSessionId === 'string' && (h.currentSession as any).providerSessionId.trim())
1833
+ );
1834
+ const history = supportsCliNativeTranscript(agentStr, provider) && isNativeSourceCanonicalHistory(provider?.canonicalHistory)
1835
+ ? readCliProviderNativeHistory(agentStr, {
1836
+ canonicalHistory: provider?.canonicalHistory,
1837
+ historySessionId,
1838
+ workspace,
1839
+ offset: 0,
1840
+ limit: historyLimit,
1841
+ excludeRecentCount: 0,
1842
+ historyBehavior: provider?.historyBehavior,
1843
+ scripts: provider?.scripts as any,
1844
+ })
1845
+ : readProviderChatHistory(agentStr, {
915
1846
  canonicalHistory: provider?.canonicalHistory,
916
1847
  historySessionId,
917
1848
  workspace,
@@ -921,12 +1852,87 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
921
1852
  historyBehavior: provider?.historyBehavior,
922
1853
  scripts: provider?.scripts as any,
923
1854
  });
1855
+ const lookup = (history as any).lookup === 'workspace' ? 'workspace' : 'session';
1856
+ const historyMessages = Array.isArray((history as any)?.messages)
1857
+ ? normalizeNativeHistoryMessages(agentStr, (history as any).messages as ChatMessage[], (history as any)?.providerSessionId)
1858
+ : [];
924
1859
  const historyProviderSessionId = typeof (history as any)?.providerSessionId === 'string'
925
1860
  ? (history as any).providerSessionId
926
- : historySessionId;
1861
+ : readHistorySessionIdFromMessages(historyMessages) || historySessionId;
1862
+ const nativeHistoryCoverage = typeof (history as any)?.nativeHistoryCoverage === 'string'
1863
+ ? (history as any).nativeHistoryCoverage
1864
+ : undefined;
1865
+ const partialReason = typeof (history as any)?.partialReason === 'string'
1866
+ ? (history as any).partialReason
1867
+ : undefined;
1868
+ const unavailableReason = typeof (history as any)?.unavailableReason === 'string'
1869
+ ? (history as any).unavailableReason
1870
+ : undefined;
1871
+ const transcriptWorkspace = typeof (history as any)?.workspace === 'string'
1872
+ ? (history as any).workspace
1873
+ : historyMessages.map((message: any) => typeof message?.workspace === 'string' ? message.workspace.trim() : '').find(Boolean);
1874
+ const safeMapping = supportsCliNativeTranscript(agentStr, provider)
1875
+ ? hasSafeNativeHistoryMapping({
1876
+ historySessionId: lookup === 'workspace' ? undefined : historySessionId,
1877
+ providerSessionId: lookup === 'workspace' ? undefined : historyProviderSessionId,
1878
+ workspace,
1879
+ nativeMessages: historyMessages,
1880
+ })
1881
+ : false;
1882
+ const nativeSelected = supportsCliNativeTranscript(agentStr, provider)
1883
+ && (history as any).source === 'provider-native'
1884
+ && historyMessages.length > 0
1885
+ && nativeHistoryCoverage !== 'partial'
1886
+ && nativeHistoryCoverage !== 'unavailable'
1887
+ && safeMapping;
1888
+ const messageSource = buildCliMessageSourceProvenance({
1889
+ selected: nativeSelected ? 'native-history' : 'pty-parser',
1890
+ provider: agentStr,
1891
+ nativeHandle: historyProviderSessionId || historySessionId,
1892
+ sessionWorkspace: workspace,
1893
+ intendedWorkspace,
1894
+ transcriptWorkspace,
1895
+ fallbackReason: nativeSelected
1896
+ ? undefined
1897
+ : buildNativeHistoryFallbackReason({
1898
+ providerType: agentStr,
1899
+ provider,
1900
+ nativeSource: (history as any).source,
1901
+ nativeHistoryCoverage,
1902
+ unavailableReason,
1903
+ nativeMessageCount: historyMessages.length,
1904
+ safeMapping,
1905
+ freshEnough: true,
1906
+ }),
1907
+ nativeSource: (history as any).source,
1908
+ sourcePath: (history as any).sourcePath,
1909
+ sourceMtimeMs: (history as any).sourceMtimeMs,
1910
+ nativeHistoryCoverage,
1911
+ partialReason,
1912
+ unavailableReason,
1913
+ nativeMessages: historyMessages,
1914
+ returnedMessages: historyMessages,
1915
+ safeMapping,
1916
+ freshEnough: true,
1917
+ ptyStatusApprovalOnly: false,
1918
+ });
1919
+ const requiresNativeSource = supportsCliNativeTranscript(agentStr, provider)
1920
+ && isNativeSourceCanonicalHistory(provider?.canonicalHistory);
1921
+ if (requiresNativeSource && !nativeSelected) {
1922
+ return {
1923
+ success: false,
1924
+ code: 'native_history_not_safely_available',
1925
+ error: 'Provider-native history was not safely available for the requested CLI session.',
1926
+ providerSessionId: historyProviderSessionId,
1927
+ messageSource,
1928
+ transcriptProvenance: messageSource,
1929
+ };
1930
+ }
927
1931
  return buildReadChatCommandResult({
928
- messages: Array.isArray((history as any)?.messages) ? (history as any).messages : [],
1932
+ messages: historyMessages,
929
1933
  status: 'idle',
1934
+ messageSource,
1935
+ transcriptProvenance: messageSource,
930
1936
  ...(typeof (history as any)?.title === 'string' ? { title: (history as any).title } : {}),
931
1937
  ...(historyProviderSessionId ? { providerSessionId: historyProviderSessionId } : {}),
932
1938
  ...(((provider?.historyBehavior as any)?.transcriptAuthority === 'provider' || (provider?.historyBehavior as any)?.transcriptAuthority === 'daemon')
@@ -1184,8 +2190,24 @@ export async function handleSendChat(h: CommandHelpers, args: any): Promise<Comm
1184
2190
  assertTextOnlyInput(provider, input);
1185
2191
  if (!text) return { success: false, error: 'text required for PTY send' };
1186
2192
  await waitOnceForFreshHermesCliStart(adapter, _log);
1187
- await adapter.sendMessage(text);
1188
- return _logSendSuccess(`${transport}-adapter`, adapter.cliType);
2193
+ const forceSend = args?.force === true || args?.forceSend === true;
2194
+ if (forceSend && typeof adapter.forceSendMessage === 'function') {
2195
+ await adapter.forceSendMessage(text);
2196
+ } else if (forceSend) {
2197
+ await adapter.sendMessage(text, { force: true });
2198
+ } else {
2199
+ await adapter.sendMessage(text);
2200
+ }
2201
+ const target = getTargetInstance(h, args) as RuntimeChatMessageMerger | null;
2202
+ if (target?.category === 'cli'
2203
+ && target.type === adapter.cliType
2204
+ && typeof target.recordAcknowledgedUserInput === 'function') {
2205
+ target.recordAcknowledgedUserInput(input);
2206
+ }
2207
+ return {
2208
+ ..._logSendSuccess(`${transport}-adapter`, adapter.cliType),
2209
+ ...(forceSend ? { forceSent: true } : {}),
2210
+ };
1189
2211
  } catch (e: any) {
1190
2212
  return { success: false, error: `${transport} send failed: ${e.message}` };
1191
2213
  }
@@ -1742,6 +2764,9 @@ export async function handleResolveAction(h: CommandHelpers, args: any): Promise
1742
2764
  if (buttonIndex < 0 && (action === 'always' || /always/i.test(button))) {
1743
2765
  buttonIndex = buttons.findIndex(b => /always/i.test(b));
1744
2766
  }
2767
+ if (buttonIndex < 0 && (action === 'approve' || action === 'accept')) {
2768
+ buttonIndex = pickApprovalButton(buttons, provider).index;
2769
+ }
1745
2770
  if (buttonIndex < 0) {
1746
2771
  return { success: false, error: 'Approval action did not match any visible button' };
1747
2772
  }