@adhdev/daemon-core 0.9.82-rc.9 → 0.9.82-rc.91

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 (67) hide show
  1. package/dist/cli-adapters/provider-cli-adapter.d.ts +2 -0
  2. package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
  3. package/dist/cli-adapters/provider-cli-shared.d.ts +2 -0
  4. package/dist/commands/router.d.ts +22 -0
  5. package/dist/config/mesh-config.d.ts +66 -1
  6. package/dist/index.d.ts +13 -6
  7. package/dist/index.js +5417 -1207
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +5381 -1193
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/installer.d.ts +1 -4
  12. package/dist/launch.d.ts +1 -1
  13. package/dist/logging/async-batch-writer.d.ts +10 -0
  14. package/dist/mesh/beads-db.d.ts +18 -0
  15. package/dist/mesh/mesh-active-work.d.ts +60 -0
  16. package/dist/mesh/mesh-events.d.ts +29 -5
  17. package/dist/mesh/mesh-fast-forward.d.ts +39 -0
  18. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  19. package/dist/mesh/mesh-ledger.d.ts +38 -1
  20. package/dist/mesh/mesh-work-queue.d.ts +27 -5
  21. package/dist/mesh/refine-config.d.ts +176 -0
  22. package/dist/providers/chat-message-normalization.d.ts +1 -0
  23. package/dist/providers/cli-provider-instance.d.ts +2 -1
  24. package/dist/repo-mesh-types.d.ts +46 -0
  25. package/dist/status/reporter.d.ts +2 -0
  26. package/package.json +3 -1
  27. package/src/boot/daemon-lifecycle.ts +1 -0
  28. package/src/cli-adapters/provider-cli-adapter.ts +91 -3
  29. package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
  30. package/src/cli-adapters/provider-cli-parse.ts +4 -0
  31. package/src/cli-adapters/provider-cli-runtime.ts +3 -1
  32. package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
  33. package/src/cli-adapters/provider-cli-shared.ts +20 -10
  34. package/src/commands/chat-commands.ts +472 -15
  35. package/src/commands/cli-manager.ts +126 -0
  36. package/src/commands/handler.ts +8 -1
  37. package/src/commands/mesh-coordinator.ts +13 -143
  38. package/src/commands/router.ts +2687 -435
  39. package/src/config/chat-history.ts +9 -7
  40. package/src/config/mesh-config.ts +245 -1
  41. package/src/daemon/dev-cli-debug.ts +10 -1
  42. package/src/detection/ide-detector.ts +26 -16
  43. package/src/index.ts +31 -5
  44. package/src/installer.d.ts +1 -1
  45. package/src/installer.ts +8 -6
  46. package/src/launch.d.ts +1 -1
  47. package/src/launch.ts +37 -28
  48. package/src/logging/async-batch-writer.ts +55 -0
  49. package/src/logging/logger.ts +2 -1
  50. package/src/mesh/beads-db.ts +176 -0
  51. package/src/mesh/coordinator-prompt.ts +30 -7
  52. package/src/mesh/mesh-active-work.ts +255 -0
  53. package/src/mesh/mesh-events.ts +400 -47
  54. package/src/mesh/mesh-fast-forward.ts +430 -0
  55. package/src/mesh/mesh-host-ownership.ts +73 -0
  56. package/src/mesh/mesh-ledger.ts +138 -1
  57. package/src/mesh/mesh-work-queue.ts +199 -137
  58. package/src/mesh/refine-config.ts +356 -0
  59. package/src/providers/chat-message-normalization.ts +7 -12
  60. package/src/providers/cli-provider-instance.ts +93 -14
  61. package/src/providers/ide-provider-instance.ts +17 -3
  62. package/src/providers/provider-loader.ts +10 -4
  63. package/src/providers/read-chat-contract.ts +1 -1
  64. package/src/providers/version-archive.ts +38 -20
  65. package/src/repo-mesh-types.ts +51 -0
  66. package/src/status/reporter.ts +15 -0
  67. package/src/system/host-memory.ts +29 -12
@@ -13,7 +13,7 @@ import { flattenContent, normalizeInputEnvelope, type InputEnvelope, type Provid
13
13
  import { assertProviderSupportsDeclaredInput, assertTextOnlyInput } from '../providers/provider-input-support.js';
14
14
  import { validateReadChatResultPayload } from '../providers/read-chat-contract.js';
15
15
  import type { ProviderInstance } from '../providers/provider-instance.js';
16
- import { readProviderChatHistory } from '../config/chat-history.js';
16
+ import { isNativeSourceCanonicalHistory, readProviderChatHistory } from '../config/chat-history.js';
17
17
  import { LOG, getRecentLogs } from '../logging/logger.js';
18
18
  import { getRecentDebugTrace, recordDebugTrace } from '../logging/debug-trace.js';
19
19
  import { buildChatMessageSignature, hashSignatureParts } from '../chat/chat-signatures.js';
@@ -24,6 +24,8 @@ import { filterUserFacingChatMessages, normalizeChatMessages } from '../provider
24
24
  const RECENT_SEND_WINDOW_MS = 1200;
25
25
  export const READ_CHAT_PROVIDER_EVAL_TIMEOUT_MS = 25_000;
26
26
  const HERMES_CLI_STARTING_SEND_SETTLE_MS = 2_000;
27
+ const CLI_NATIVE_HISTORY_FRESH_MS = 5 * 60_000;
28
+ const CLI_NATIVE_TRANSCRIPT_PROVIDERS = new Set(['codex-cli', 'claude-cli']);
27
29
  const recentSendByTarget = new Map<string, number>();
28
30
 
29
31
  interface ApprovalSelectableInstance extends ProviderInstance {
@@ -151,7 +153,17 @@ function getHistorySessionId(h: CommandHelpers, args: any): string | undefined {
151
153
  const instance = h.ctx.instanceManager?.getInstance(targetSessionId);
152
154
  const state = instance?.getState?.();
153
155
  const providerSessionId = typeof state?.providerSessionId === 'string' ? state.providerSessionId.trim() : '';
154
- return providerSessionId || targetSessionId;
156
+ if (providerSessionId) return providerSessionId;
157
+
158
+ const currentSession = h.currentSession as any;
159
+ if (currentSession?.sessionId === targetSessionId) {
160
+ const currentProviderSessionId = typeof currentSession.providerSessionId === 'string'
161
+ ? currentSession.providerSessionId.trim()
162
+ : '';
163
+ if (currentProviderSessionId) return currentProviderSessionId;
164
+ }
165
+
166
+ return targetSessionId;
155
167
  }
156
168
 
157
169
  function getInteractionId(args: any): string | undefined {
@@ -221,6 +233,217 @@ function normalizeReadChatMessages(payload: Record<string, any>): ChatMessage[]
221
233
  return normalizeChatMessages(messages);
222
234
  }
223
235
 
236
+ function getMessageNewestReceivedAt(messages: Array<{ receivedAt?: unknown; timestamp?: unknown }>): number {
237
+ let newest = 0;
238
+ for (const message of messages) {
239
+ const receivedAt = Number(message?.receivedAt ?? message?.timestamp ?? 0);
240
+ if (Number.isFinite(receivedAt) && receivedAt > newest) newest = receivedAt;
241
+ }
242
+ return newest;
243
+ }
244
+
245
+ function readHistorySessionIdFromMessages(messages: ChatMessage[]): string | undefined {
246
+ for (const message of messages as Array<ChatMessage & { historySessionId?: unknown }>) {
247
+ const historySessionId = typeof message?.historySessionId === 'string' ? message.historySessionId.trim() : '';
248
+ if (historySessionId) return historySessionId;
249
+ }
250
+ return undefined;
251
+ }
252
+
253
+ function normalizeNativeHistoryMessages(providerType: string, messages: ChatMessage[]): ChatMessage[] {
254
+ let turnIndex = 0;
255
+ return normalizeChatMessages(messages).map((message, index) => {
256
+ const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
257
+ const kind = typeof message.kind === 'string' && message.kind.trim() ? message.kind.trim() : (role === 'system' ? 'system' : 'standard');
258
+ if ((role === 'user' || role === 'human') && index > 0) turnIndex += 1;
259
+ const historySessionId = typeof (message as any).historySessionId === 'string'
260
+ ? (message as any).historySessionId.trim()
261
+ : '';
262
+ const contentHash = hashSignatureParts([
263
+ providerType,
264
+ historySessionId,
265
+ String(message.receivedAt || message.timestamp || index),
266
+ role,
267
+ kind,
268
+ flattenContent(message.content),
269
+ ]).slice(0, 12);
270
+ const providerUnitKey = typeof message.providerUnitKey === 'string' && message.providerUnitKey.trim()
271
+ ? message.providerUnitKey.trim()
272
+ : `${providerType}:native:${historySessionId || 'workspace'}:${index}:${role || 'message'}:${kind}:${contentHash}`;
273
+ const meta = message.meta && typeof message.meta === 'object' ? message.meta as Record<string, unknown> : undefined;
274
+ const isSystemSessionStart = role === 'system' || kind === 'system' || kind === 'session_start';
275
+ const isActivity = role === 'assistant' && (kind === 'tool' || kind === 'terminal' || kind === 'thought');
276
+ return {
277
+ ...message,
278
+ role: role === 'human' ? 'user' : (role || 'assistant'),
279
+ kind: isSystemSessionStart ? 'system' : kind,
280
+ providerUnitKey,
281
+ bubbleId: typeof message.bubbleId === 'string' && message.bubbleId.trim()
282
+ ? message.bubbleId.trim()
283
+ : `bubble:${providerUnitKey}`,
284
+ _turnKey: typeof message._turnKey === 'string' && message._turnKey.trim()
285
+ ? message._turnKey.trim()
286
+ : `${providerType}:native-turn:${historySessionId || 'workspace'}:${turnIndex}`,
287
+ bubbleState: message.bubbleState || 'final',
288
+ ...(isSystemSessionStart ? {
289
+ visibility: message.visibility || 'hidden',
290
+ transcriptVisibility: message.transcriptVisibility || 'hidden',
291
+ audience: message.audience || 'internal',
292
+ source: message.source || 'runtime_status',
293
+ } : isActivity ? {
294
+ source: message.source || (kind === 'terminal' ? 'terminal_command' : 'tool_call'),
295
+ meta: { ...meta, label: message.senderName || meta?.label || (kind === 'terminal' ? 'Terminal' : 'Tool') },
296
+ } : {
297
+ source: message.source || (role === 'assistant' ? 'assistant_text' : undefined),
298
+ }),
299
+ } as ChatMessage;
300
+ });
301
+ }
302
+
303
+ function buildCliMessageSourceProvenance(args: {
304
+ selected: 'native-history' | 'pty-parser';
305
+ provider: string;
306
+ nativeHandle?: string;
307
+ fallbackReason?: string;
308
+ nativeSource?: string;
309
+ sourcePath?: string;
310
+ sourceMtimeMs?: number;
311
+ nativeMessages?: ChatMessage[];
312
+ ptyMessages?: ChatMessage[];
313
+ returnedMessages?: ChatMessage[];
314
+ safeMapping?: boolean;
315
+ freshEnough?: boolean;
316
+ ptyStatusApprovalOnly?: boolean;
317
+ }): Record<string, unknown> {
318
+ const sourceMtimeMs = Number(args.sourceMtimeMs || 0);
319
+ const sourceMtimeAgeMs = sourceMtimeMs > 0 ? Math.max(0, Date.now() - sourceMtimeMs) : undefined;
320
+ const nativeMessages = args.nativeMessages || [];
321
+ const ptyMessages = args.ptyMessages || [];
322
+ const returnedMessages = args.returnedMessages || [];
323
+ return {
324
+ selected: args.selected,
325
+ provider: args.provider,
326
+ providerType: args.provider,
327
+ ...(args.nativeHandle ? { nativeHandle: args.nativeHandle } : {}),
328
+ ...(args.nativeHandle ? { nativeSessionId: args.nativeHandle } : {}),
329
+ ...(args.fallbackReason ? { fallbackReason: args.fallbackReason } : {}),
330
+ ...(args.nativeSource ? { nativeSource: args.nativeSource } : {}),
331
+ ...(args.sourcePath ? { sourcePath: args.sourcePath } : {}),
332
+ ptyStatusApprovalOnly: args.ptyStatusApprovalOnly === true,
333
+ staleness: {
334
+ sourceMtimeMs: sourceMtimeMs || undefined,
335
+ sourceMtimeAgeMs,
336
+ nativeNewestMessageAt: getMessageNewestReceivedAt(nativeMessages),
337
+ ptyNewestMessageAt: getMessageNewestReceivedAt(ptyMessages),
338
+ freshEnough: args.freshEnough === true,
339
+ },
340
+ coverage: {
341
+ nativeMessageCount: nativeMessages.length,
342
+ ptyMessageCount: ptyMessages.length,
343
+ returnedMessageCount: returnedMessages.length,
344
+ safeMapping: args.safeMapping === true,
345
+ },
346
+ };
347
+ }
348
+
349
+ function buildNativeHistoryFallbackReason(args: {
350
+ providerType: string;
351
+ provider?: ProviderModule;
352
+ nativeSource?: string;
353
+ nativeMessageCount: number;
354
+ safeMapping: boolean;
355
+ freshEnough: boolean;
356
+ }): string {
357
+ if (!supportsCliNativeTranscript(args.providerType, args.provider)) return 'provider_native_transcript_not_supported';
358
+ if (args.nativeSource === 'native-unavailable') return 'native_history_unavailable';
359
+ if (args.nativeSource && args.nativeSource !== 'provider-native') return `native_history_source_${args.nativeSource}`;
360
+ if (args.nativeMessageCount <= 0) return 'native_history_empty';
361
+ if (!args.safeMapping) return 'native_history_not_safely_mapped';
362
+ if (!args.freshEnough) return 'native_history_stale';
363
+ return 'native_history_not_selected';
364
+ }
365
+
366
+ function supportsCliNativeTranscript(providerType: string, provider?: ProviderModule): boolean {
367
+ if (CLI_NATIVE_TRANSCRIPT_PROVIDERS.has(providerType)) return true;
368
+ return provider?.category === 'cli' && isNativeSourceCanonicalHistory(provider?.canonicalHistory);
369
+ }
370
+
371
+ function hasSafeNativeHistoryMapping(args: {
372
+ historySessionId?: string;
373
+ providerSessionId?: string;
374
+ workspace?: string;
375
+ nativeMessages: ChatMessage[];
376
+ }): boolean {
377
+ const explicitSessionId = String(args.historySessionId || args.providerSessionId || '').trim();
378
+ if (explicitSessionId) {
379
+ const messageSessionIds = args.nativeMessages
380
+ .map((message: any) => typeof message?.historySessionId === 'string' ? message.historySessionId.trim() : '')
381
+ .filter(Boolean);
382
+ if (messageSessionIds.length === 0) return true;
383
+ return messageSessionIds.some((id) => id === explicitSessionId);
384
+ }
385
+ const workspace = String(args.workspace || '').trim();
386
+ if (!workspace) return false;
387
+ return args.nativeMessages.some((message: any) => String(message?.workspace || '').trim() === workspace);
388
+ }
389
+
390
+ function readCliProviderNativeHistory(agentStr: string, args: {
391
+ canonicalHistory?: ProviderModule['canonicalHistory'];
392
+ historySessionId?: string;
393
+ workspace?: string;
394
+ offset: number;
395
+ limit: number;
396
+ excludeRecentCount: number;
397
+ historyBehavior?: ProviderModule['historyBehavior'];
398
+ scripts?: ProviderScripts;
399
+ exactSessionScoped?: boolean;
400
+ }): ReturnType<typeof readProviderChatHistory> & { lookup: 'session' | 'workspace' } {
401
+ const sessionHistory = readProviderChatHistory(agentStr, {
402
+ canonicalHistory: args.canonicalHistory,
403
+ historySessionId: args.historySessionId,
404
+ workspace: args.workspace,
405
+ offset: args.offset,
406
+ limit: args.limit,
407
+ excludeRecentCount: args.excludeRecentCount,
408
+ historyBehavior: args.historyBehavior,
409
+ scripts: args.scripts as any,
410
+ });
411
+ // Exact runtime/provider transcript reads must not silently fall back to the
412
+ // workspace's active or most recent native transcript: multiple Hermes/Gemini/
413
+ // Codex sessions can run in the same workspace, and workspace fallback can make
414
+ // read_chat/completion evidence point at a different runtime's prompt.
415
+ if ((sessionHistory as any).source !== 'native-unavailable' || args.exactSessionScoped || !args.historySessionId || !args.workspace) {
416
+ return { ...(sessionHistory as any), lookup: args.historySessionId ? 'session' : 'workspace' };
417
+ }
418
+ const workspaceHistory = readProviderChatHistory(agentStr, {
419
+ canonicalHistory: args.canonicalHistory,
420
+ historySessionId: undefined,
421
+ workspace: args.workspace,
422
+ offset: args.offset,
423
+ limit: args.limit,
424
+ excludeRecentCount: args.excludeRecentCount,
425
+ historyBehavior: args.historyBehavior,
426
+ scripts: args.scripts as any,
427
+ });
428
+ return { ...(workspaceHistory as any), lookup: 'workspace' };
429
+ }
430
+
431
+ function isNativeHistoryFreshEnough(args: {
432
+ sourceMtimeMs?: number;
433
+ nativeMessages: ChatMessage[];
434
+ ptyMessages: ChatMessage[];
435
+ }): boolean {
436
+ const nativeNewest = getMessageNewestReceivedAt(args.nativeMessages);
437
+ const ptyNewest = getMessageNewestReceivedAt(args.ptyMessages);
438
+ if (nativeNewest > 0 && nativeNewest >= ptyNewest) return true;
439
+ const sourceMtimeMs = Number(args.sourceMtimeMs || 0);
440
+ if (sourceMtimeMs > 0 && Date.now() - sourceMtimeMs <= CLI_NATIVE_HISTORY_FRESH_MS) return true;
441
+ return ptyNewest === 0 && nativeNewest > 0;
442
+ }
443
+
444
+ function shouldPreserveReadChatPayloadField(key: string): boolean {
445
+ return key === 'messageSource' || key === 'transcriptProvenance';
446
+ }
224
447
 
225
448
  function deriveHistoryDedupKey(message: ChatMessage & { _unitKey?: string; _turnKey?: string }): string | undefined {
226
449
  const unitKey = typeof message._unitKey === 'string' ? message._unitKey.trim() : '';
@@ -281,7 +504,7 @@ function normalizeReadChatCommandStatus(status: unknown, activeModal: unknown):
281
504
  }
282
505
  switch (raw) {
283
506
  case 'starting':
284
- return hasNonEmptyModalButtons(activeModal) ? 'waiting_approval' : 'generating';
507
+ return hasNonEmptyModalButtons(activeModal) ? 'waiting_approval' : 'starting';
285
508
  case 'stopped':
286
509
  case 'disconnected':
287
510
  case 'not_monitored':
@@ -295,6 +518,16 @@ function isGeneratingLikeStatus(status: unknown): boolean {
295
518
  return status === 'generating' || status === 'streaming' || status === 'long_generating' || status === 'starting';
296
519
  }
297
520
 
521
+ function hasVisibleAssistantMessage(messages: unknown[] | undefined): boolean {
522
+ if (!Array.isArray(messages)) return false;
523
+ return messages.some((message: any) => {
524
+ if (!message || message.role !== 'assistant') return false;
525
+ const kind = typeof message.kind === 'string' ? message.kind : 'standard';
526
+ if (kind !== 'standard') return false;
527
+ return String(message.content || '').trim().length > 0;
528
+ });
529
+ }
530
+
298
531
  function shouldTrustCliAdapterTerminalStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any): boolean {
299
532
  if (!isGeneratingLikeStatus(parsedStatus)) return false;
300
533
  if (hasNonEmptyModalButtons(activeModal)) return false;
@@ -304,7 +537,26 @@ function shouldTrustCliAdapterTerminalStatus(parsedStatus: unknown, activeModal:
304
537
  return true;
305
538
  }
306
539
 
307
- function normalizeCliReadChatStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any): string {
540
+ function normalizeCliReadChatStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any, parsedMessages?: unknown[]): string {
541
+ const adapterRawStatus = typeof adapterStatus?.status === 'string' ? adapterStatus.status.trim() : '';
542
+ if (adapterRawStatus === 'starting'
543
+ && isGeneratingLikeStatus(parsedStatus)
544
+ && !hasNonEmptyModalButtons(activeModal)
545
+ && Array.isArray(parsedMessages)
546
+ && parsedMessages.length === 0
547
+ && Array.isArray(adapterStatus?.messages)
548
+ && adapterStatus.messages.length === 0
549
+ && !(typeof adapter.isProcessing === 'function' && adapter.isProcessing())) {
550
+ return 'starting';
551
+ }
552
+ if (
553
+ isGeneratingLikeStatus(adapterRawStatus)
554
+ && parsedStatus === 'idle'
555
+ && !hasNonEmptyModalButtons(activeModal)
556
+ && !hasVisibleAssistantMessage(parsedMessages)
557
+ ) {
558
+ return adapterRawStatus;
559
+ }
308
560
  if (shouldTrustCliAdapterTerminalStatus(parsedStatus, activeModal, adapter, adapterStatus)) return 'idle';
309
561
  return typeof parsedStatus === 'string' && parsedStatus.trim() ? parsedStatus : 'idle';
310
562
  }
@@ -356,6 +608,7 @@ function buildReadChatCommandResult(payload: Record<string, any>, args: any): Co
356
608
  return {
357
609
  success: true,
358
610
  ...validatedPayload,
611
+ ...Object.fromEntries(Object.entries(payload).filter(([key]) => shouldPreserveReadChatPayloadField(key))),
359
612
  messages: sync.messages,
360
613
  totalMessages: sync.totalMessages,
361
614
  ...(returnedDebugReadChat ? { debugReadChat: returnedDebugReadChat } : {}),
@@ -584,6 +837,8 @@ function buildChatDebugBundleSummary(bundle: Record<string, unknown>): Record<st
584
837
  adapterStatus: debugReadChat.adapterStatus,
585
838
  parsedStatus: debugReadChat.parsedStatus,
586
839
  returnedStatus: debugReadChat.returnedStatus,
840
+ selectedMessageSource: debugReadChat.selectedMessageSource,
841
+ messageSource: debugReadChat.messageSource,
587
842
  parsedMsgCount: debugReadChat.parsedMsgCount,
588
843
  returnedMsgCount: debugReadChat.returnedMsgCount,
589
844
  shouldPreferAdapterMessages: debugReadChat.shouldPreferAdapterMessages,
@@ -646,6 +901,8 @@ export async function handleGetChatDebugBundle(h: CommandHelpers, args: any): Pr
646
901
  providerSessionId: readResult.providerSessionId,
647
902
  transcriptAuthority: readResult.transcriptAuthority,
648
903
  coverage: readResult.coverage,
904
+ messageSource: readResult.messageSource,
905
+ transcriptProvenance: readResult.transcriptProvenance,
649
906
  activeModal: readResult.activeModal,
650
907
  messagesTail: Array.isArray(readResult.messages) ? readResult.messages.slice(-20) : [],
651
908
  debugReadChat: readResult.debugReadChat,
@@ -874,7 +1131,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
874
1131
  ? parsedRecord.coverage
875
1132
  : undefined;
876
1133
  const activeModal = parsedRecord.activeModal ?? parsedRecord.modal ?? null;
877
- const returnedStatus = normalizeCliReadChatStatus(parsedRecord.status, activeModal, adapter, adapterStatus);
1134
+ const returnedStatus = normalizeCliReadChatStatus(parsedRecord.status, activeModal, adapter, adapterStatus, parsedRecord.messages);
878
1135
  const runtimeMessageMerger = getTargetInstance(h, args) as RuntimeChatMessageMerger | null;
879
1136
  const parsedMessages = finalizeStreamingMessagesWhenIdle(parsedRecord.messages as ChatMessage[], returnedStatus);
880
1137
  const returnedMessages = runtimeMessageMerger?.category === 'cli'
@@ -882,25 +1139,155 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
882
1139
  && typeof runtimeMessageMerger.mergeRuntimeChatMessages === 'function'
883
1140
  ? runtimeMessageMerger.mergeRuntimeChatMessages(parsedMessages)
884
1141
  : parsedMessages;
1142
+ const providerType = provider?.type || adapter.cliType;
1143
+ let selectedMessages = returnedMessages;
1144
+ let selectedTitle = title;
1145
+ let selectedProviderSessionId = providerSessionId;
1146
+ let selectedTranscriptAuthority = transcriptAuthority;
1147
+ let selectedCoverage = coverage;
1148
+ let messageSource = buildCliMessageSourceProvenance({
1149
+ selected: 'pty-parser',
1150
+ provider: adapter.cliType,
1151
+ fallbackReason: supportsCliNativeTranscript(providerType, provider) ? 'native_history_not_checked' : 'provider_native_transcript_not_supported',
1152
+ ptyMessages: returnedMessages,
1153
+ returnedMessages,
1154
+ ptyStatusApprovalOnly: false,
1155
+ });
1156
+
1157
+ if (supportsCliNativeTranscript(providerType, provider) && isNativeSourceCanonicalHistory(provider?.canonicalHistory)) {
1158
+ const agentStr = provider?.type || args?.agentType || getCurrentProviderType(h, adapter.cliType);
1159
+ const workspace = typeof args?.workspace === 'string'
1160
+ ? args.workspace
1161
+ : typeof (h.currentSession as any)?.workspace === 'string'
1162
+ ? (h.currentSession as any).workspace
1163
+ : typeof adapter.workingDir === 'string'
1164
+ ? adapter.workingDir
1165
+ : undefined;
1166
+ const nativeHistoryLimit = Math.max(
1167
+ normalizeReadChatTailLimit(args) || 0,
1168
+ returnedMessages.length,
1169
+ 200,
1170
+ );
1171
+ const exactNativeHistoryScope = Boolean(
1172
+ (typeof args?.historySessionId === 'string' && args.historySessionId.trim())
1173
+ || (typeof args?.providerSessionId === 'string' && args.providerSessionId.trim())
1174
+ || providerSessionId
1175
+ || ((h.currentSession as any)?.sessionId === args?.targetSessionId && typeof (h.currentSession as any)?.providerSessionId === 'string' && (h.currentSession as any).providerSessionId.trim())
1176
+ );
1177
+ let nativeHistory: (ReturnType<typeof readProviderChatHistory> & { lookup?: 'session' | 'workspace' }) | null = null;
1178
+ try {
1179
+ nativeHistory = readCliProviderNativeHistory(agentStr, {
1180
+ canonicalHistory: provider?.canonicalHistory,
1181
+ historySessionId,
1182
+ workspace,
1183
+ offset: 0,
1184
+ limit: nativeHistoryLimit,
1185
+ excludeRecentCount: 0,
1186
+ historyBehavior: provider?.historyBehavior,
1187
+ scripts: provider?.scripts as any,
1188
+ exactSessionScoped: exactNativeHistoryScope,
1189
+ });
1190
+ } catch (error: any) {
1191
+ const fallbackReason = `native_history_error:${error?.message || String(error)}`;
1192
+ messageSource = buildCliMessageSourceProvenance({
1193
+ selected: 'pty-parser',
1194
+ provider: adapter.cliType,
1195
+ fallbackReason,
1196
+ ptyMessages: returnedMessages,
1197
+ returnedMessages,
1198
+ ptyStatusApprovalOnly: false,
1199
+ });
1200
+ nativeHistory = null;
1201
+ }
1202
+
1203
+ if (nativeHistory) {
1204
+ const nativeMessages = Array.isArray((nativeHistory as any).messages)
1205
+ ? normalizeNativeHistoryMessages(agentStr, (nativeHistory as any).messages as ChatMessage[])
1206
+ : [];
1207
+ const historyProviderSessionId = typeof (nativeHistory as any)?.providerSessionId === 'string'
1208
+ ? (nativeHistory as any).providerSessionId
1209
+ : readHistorySessionIdFromMessages(nativeMessages) || historySessionId;
1210
+ const lookup = (nativeHistory as any).lookup === 'workspace' ? 'workspace' : 'session';
1211
+ const safeMapping = hasSafeNativeHistoryMapping({
1212
+ historySessionId: lookup === 'workspace' ? undefined : historySessionId,
1213
+ providerSessionId: lookup === 'workspace' ? undefined : providerSessionId,
1214
+ workspace,
1215
+ nativeMessages,
1216
+ });
1217
+ const freshEnough = isNativeHistoryFreshEnough({
1218
+ sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
1219
+ nativeMessages,
1220
+ ptyMessages: returnedMessages,
1221
+ });
1222
+ if ((nativeHistory as any).source === 'provider-native' && nativeMessages.length > 0 && safeMapping && freshEnough) {
1223
+ selectedMessages = finalizeStreamingMessagesWhenIdle(nativeMessages, returnedStatus);
1224
+ selectedProviderSessionId = historyProviderSessionId || providerSessionId;
1225
+ selectedTranscriptAuthority = 'provider';
1226
+ selectedCoverage = (nativeHistory as any).hasMore ? 'tail' : 'full';
1227
+ messageSource = buildCliMessageSourceProvenance({
1228
+ selected: 'native-history',
1229
+ provider: adapter.cliType,
1230
+ nativeHandle: selectedProviderSessionId || historySessionId,
1231
+ nativeSource: (nativeHistory as any).source,
1232
+ sourcePath: (nativeHistory as any).sourcePath,
1233
+ sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
1234
+ nativeMessages,
1235
+ ptyMessages: returnedMessages,
1236
+ returnedMessages: selectedMessages,
1237
+ safeMapping,
1238
+ freshEnough,
1239
+ ptyStatusApprovalOnly: true,
1240
+ });
1241
+ } else {
1242
+ const fallbackReason = buildNativeHistoryFallbackReason({
1243
+ providerType,
1244
+ provider,
1245
+ nativeSource: (nativeHistory as any).source,
1246
+ nativeMessageCount: nativeMessages.length,
1247
+ safeMapping,
1248
+ freshEnough,
1249
+ });
1250
+ messageSource = buildCliMessageSourceProvenance({
1251
+ selected: 'pty-parser',
1252
+ provider: adapter.cliType,
1253
+ nativeHandle: historyProviderSessionId || historySessionId,
1254
+ fallbackReason,
1255
+ nativeSource: (nativeHistory as any).source,
1256
+ sourcePath: (nativeHistory as any).sourcePath,
1257
+ sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
1258
+ nativeMessages,
1259
+ ptyMessages: returnedMessages,
1260
+ returnedMessages,
1261
+ safeMapping,
1262
+ freshEnough,
1263
+ ptyStatusApprovalOnly: false,
1264
+ });
1265
+ }
1266
+ }
1267
+ }
885
1268
  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
1269
  return buildReadChatCommandResult({
887
- messages: returnedMessages,
1270
+ messages: selectedMessages,
888
1271
  status: returnedStatus,
889
1272
  activeModal,
1273
+ messageSource,
1274
+ transcriptProvenance: messageSource,
890
1275
  debugReadChat: {
891
1276
  provider: adapter.cliType,
892
1277
  targetSessionId: String(args?.targetSessionId || ''),
893
1278
  adapterStatus: String(adapterStatus.status || ''),
894
1279
  parsedStatus: String(parsedRecord.status || ''),
895
1280
  returnedStatus: String(returnedStatus || ''),
896
- shouldPreferAdapterMessages: false,
1281
+ selectedMessageSource: (messageSource as any).selected,
1282
+ messageSource,
1283
+ shouldPreferAdapterMessages: supportsCliNativeTranscript(providerType, provider) && (messageSource as any).selected !== 'native-history',
897
1284
  parsedMsgCount: parsedRecord.messages.length,
898
- returnedMsgCount: returnedMessages.length,
1285
+ returnedMsgCount: selectedMessages.length,
899
1286
  },
900
- ...(title ? { title } : {}),
901
- ...(providerSessionId ? { providerSessionId } : {}),
902
- ...(transcriptAuthority ? { transcriptAuthority } : {}),
903
- ...(coverage ? { coverage } : {}),
1287
+ ...(selectedTitle ? { title: selectedTitle } : {}),
1288
+ ...(selectedProviderSessionId ? { providerSessionId: selectedProviderSessionId } : {}),
1289
+ ...(selectedTranscriptAuthority ? { transcriptAuthority: selectedTranscriptAuthority } : {}),
1290
+ ...(selectedCoverage ? { coverage: selectedCoverage } : {}),
904
1291
  }, args);
905
1292
  }
906
1293
  const historyLimit = normalizeReadChatTailLimit(args);
@@ -911,7 +1298,24 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
911
1298
  : typeof (h.currentSession as any)?.workspace === 'string'
912
1299
  ? (h.currentSession as any).workspace
913
1300
  : undefined;
914
- const history = readProviderChatHistory(agentStr, {
1301
+ const exactNativeHistoryScope = Boolean(
1302
+ (typeof args?.historySessionId === 'string' && args.historySessionId.trim())
1303
+ || (typeof args?.providerSessionId === 'string' && args.providerSessionId.trim())
1304
+ || ((h.currentSession as any)?.sessionId === args?.targetSessionId && typeof (h.currentSession as any)?.providerSessionId === 'string' && (h.currentSession as any).providerSessionId.trim())
1305
+ );
1306
+ const history = supportsCliNativeTranscript(agentStr, provider) && isNativeSourceCanonicalHistory(provider?.canonicalHistory)
1307
+ ? readCliProviderNativeHistory(agentStr, {
1308
+ canonicalHistory: provider?.canonicalHistory,
1309
+ historySessionId,
1310
+ workspace,
1311
+ offset: 0,
1312
+ limit: historyLimit,
1313
+ excludeRecentCount: 0,
1314
+ historyBehavior: provider?.historyBehavior,
1315
+ scripts: provider?.scripts as any,
1316
+ exactSessionScoped: exactNativeHistoryScope,
1317
+ })
1318
+ : readProviderChatHistory(agentStr, {
915
1319
  canonicalHistory: provider?.canonicalHistory,
916
1320
  historySessionId,
917
1321
  workspace,
@@ -921,12 +1325,65 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
921
1325
  historyBehavior: provider?.historyBehavior,
922
1326
  scripts: provider?.scripts as any,
923
1327
  });
1328
+ const lookup = (history as any).lookup === 'workspace' ? 'workspace' : 'session';
1329
+ const historyMessages = Array.isArray((history as any)?.messages)
1330
+ ? normalizeNativeHistoryMessages(agentStr, (history as any).messages as ChatMessage[])
1331
+ : [];
924
1332
  const historyProviderSessionId = typeof (history as any)?.providerSessionId === 'string'
925
1333
  ? (history as any).providerSessionId
926
- : historySessionId;
1334
+ : readHistorySessionIdFromMessages(historyMessages) || historySessionId;
1335
+ const safeMapping = supportsCliNativeTranscript(agentStr, provider)
1336
+ ? hasSafeNativeHistoryMapping({
1337
+ historySessionId: lookup === 'workspace' ? undefined : historySessionId,
1338
+ providerSessionId: lookup === 'workspace' ? undefined : historyProviderSessionId,
1339
+ workspace,
1340
+ nativeMessages: historyMessages,
1341
+ })
1342
+ : false;
1343
+ const nativeSelected = supportsCliNativeTranscript(agentStr, provider)
1344
+ && (history as any).source === 'provider-native'
1345
+ && historyMessages.length > 0
1346
+ && safeMapping;
1347
+ const messageSource = buildCliMessageSourceProvenance({
1348
+ selected: nativeSelected ? 'native-history' : 'pty-parser',
1349
+ provider: agentStr,
1350
+ nativeHandle: historyProviderSessionId || historySessionId,
1351
+ fallbackReason: nativeSelected
1352
+ ? undefined
1353
+ : buildNativeHistoryFallbackReason({
1354
+ providerType: agentStr,
1355
+ provider,
1356
+ nativeSource: (history as any).source,
1357
+ nativeMessageCount: historyMessages.length,
1358
+ safeMapping,
1359
+ freshEnough: true,
1360
+ }),
1361
+ nativeSource: (history as any).source,
1362
+ sourcePath: (history as any).sourcePath,
1363
+ sourceMtimeMs: (history as any).sourceMtimeMs,
1364
+ nativeMessages: historyMessages,
1365
+ returnedMessages: historyMessages,
1366
+ safeMapping,
1367
+ freshEnough: true,
1368
+ ptyStatusApprovalOnly: false,
1369
+ });
1370
+ const requiresNativeSource = supportsCliNativeTranscript(agentStr, provider)
1371
+ && isNativeSourceCanonicalHistory(provider?.canonicalHistory);
1372
+ if (requiresNativeSource && !nativeSelected) {
1373
+ return {
1374
+ success: false,
1375
+ code: 'native_history_not_safely_available',
1376
+ error: 'Provider-native history was not safely available for the requested CLI session.',
1377
+ providerSessionId: historyProviderSessionId,
1378
+ messageSource,
1379
+ transcriptProvenance: messageSource,
1380
+ };
1381
+ }
927
1382
  return buildReadChatCommandResult({
928
- messages: Array.isArray((history as any)?.messages) ? (history as any).messages : [],
1383
+ messages: historyMessages,
929
1384
  status: 'idle',
1385
+ messageSource,
1386
+ transcriptProvenance: messageSource,
930
1387
  ...(typeof (history as any)?.title === 'string' ? { title: (history as any).title } : {}),
931
1388
  ...(historyProviderSessionId ? { providerSessionId: historyProviderSessionId } : {}),
932
1389
  ...(((provider?.historyBehavior as any)?.transcriptAuthority === 'provider' || (provider?.historyBehavior as any)?.transcriptAuthority === 'daemon')