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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/cli-adapters/provider-cli-adapter.d.ts +10 -0
  2. package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
  3. package/dist/cli-adapters/provider-cli-shared.d.ts +10 -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 +12 -5
  7. package/dist/index.js +5733 -1216
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +5699 -1203
  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 +26 -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 +255 -14
  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 +28 -10
  34. package/src/commands/chat-commands.ts +570 -20
  35. package/src/commands/cli-manager.ts +129 -1
  36. package/src/commands/handler.ts +8 -1
  37. package/src/commands/mesh-coordinator.ts +13 -143
  38. package/src/commands/router.ts +2820 -437
  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 +30 -4
  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 +31 -8
  52. package/src/mesh/mesh-active-work.ts +255 -0
  53. package/src/mesh/mesh-events.ts +389 -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', 'hermes-cli', 'antigravity-cli']);
27
29
  const recentSendByTarget = new Map<string, number>();
28
30
 
29
31
  interface ApprovalSelectableInstance extends ProviderInstance {
@@ -48,6 +50,16 @@ function getTargetedCliAdapter(h: CommandHelpers, args: any, providerType?: stri
48
50
  return h.getCliAdapter(args?.targetSessionId || providerType || h.currentSession?.providerType || h.currentManagerKey);
49
51
  }
50
52
 
53
+ function getExplicitHistorySessionId(args: any): string | undefined {
54
+ const explicit = typeof args?.historySessionId === 'string' ? args.historySessionId.trim() : '';
55
+ if (explicit) return explicit;
56
+
57
+ const explicitProviderSessionId = typeof args?.providerSessionId === 'string' ? args.providerSessionId.trim() : '';
58
+ if (explicitProviderSessionId) return explicitProviderSessionId;
59
+
60
+ return undefined;
61
+ }
62
+
51
63
  function getTargetInstance(h: CommandHelpers, args: any): ApprovalSelectableInstance | null {
52
64
  const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
53
65
  const sessionId = targetSessionId || h.currentSession?.sessionId || '';
@@ -139,19 +151,47 @@ async function waitOnceForFreshHermesCliStart(adapter: CliAdapter, log: (msg: st
139
151
  }
140
152
 
141
153
  function getHistorySessionId(h: CommandHelpers, args: any): string | undefined {
142
- const explicit = typeof args?.historySessionId === 'string' ? args.historySessionId.trim() : '';
154
+ const explicit = getExplicitHistorySessionId(args);
143
155
  if (explicit) return explicit;
144
156
 
145
- const explicitProviderSessionId = typeof args?.providerSessionId === 'string' ? args.providerSessionId.trim() : '';
146
- if (explicitProviderSessionId) return explicitProviderSessionId;
147
-
148
157
  const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
149
158
  if (!targetSessionId) return undefined;
150
159
 
151
- const instance = h.ctx.instanceManager?.getInstance(targetSessionId);
160
+ const session = h.ctx.sessionRegistry?.get(targetSessionId) as any;
161
+ const registeredProviderSessionId = typeof session?.providerSessionId === 'string' ? session.providerSessionId.trim() : '';
162
+ if (registeredProviderSessionId) return registeredProviderSessionId;
163
+
164
+ const instance = getTargetInstance(h, args);
152
165
  const state = instance?.getState?.();
153
166
  const providerSessionId = typeof state?.providerSessionId === 'string' ? state.providerSessionId.trim() : '';
154
- return providerSessionId || targetSessionId;
167
+ if (providerSessionId) return providerSessionId;
168
+
169
+ const currentSession = h.currentSession as any;
170
+ if (currentSession?.sessionId === targetSessionId) {
171
+ const currentProviderSessionId = typeof currentSession.providerSessionId === 'string'
172
+ ? currentSession.providerSessionId.trim()
173
+ : '';
174
+ if (currentProviderSessionId) return currentProviderSessionId;
175
+ }
176
+
177
+ return targetSessionId;
178
+ }
179
+
180
+ function resolveCliNativeHistorySessionId(args: any, currentHistorySessionId: string | undefined, parsedProviderSessionId: string | undefined): string | undefined {
181
+ const explicit = getExplicitHistorySessionId(args);
182
+ if (explicit) return explicit;
183
+
184
+ const parsed = typeof parsedProviderSessionId === 'string' ? parsedProviderSessionId.trim() : '';
185
+ const current = typeof currentHistorySessionId === 'string' ? currentHistorySessionId.trim() : '';
186
+ const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
187
+
188
+ // getHistorySessionId falls back to the runtime session id when no native
189
+ // handle has been registered yet. For live CLI adapters the parser may
190
+ // already know the provider-native handle; prefer it over the runtime id so
191
+ // exact native reads do not miss the worker transcript and fall back to PTY
192
+ // or same-workspace history.
193
+ if (parsed && (!current || current === targetSessionId)) return parsed;
194
+ return current || parsed || undefined;
155
195
  }
156
196
 
157
197
  function getInteractionId(args: any): string | undefined {
@@ -221,6 +261,263 @@ function normalizeReadChatMessages(payload: Record<string, any>): ChatMessage[]
221
261
  return normalizeChatMessages(messages);
222
262
  }
223
263
 
264
+ function getMessageNewestReceivedAt(messages: Array<{ receivedAt?: unknown; timestamp?: unknown }>): number {
265
+ let newest = 0;
266
+ for (const message of messages) {
267
+ const receivedAt = Number(message?.receivedAt ?? message?.timestamp ?? 0);
268
+ if (Number.isFinite(receivedAt) && receivedAt > newest) newest = receivedAt;
269
+ }
270
+ return newest;
271
+ }
272
+
273
+ function readHistorySessionIdFromMessages(messages: ChatMessage[]): string | undefined {
274
+ for (const message of messages as Array<ChatMessage & { historySessionId?: unknown }>) {
275
+ const historySessionId = typeof message?.historySessionId === 'string' ? message.historySessionId.trim() : '';
276
+ if (historySessionId) return historySessionId;
277
+ }
278
+ return undefined;
279
+ }
280
+
281
+ function normalizeNativeHistoryMessages(providerType: string, messages: ChatMessage[]): ChatMessage[] {
282
+ let turnIndex = 0;
283
+ return normalizeChatMessages(messages).map((message, index) => {
284
+ const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
285
+ const kind = typeof message.kind === 'string' && message.kind.trim() ? message.kind.trim() : (role === 'system' ? 'system' : 'standard');
286
+ if ((role === 'user' || role === 'human') && index > 0) turnIndex += 1;
287
+ const historySessionId = typeof (message as any).historySessionId === 'string'
288
+ ? (message as any).historySessionId.trim()
289
+ : '';
290
+ const contentHash = hashSignatureParts([
291
+ providerType,
292
+ historySessionId,
293
+ String(message.receivedAt || message.timestamp || index),
294
+ role,
295
+ kind,
296
+ flattenContent(message.content),
297
+ ]).slice(0, 12);
298
+ const providerUnitKey = typeof message.providerUnitKey === 'string' && message.providerUnitKey.trim()
299
+ ? message.providerUnitKey.trim()
300
+ : `${providerType}:native:${historySessionId || 'workspace'}:${index}:${role || 'message'}:${kind}:${contentHash}`;
301
+ const meta = message.meta && typeof message.meta === 'object' ? message.meta as Record<string, unknown> : undefined;
302
+ const isSystemSessionStart = role === 'system' || kind === 'system' || kind === 'session_start';
303
+ const isActivity = role === 'assistant' && (kind === 'tool' || kind === 'terminal' || kind === 'thought');
304
+ return {
305
+ ...message,
306
+ role: role === 'human' ? 'user' : (role || 'assistant'),
307
+ kind: isSystemSessionStart ? 'system' : kind,
308
+ providerUnitKey,
309
+ bubbleId: typeof message.bubbleId === 'string' && message.bubbleId.trim()
310
+ ? message.bubbleId.trim()
311
+ : `bubble:${providerUnitKey}`,
312
+ _turnKey: typeof message._turnKey === 'string' && message._turnKey.trim()
313
+ ? message._turnKey.trim()
314
+ : `${providerType}:native-turn:${historySessionId || 'workspace'}:${turnIndex}`,
315
+ bubbleState: message.bubbleState || 'final',
316
+ ...(isSystemSessionStart ? {
317
+ visibility: message.visibility || 'hidden',
318
+ transcriptVisibility: message.transcriptVisibility || 'hidden',
319
+ audience: message.audience || 'internal',
320
+ source: message.source || 'runtime_status',
321
+ } : isActivity ? {
322
+ source: message.source || (kind === 'terminal' ? 'terminal_command' : 'tool_call'),
323
+ meta: { ...meta, label: message.senderName || meta?.label || (kind === 'terminal' ? 'Terminal' : 'Tool') },
324
+ } : {
325
+ source: message.source || (role === 'assistant' ? 'assistant_text' : undefined),
326
+ }),
327
+ } as ChatMessage;
328
+ });
329
+ }
330
+
331
+ function buildCliMessageSourceProvenance(args: {
332
+ selected: 'native-history' | 'pty-parser';
333
+ provider: string;
334
+ nativeHandle?: string;
335
+ fallbackReason?: string;
336
+ nativeSource?: string;
337
+ sourcePath?: string;
338
+ sourceMtimeMs?: number;
339
+ nativeMessages?: ChatMessage[];
340
+ ptyMessages?: ChatMessage[];
341
+ returnedMessages?: ChatMessage[];
342
+ safeMapping?: boolean;
343
+ freshEnough?: boolean;
344
+ ptyStatusApprovalOnly?: boolean;
345
+ }): Record<string, unknown> {
346
+ const sourceMtimeMs = Number(args.sourceMtimeMs || 0);
347
+ const sourceMtimeAgeMs = sourceMtimeMs > 0 ? Math.max(0, Date.now() - sourceMtimeMs) : undefined;
348
+ const nativeMessages = args.nativeMessages || [];
349
+ const ptyMessages = args.ptyMessages || [];
350
+ const returnedMessages = args.returnedMessages || [];
351
+ return {
352
+ selected: args.selected,
353
+ provider: args.provider,
354
+ providerType: args.provider,
355
+ ...(args.nativeHandle ? { nativeHandle: args.nativeHandle } : {}),
356
+ ...(args.nativeHandle ? { nativeSessionId: args.nativeHandle } : {}),
357
+ ...(args.fallbackReason ? { fallbackReason: args.fallbackReason } : {}),
358
+ ...(args.nativeSource ? { nativeSource: args.nativeSource } : {}),
359
+ ...(args.sourcePath ? { sourcePath: args.sourcePath } : {}),
360
+ ptyStatusApprovalOnly: args.ptyStatusApprovalOnly === true,
361
+ staleness: {
362
+ sourceMtimeMs: sourceMtimeMs || undefined,
363
+ sourceMtimeAgeMs,
364
+ nativeNewestMessageAt: getMessageNewestReceivedAt(nativeMessages),
365
+ ptyNewestMessageAt: getMessageNewestReceivedAt(ptyMessages),
366
+ freshEnough: args.freshEnough === true,
367
+ },
368
+ coverage: {
369
+ nativeMessageCount: nativeMessages.length,
370
+ ptyMessageCount: ptyMessages.length,
371
+ returnedMessageCount: returnedMessages.length,
372
+ safeMapping: args.safeMapping === true,
373
+ },
374
+ };
375
+ }
376
+
377
+ function buildNativeHistoryFallbackReason(args: {
378
+ providerType: string;
379
+ provider?: ProviderModule;
380
+ nativeSource?: string;
381
+ nativeMessageCount: number;
382
+ safeMapping: boolean;
383
+ freshEnough: boolean;
384
+ }): string {
385
+ if (!supportsCliNativeTranscript(args.providerType, args.provider)) return 'provider_native_transcript_not_supported';
386
+ if (args.nativeSource === 'native-unavailable') return 'native_history_unavailable';
387
+ if (args.nativeSource && args.nativeSource !== 'provider-native') return `native_history_source_${args.nativeSource}`;
388
+ if (args.nativeMessageCount <= 0) return 'native_history_empty';
389
+ if (!args.safeMapping) return 'native_history_not_safely_mapped';
390
+ if (!args.freshEnough) return 'native_history_stale';
391
+ return 'native_history_not_selected';
392
+ }
393
+
394
+ function supportsCliNativeTranscript(providerType: string, provider?: ProviderModule): boolean {
395
+ if (CLI_NATIVE_TRANSCRIPT_PROVIDERS.has(providerType)) return true;
396
+ return provider?.category === 'cli' && isNativeSourceCanonicalHistory(provider?.canonicalHistory);
397
+ }
398
+
399
+ function getComparableVisibleText(message: ChatMessage | undefined): string {
400
+ if (!message) return '';
401
+ const role = String((message as any).role || '').trim().toLowerCase();
402
+ if (role !== 'user' && role !== 'assistant') return '';
403
+ const kind = String((message as any).kind || 'standard').trim().toLowerCase();
404
+ if (kind && kind !== 'standard') return '';
405
+ const content = flattenContent((message as any).content).replace(/\s+/g, ' ').trim();
406
+ return content;
407
+ }
408
+
409
+ function hasOverlappingVisibleConversationText(nativeMessages: ChatMessage[], ptyMessages: ChatMessage[]): boolean {
410
+ const nativeTexts = nativeMessages.map(getComparableVisibleText).filter(Boolean);
411
+ const ptyTexts = ptyMessages.map(getComparableVisibleText).filter(Boolean);
412
+ if (nativeTexts.length === 0 || ptyTexts.length === 0) return false;
413
+ for (const nativeText of nativeTexts) {
414
+ for (const ptyText of ptyTexts) {
415
+ if (nativeText === ptyText) return true;
416
+ const shorter = nativeText.length <= ptyText.length ? nativeText : ptyText;
417
+ const longer = nativeText.length <= ptyText.length ? ptyText : nativeText;
418
+ if (shorter.length >= 32 && longer.includes(shorter)) return true;
419
+ }
420
+ }
421
+ return false;
422
+ }
423
+
424
+ function hasSafeNativeHistoryMapping(args: {
425
+ historySessionId?: string;
426
+ providerSessionId?: string;
427
+ workspace?: string;
428
+ nativeMessages: ChatMessage[];
429
+ ptyMessages?: ChatMessage[];
430
+ requireWorkspaceContentOverlap?: boolean;
431
+ }): boolean {
432
+ const explicitSessionId = String(args.historySessionId || args.providerSessionId || '').trim();
433
+ if (explicitSessionId) {
434
+ const messageSessionIds = args.nativeMessages
435
+ .map((message: any) => typeof message?.historySessionId === 'string' ? message.historySessionId.trim() : '')
436
+ .filter(Boolean);
437
+ if (messageSessionIds.length === 0) return true;
438
+ return messageSessionIds.some((id) => id === explicitSessionId);
439
+ }
440
+ const workspace = String(args.workspace || '').trim();
441
+ if (!workspace) return false;
442
+ const workspaceMatches = args.nativeMessages.some((message: any) => String(message?.workspace || '').trim() === workspace);
443
+ if (!workspaceMatches) return false;
444
+ if (!args.requireWorkspaceContentOverlap) return true;
445
+ return hasOverlappingVisibleConversationText(args.nativeMessages, args.ptyMessages || []);
446
+ }
447
+
448
+ function readCliProviderNativeHistory(agentStr: string, args: {
449
+ canonicalHistory?: ProviderModule['canonicalHistory'];
450
+ historySessionId?: string;
451
+ workspace?: string;
452
+ offset: number;
453
+ limit: number;
454
+ excludeRecentCount: number;
455
+ historyBehavior?: ProviderModule['historyBehavior'];
456
+ scripts?: ProviderScripts;
457
+ exactSessionScoped?: boolean;
458
+ }): ReturnType<typeof readProviderChatHistory> & { lookup: 'session' | 'workspace' } {
459
+ const sessionHistory = readProviderChatHistory(agentStr, {
460
+ canonicalHistory: args.canonicalHistory,
461
+ historySessionId: args.historySessionId,
462
+ workspace: args.workspace,
463
+ offset: args.offset,
464
+ limit: args.limit,
465
+ excludeRecentCount: args.excludeRecentCount,
466
+ historyBehavior: args.historyBehavior,
467
+ scripts: args.scripts as any,
468
+ });
469
+ // Exact runtime/provider transcript reads must not silently fall back to the
470
+ // workspace's active or most recent native transcript: multiple Hermes/Gemini/
471
+ // Codex sessions can run in the same workspace, and workspace fallback can make
472
+ // read_chat/completion evidence point at a different runtime's prompt.
473
+ if ((sessionHistory as any).source !== 'native-unavailable' || args.exactSessionScoped || !args.historySessionId || !args.workspace) {
474
+ return { ...(sessionHistory as any), lookup: args.historySessionId ? 'session' : 'workspace' };
475
+ }
476
+ const workspaceHistory = readProviderChatHistory(agentStr, {
477
+ canonicalHistory: args.canonicalHistory,
478
+ historySessionId: undefined,
479
+ workspace: args.workspace,
480
+ offset: args.offset,
481
+ limit: args.limit,
482
+ excludeRecentCount: args.excludeRecentCount,
483
+ historyBehavior: args.historyBehavior,
484
+ scripts: args.scripts as any,
485
+ });
486
+ return { ...(workspaceHistory as any), lookup: 'workspace' };
487
+ }
488
+
489
+ function isNativeHistoryFreshEnough(args: {
490
+ sourceMtimeMs?: number;
491
+ nativeMessages: ChatMessage[];
492
+ ptyMessages: ChatMessage[];
493
+ }): boolean {
494
+ const nativeNewest = getMessageNewestReceivedAt(args.nativeMessages);
495
+ const ptyNewest = getMessageNewestReceivedAt(args.ptyMessages);
496
+ if (nativeNewest > 0 && nativeNewest >= ptyNewest) return true;
497
+ const sourceMtimeMs = Number(args.sourceMtimeMs || 0);
498
+ if (sourceMtimeMs > 0 && Date.now() - sourceMtimeMs <= CLI_NATIVE_HISTORY_FRESH_MS) return true;
499
+ return ptyNewest === 0 && nativeNewest > 0;
500
+ }
501
+
502
+ function shouldPreserveReadChatPayloadField(key: string): boolean {
503
+ return key === 'messageSource' || key === 'transcriptProvenance';
504
+ }
505
+
506
+ function updateMessageSourceReturnedCount(value: unknown, returnedMessageCount: number): unknown {
507
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return value;
508
+ const record = value as Record<string, unknown>;
509
+ const coverage = record.coverage && typeof record.coverage === 'object' && !Array.isArray(record.coverage)
510
+ ? record.coverage as Record<string, unknown>
511
+ : undefined;
512
+ if (!coverage) return value;
513
+ return {
514
+ ...record,
515
+ coverage: {
516
+ ...coverage,
517
+ returnedMessageCount,
518
+ },
519
+ };
520
+ }
224
521
 
225
522
  function deriveHistoryDedupKey(message: ChatMessage & { _unitKey?: string; _turnKey?: string }): string | undefined {
226
523
  const unitKey = typeof message._unitKey === 'string' ? message._unitKey.trim() : '';
@@ -281,7 +578,7 @@ function normalizeReadChatCommandStatus(status: unknown, activeModal: unknown):
281
578
  }
282
579
  switch (raw) {
283
580
  case 'starting':
284
- return hasNonEmptyModalButtons(activeModal) ? 'waiting_approval' : 'generating';
581
+ return hasNonEmptyModalButtons(activeModal) ? 'waiting_approval' : 'starting';
285
582
  case 'stopped':
286
583
  case 'disconnected':
287
584
  case 'not_monitored':
@@ -295,6 +592,16 @@ function isGeneratingLikeStatus(status: unknown): boolean {
295
592
  return status === 'generating' || status === 'streaming' || status === 'long_generating' || status === 'starting';
296
593
  }
297
594
 
595
+ function hasVisibleAssistantMessage(messages: unknown[] | undefined): boolean {
596
+ if (!Array.isArray(messages)) return false;
597
+ return messages.some((message: any) => {
598
+ if (!message || message.role !== 'assistant') return false;
599
+ const kind = typeof message.kind === 'string' ? message.kind : 'standard';
600
+ if (kind !== 'standard') return false;
601
+ return String(message.content || '').trim().length > 0;
602
+ });
603
+ }
604
+
298
605
  function shouldTrustCliAdapterTerminalStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any): boolean {
299
606
  if (!isGeneratingLikeStatus(parsedStatus)) return false;
300
607
  if (hasNonEmptyModalButtons(activeModal)) return false;
@@ -304,7 +611,26 @@ function shouldTrustCliAdapterTerminalStatus(parsedStatus: unknown, activeModal:
304
611
  return true;
305
612
  }
306
613
 
307
- function normalizeCliReadChatStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any): string {
614
+ function normalizeCliReadChatStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any, parsedMessages?: unknown[]): string {
615
+ const adapterRawStatus = typeof adapterStatus?.status === 'string' ? adapterStatus.status.trim() : '';
616
+ if (adapterRawStatus === 'starting'
617
+ && isGeneratingLikeStatus(parsedStatus)
618
+ && !hasNonEmptyModalButtons(activeModal)
619
+ && Array.isArray(parsedMessages)
620
+ && parsedMessages.length === 0
621
+ && Array.isArray(adapterStatus?.messages)
622
+ && adapterStatus.messages.length === 0
623
+ && !(typeof adapter.isProcessing === 'function' && adapter.isProcessing())) {
624
+ return 'starting';
625
+ }
626
+ if (
627
+ isGeneratingLikeStatus(adapterRawStatus)
628
+ && parsedStatus === 'idle'
629
+ && !hasNonEmptyModalButtons(activeModal)
630
+ && !hasVisibleAssistantMessage(parsedMessages)
631
+ ) {
632
+ return adapterRawStatus;
633
+ }
308
634
  if (shouldTrustCliAdapterTerminalStatus(parsedStatus, activeModal, adapter, adapterStatus)) return 'idle';
309
635
  return typeof parsedStatus === 'string' && parsedStatus.trim() ? parsedStatus : 'idle';
310
636
  }
@@ -342,6 +668,13 @@ function buildReadChatCommandResult(payload: Record<string, any>, args: any): Co
342
668
  const visibleMessages = filterUserFacingChatMessages(messages);
343
669
  const sync = buildFullTail(visibleMessages, normalizeReadChatTailLimit(args));
344
670
  const hiddenMsgCount = Math.max(0, messages.length - visibleMessages.length);
671
+ const preservedPayloadFields = Object.fromEntries(Object.entries(payload).filter(([key]) => shouldPreserveReadChatPayloadField(key)));
672
+ if (preservedPayloadFields.messageSource) {
673
+ preservedPayloadFields.messageSource = updateMessageSourceReturnedCount(preservedPayloadFields.messageSource, sync.messages.length);
674
+ }
675
+ if (preservedPayloadFields.transcriptProvenance) {
676
+ preservedPayloadFields.transcriptProvenance = updateMessageSourceReturnedCount(preservedPayloadFields.transcriptProvenance, sync.messages.length);
677
+ }
345
678
  const returnedDebugReadChat = debugReadChat
346
679
  ? {
347
680
  ...debugReadChat,
@@ -356,6 +689,7 @@ function buildReadChatCommandResult(payload: Record<string, any>, args: any): Co
356
689
  return {
357
690
  success: true,
358
691
  ...validatedPayload,
692
+ ...preservedPayloadFields,
359
693
  messages: sync.messages,
360
694
  totalMessages: sync.totalMessages,
361
695
  ...(returnedDebugReadChat ? { debugReadChat: returnedDebugReadChat } : {}),
@@ -584,6 +918,8 @@ function buildChatDebugBundleSummary(bundle: Record<string, unknown>): Record<st
584
918
  adapterStatus: debugReadChat.adapterStatus,
585
919
  parsedStatus: debugReadChat.parsedStatus,
586
920
  returnedStatus: debugReadChat.returnedStatus,
921
+ selectedMessageSource: debugReadChat.selectedMessageSource,
922
+ messageSource: debugReadChat.messageSource,
587
923
  parsedMsgCount: debugReadChat.parsedMsgCount,
588
924
  returnedMsgCount: debugReadChat.returnedMsgCount,
589
925
  shouldPreferAdapterMessages: debugReadChat.shouldPreferAdapterMessages,
@@ -646,6 +982,8 @@ export async function handleGetChatDebugBundle(h: CommandHelpers, args: any): Pr
646
982
  providerSessionId: readResult.providerSessionId,
647
983
  transcriptAuthority: readResult.transcriptAuthority,
648
984
  coverage: readResult.coverage,
985
+ messageSource: readResult.messageSource,
986
+ transcriptProvenance: readResult.transcriptProvenance,
649
987
  activeModal: readResult.activeModal,
650
988
  messagesTail: Array.isArray(readResult.messages) ? readResult.messages.slice(-20) : [],
651
989
  debugReadChat: readResult.debugReadChat,
@@ -874,7 +1212,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
874
1212
  ? parsedRecord.coverage
875
1213
  : undefined;
876
1214
  const activeModal = parsedRecord.activeModal ?? parsedRecord.modal ?? null;
877
- const returnedStatus = normalizeCliReadChatStatus(parsedRecord.status, activeModal, adapter, adapterStatus);
1215
+ const returnedStatus = normalizeCliReadChatStatus(parsedRecord.status, activeModal, adapter, adapterStatus, parsedRecord.messages);
878
1216
  const runtimeMessageMerger = getTargetInstance(h, args) as RuntimeChatMessageMerger | null;
879
1217
  const parsedMessages = finalizeStreamingMessagesWhenIdle(parsedRecord.messages as ChatMessage[], returnedStatus);
880
1218
  const returnedMessages = runtimeMessageMerger?.category === 'cli'
@@ -882,25 +1220,166 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
882
1220
  && typeof runtimeMessageMerger.mergeRuntimeChatMessages === 'function'
883
1221
  ? runtimeMessageMerger.mergeRuntimeChatMessages(parsedMessages)
884
1222
  : parsedMessages;
1223
+ const providerType = provider?.type || adapter.cliType;
1224
+ let selectedMessages = returnedMessages;
1225
+ let selectedTitle = title;
1226
+ let selectedProviderSessionId = providerSessionId;
1227
+ let selectedTranscriptAuthority = transcriptAuthority;
1228
+ let selectedCoverage = coverage;
1229
+ let messageSource = buildCliMessageSourceProvenance({
1230
+ selected: 'pty-parser',
1231
+ provider: adapter.cliType,
1232
+ fallbackReason: supportsCliNativeTranscript(providerType, provider) ? 'native_history_not_checked' : 'provider_native_transcript_not_supported',
1233
+ ptyMessages: returnedMessages,
1234
+ returnedMessages,
1235
+ ptyStatusApprovalOnly: false,
1236
+ });
1237
+
1238
+ if (supportsCliNativeTranscript(providerType, provider) && isNativeSourceCanonicalHistory(provider?.canonicalHistory)) {
1239
+ const agentStr = provider?.type || args?.agentType || getCurrentProviderType(h, adapter.cliType);
1240
+ const workspace = typeof args?.workspace === 'string'
1241
+ ? args.workspace
1242
+ : typeof (h.currentSession as any)?.workspace === 'string'
1243
+ ? (h.currentSession as any).workspace
1244
+ : typeof adapter.workingDir === 'string'
1245
+ ? adapter.workingDir
1246
+ : undefined;
1247
+ const nativeHistoryLimit = Math.max(
1248
+ normalizeReadChatTailLimit(args) || 0,
1249
+ returnedMessages.length,
1250
+ 200,
1251
+ );
1252
+ const nativeHistorySessionId = resolveCliNativeHistorySessionId(args, historySessionId, providerSessionId);
1253
+ const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
1254
+ const exactNativeHistoryScope = Boolean(
1255
+ (typeof args?.historySessionId === 'string' && args.historySessionId.trim())
1256
+ || (typeof args?.providerSessionId === 'string' && args.providerSessionId.trim())
1257
+ || providerSessionId
1258
+ || (nativeHistorySessionId && nativeHistorySessionId !== targetSessionId)
1259
+ || ((h.currentSession as any)?.sessionId === args?.targetSessionId && typeof (h.currentSession as any)?.providerSessionId === 'string' && (h.currentSession as any).providerSessionId.trim())
1260
+ );
1261
+ let nativeHistory: (ReturnType<typeof readProviderChatHistory> & { lookup?: 'session' | 'workspace' }) | null = null;
1262
+ try {
1263
+ nativeHistory = readCliProviderNativeHistory(agentStr, {
1264
+ canonicalHistory: provider?.canonicalHistory,
1265
+ historySessionId: nativeHistorySessionId,
1266
+ workspace,
1267
+ offset: 0,
1268
+ limit: nativeHistoryLimit,
1269
+ excludeRecentCount: 0,
1270
+ historyBehavior: provider?.historyBehavior,
1271
+ scripts: provider?.scripts as any,
1272
+ exactSessionScoped: exactNativeHistoryScope,
1273
+ });
1274
+ } catch (error: any) {
1275
+ const fallbackReason = `native_history_error:${error?.message || String(error)}`;
1276
+ messageSource = buildCliMessageSourceProvenance({
1277
+ selected: 'pty-parser',
1278
+ provider: adapter.cliType,
1279
+ fallbackReason,
1280
+ ptyMessages: returnedMessages,
1281
+ returnedMessages,
1282
+ ptyStatusApprovalOnly: false,
1283
+ });
1284
+ nativeHistory = null;
1285
+ }
1286
+
1287
+ if (nativeHistory) {
1288
+ const nativeMessages = Array.isArray((nativeHistory as any).messages)
1289
+ ? normalizeNativeHistoryMessages(agentStr, (nativeHistory as any).messages as ChatMessage[])
1290
+ : [];
1291
+ const historyProviderSessionId = typeof (nativeHistory as any)?.providerSessionId === 'string'
1292
+ ? (nativeHistory as any).providerSessionId
1293
+ : readHistorySessionIdFromMessages(nativeMessages) || nativeHistorySessionId || historySessionId;
1294
+ const lookup = (nativeHistory as any).lookup === 'workspace' ? 'workspace' : 'session';
1295
+ const safeMapping = hasSafeNativeHistoryMapping({
1296
+ historySessionId: lookup === 'workspace' ? undefined : nativeHistorySessionId,
1297
+ providerSessionId: lookup === 'workspace' ? undefined : historyProviderSessionId || providerSessionId,
1298
+ workspace,
1299
+ nativeMessages,
1300
+ ptyMessages: returnedMessages,
1301
+ requireWorkspaceContentOverlap: lookup === 'workspace' && !exactNativeHistoryScope,
1302
+ });
1303
+ const freshEnough = isNativeHistoryFreshEnough({
1304
+ sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
1305
+ nativeMessages,
1306
+ ptyMessages: returnedMessages,
1307
+ });
1308
+ if ((nativeHistory as any).source === 'provider-native' && nativeMessages.length > 0 && safeMapping && freshEnough) {
1309
+ selectedMessages = finalizeStreamingMessagesWhenIdle(nativeMessages, returnedStatus);
1310
+ selectedProviderSessionId = historyProviderSessionId || providerSessionId;
1311
+ selectedTranscriptAuthority = 'provider';
1312
+ selectedCoverage = (nativeHistory as any).hasMore ? 'tail' : 'full';
1313
+ messageSource = buildCliMessageSourceProvenance({
1314
+ selected: 'native-history',
1315
+ provider: adapter.cliType,
1316
+ nativeHandle: selectedProviderSessionId || nativeHistorySessionId || historySessionId,
1317
+ nativeSource: (nativeHistory as any).source,
1318
+ sourcePath: (nativeHistory as any).sourcePath,
1319
+ sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
1320
+ nativeMessages,
1321
+ ptyMessages: returnedMessages,
1322
+ returnedMessages: selectedMessages,
1323
+ safeMapping,
1324
+ freshEnough,
1325
+ ptyStatusApprovalOnly: true,
1326
+ });
1327
+ } else {
1328
+ const fallbackReason = buildNativeHistoryFallbackReason({
1329
+ providerType,
1330
+ provider,
1331
+ nativeSource: (nativeHistory as any).source,
1332
+ nativeMessageCount: nativeMessages.length,
1333
+ safeMapping,
1334
+ freshEnough,
1335
+ });
1336
+ messageSource = buildCliMessageSourceProvenance({
1337
+ selected: 'pty-parser',
1338
+ provider: adapter.cliType,
1339
+ nativeHandle: historyProviderSessionId || nativeHistorySessionId || historySessionId,
1340
+ fallbackReason,
1341
+ nativeSource: (nativeHistory as any).source,
1342
+ sourcePath: (nativeHistory as any).sourcePath,
1343
+ sourceMtimeMs: (nativeHistory as any).sourceMtimeMs,
1344
+ nativeMessages,
1345
+ ptyMessages: returnedMessages,
1346
+ returnedMessages,
1347
+ safeMapping,
1348
+ freshEnough,
1349
+ ptyStatusApprovalOnly: false,
1350
+ });
1351
+ }
1352
+ }
1353
+ }
885
1354
  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
1355
  return buildReadChatCommandResult({
887
- messages: returnedMessages,
1356
+ messages: selectedMessages,
888
1357
  status: returnedStatus,
889
1358
  activeModal,
1359
+ messageSource,
1360
+ transcriptProvenance: messageSource,
890
1361
  debugReadChat: {
891
1362
  provider: adapter.cliType,
892
1363
  targetSessionId: String(args?.targetSessionId || ''),
893
1364
  adapterStatus: String(adapterStatus.status || ''),
894
1365
  parsedStatus: String(parsedRecord.status || ''),
895
1366
  returnedStatus: String(returnedStatus || ''),
896
- shouldPreferAdapterMessages: false,
1367
+ selectedMessageSource: (messageSource as any).selected,
1368
+ messageSource,
1369
+ shouldPreferAdapterMessages: supportsCliNativeTranscript(providerType, provider)
1370
+ && isNativeSourceCanonicalHistory(provider?.canonicalHistory)
1371
+ && (messageSource as any).selected !== 'native-history'
1372
+ && typeof (messageSource as any).fallbackReason === 'string'
1373
+ && (messageSource as any).fallbackReason.startsWith('native_history_')
1374
+ && (messageSource as any).fallbackReason !== 'native_history_not_checked'
1375
+ && !(selectedTranscriptAuthority === 'provider' && selectedCoverage === 'full'),
897
1376
  parsedMsgCount: parsedRecord.messages.length,
898
- returnedMsgCount: returnedMessages.length,
1377
+ returnedMsgCount: selectedMessages.length,
899
1378
  },
900
- ...(title ? { title } : {}),
901
- ...(providerSessionId ? { providerSessionId } : {}),
902
- ...(transcriptAuthority ? { transcriptAuthority } : {}),
903
- ...(coverage ? { coverage } : {}),
1379
+ ...(selectedTitle ? { title: selectedTitle } : {}),
1380
+ ...(selectedProviderSessionId ? { providerSessionId: selectedProviderSessionId } : {}),
1381
+ ...(selectedTranscriptAuthority ? { transcriptAuthority: selectedTranscriptAuthority } : {}),
1382
+ ...(selectedCoverage ? { coverage: selectedCoverage } : {}),
904
1383
  }, args);
905
1384
  }
906
1385
  const historyLimit = normalizeReadChatTailLimit(args);
@@ -911,7 +1390,25 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
911
1390
  : typeof (h.currentSession as any)?.workspace === 'string'
912
1391
  ? (h.currentSession as any).workspace
913
1392
  : undefined;
914
- const history = readProviderChatHistory(agentStr, {
1393
+ const exactNativeHistoryScope = Boolean(
1394
+ (typeof args?.targetSessionId === 'string' && args.targetSessionId.trim())
1395
+ || (typeof args?.historySessionId === 'string' && args.historySessionId.trim())
1396
+ || (typeof args?.providerSessionId === 'string' && args.providerSessionId.trim())
1397
+ || ((h.currentSession as any)?.sessionId === args?.targetSessionId && typeof (h.currentSession as any)?.providerSessionId === 'string' && (h.currentSession as any).providerSessionId.trim())
1398
+ );
1399
+ const history = supportsCliNativeTranscript(agentStr, provider) && isNativeSourceCanonicalHistory(provider?.canonicalHistory)
1400
+ ? readCliProviderNativeHistory(agentStr, {
1401
+ canonicalHistory: provider?.canonicalHistory,
1402
+ historySessionId,
1403
+ workspace,
1404
+ offset: 0,
1405
+ limit: historyLimit,
1406
+ excludeRecentCount: 0,
1407
+ historyBehavior: provider?.historyBehavior,
1408
+ scripts: provider?.scripts as any,
1409
+ exactSessionScoped: exactNativeHistoryScope,
1410
+ })
1411
+ : readProviderChatHistory(agentStr, {
915
1412
  canonicalHistory: provider?.canonicalHistory,
916
1413
  historySessionId,
917
1414
  workspace,
@@ -921,12 +1418,65 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
921
1418
  historyBehavior: provider?.historyBehavior,
922
1419
  scripts: provider?.scripts as any,
923
1420
  });
1421
+ const lookup = (history as any).lookup === 'workspace' ? 'workspace' : 'session';
1422
+ const historyMessages = Array.isArray((history as any)?.messages)
1423
+ ? normalizeNativeHistoryMessages(agentStr, (history as any).messages as ChatMessage[])
1424
+ : [];
924
1425
  const historyProviderSessionId = typeof (history as any)?.providerSessionId === 'string'
925
1426
  ? (history as any).providerSessionId
926
- : historySessionId;
1427
+ : readHistorySessionIdFromMessages(historyMessages) || historySessionId;
1428
+ const safeMapping = supportsCliNativeTranscript(agentStr, provider)
1429
+ ? hasSafeNativeHistoryMapping({
1430
+ historySessionId: lookup === 'workspace' ? undefined : historySessionId,
1431
+ providerSessionId: lookup === 'workspace' ? undefined : historyProviderSessionId,
1432
+ workspace,
1433
+ nativeMessages: historyMessages,
1434
+ })
1435
+ : false;
1436
+ const nativeSelected = supportsCliNativeTranscript(agentStr, provider)
1437
+ && (history as any).source === 'provider-native'
1438
+ && historyMessages.length > 0
1439
+ && safeMapping;
1440
+ const messageSource = buildCliMessageSourceProvenance({
1441
+ selected: nativeSelected ? 'native-history' : 'pty-parser',
1442
+ provider: agentStr,
1443
+ nativeHandle: historyProviderSessionId || historySessionId,
1444
+ fallbackReason: nativeSelected
1445
+ ? undefined
1446
+ : buildNativeHistoryFallbackReason({
1447
+ providerType: agentStr,
1448
+ provider,
1449
+ nativeSource: (history as any).source,
1450
+ nativeMessageCount: historyMessages.length,
1451
+ safeMapping,
1452
+ freshEnough: true,
1453
+ }),
1454
+ nativeSource: (history as any).source,
1455
+ sourcePath: (history as any).sourcePath,
1456
+ sourceMtimeMs: (history as any).sourceMtimeMs,
1457
+ nativeMessages: historyMessages,
1458
+ returnedMessages: historyMessages,
1459
+ safeMapping,
1460
+ freshEnough: true,
1461
+ ptyStatusApprovalOnly: false,
1462
+ });
1463
+ const requiresNativeSource = supportsCliNativeTranscript(agentStr, provider)
1464
+ && isNativeSourceCanonicalHistory(provider?.canonicalHistory);
1465
+ if (requiresNativeSource && !nativeSelected) {
1466
+ return {
1467
+ success: false,
1468
+ code: 'native_history_not_safely_available',
1469
+ error: 'Provider-native history was not safely available for the requested CLI session.',
1470
+ providerSessionId: historyProviderSessionId,
1471
+ messageSource,
1472
+ transcriptProvenance: messageSource,
1473
+ };
1474
+ }
927
1475
  return buildReadChatCommandResult({
928
- messages: Array.isArray((history as any)?.messages) ? (history as any).messages : [],
1476
+ messages: historyMessages,
929
1477
  status: 'idle',
1478
+ messageSource,
1479
+ transcriptProvenance: messageSource,
930
1480
  ...(typeof (history as any)?.title === 'string' ? { title: (history as any).title } : {}),
931
1481
  ...(historyProviderSessionId ? { providerSessionId: historyProviderSessionId } : {}),
932
1482
  ...(((provider?.historyBehavior as any)?.transcriptAuthority === 'provider' || (provider?.historyBehavior as any)?.transcriptAuthority === 'daemon')