@adhdev/daemon-core 0.9.82-rc.136 → 0.9.82-rc.138
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.
- package/dist/chat/source-machine.d.ts +166 -0
- package/dist/chat/source-resolver.d.ts +104 -0
- package/dist/cli-adapters/cli-script-runner.d.ts +45 -0
- package/dist/cli-adapters/cli-state-engine.d.ts +169 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +72 -74
- package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +5 -0
- package/dist/config/chat-history.d.ts +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3507 -2288
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3515 -2301
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/beads-db.d.ts +54 -0
- package/dist/mesh/contracts.d.ts +164 -0
- package/dist/mesh/mesh-active-work.d.ts +7 -1
- package/dist/mesh/mesh-events.d.ts +10 -4
- package/dist/mesh/mesh-ledger.d.ts +21 -1
- package/dist/mesh/mesh-refine-status.d.ts +2 -3
- package/dist/mesh/mesh-work-queue.d.ts +17 -0
- package/dist/mesh/worktree-bootstrap-config.d.ts +2 -4
- package/dist/providers/contracts.d.ts +19 -0
- package/dist/providers/read-chat-contract.d.ts +29 -0
- package/dist/providers/transcript-v2.d.ts +176 -0
- package/dist/repo-mesh-types.d.ts +5 -0
- package/dist/shared-types.d.ts +7 -0
- package/dist/status/snapshot.d.ts +1 -0
- package/dist/types.d.ts +5 -0
- package/package.json +1 -1
- package/src/chat/source-machine.ts +534 -0
- package/src/chat/source-resolver.ts +0 -0
- package/src/chat/subscription-updates.ts +9 -0
- package/src/cli-adapters/cli-script-runner.ts +145 -0
- package/src/cli-adapters/cli-state-engine.ts +1054 -0
- package/src/cli-adapters/provider-cli-adapter.d.ts +0 -1
- package/src/cli-adapters/provider-cli-adapter.ts +413 -1399
- package/src/cli-adapters/provider-cli-parse.ts +3 -0
- package/src/cli-adapters/provider-cli-shared.ts +17 -1
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +17 -1
- package/src/cli-adapters/terminal-backends/xterm-backend.ts +8 -1
- package/src/commands/chat-commands.ts +715 -368
- package/src/commands/router.ts +22 -2
- package/src/config/chat-history.ts +43 -16
- package/src/git/git-worktree.ts +8 -1
- package/src/index.ts +3 -2
- package/src/mesh/beads-db.ts +305 -2
- package/src/mesh/contracts.ts +329 -0
- package/src/mesh/coordinator-prompt.ts +12 -17
- package/src/mesh/mesh-active-work.ts +162 -59
- package/src/mesh/mesh-events.ts +198 -53
- package/src/mesh/mesh-ledger.ts +321 -105
- package/src/mesh/mesh-refine-status.ts +2 -3
- package/src/mesh/mesh-work-queue.ts +116 -120
- package/src/mesh/worktree-bootstrap-config.ts +17 -4
- package/src/providers/contracts.ts +19 -0
- package/src/providers/provider-loader.ts +21 -7
- package/src/providers/provider-schema.ts +12 -0
- package/src/providers/read-chat-contract.ts +74 -14
- package/src/providers/transcript-v2.ts +567 -0
- package/src/repo-mesh-types.ts +10 -0
- package/src/shared-types.ts +7 -0
- package/src/status/snapshot.ts +35 -11
- package/src/types.ts +5 -0
|
@@ -18,6 +18,14 @@ import { isNativeSourceCanonicalHistory, readChatHistory, readProviderChatHistor
|
|
|
18
18
|
import { LOG, getRecentLogs } from '../logging/logger.js';
|
|
19
19
|
import { getRecentDebugTrace, recordDebugTrace } from '../logging/debug-trace.js';
|
|
20
20
|
import { buildChatMessageSignature, hashSignatureParts } from '../chat/chat-signatures.js';
|
|
21
|
+
import {
|
|
22
|
+
CHAT_SOURCE_REGISTRY,
|
|
23
|
+
buildV1NativePresentObservation,
|
|
24
|
+
chatSourceSessionKey,
|
|
25
|
+
type ChatSourceDecision,
|
|
26
|
+
type ChatSourceObservation,
|
|
27
|
+
type ChatSourceTransitionCause,
|
|
28
|
+
} from '../chat/source-resolver.js';
|
|
21
29
|
import type { ChatMessage } from '../types.js';
|
|
22
30
|
import type { SessionTransport } from '../shared-types.js';
|
|
23
31
|
import { filterUserFacingChatMessages, normalizeChatMessages } from '../providers/chat-message-normalization.js';
|
|
@@ -25,13 +33,25 @@ import { filterUserFacingChatMessages, normalizeChatMessages } from '../provider
|
|
|
25
33
|
const RECENT_SEND_WINDOW_MS = 1200;
|
|
26
34
|
export const READ_CHAT_PROVIDER_EVAL_TIMEOUT_MS = 25_000;
|
|
27
35
|
const HERMES_CLI_STARTING_SEND_SETTLE_MS = 2_000;
|
|
28
|
-
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
36
|
+
// (A2.2) CLI_NATIVE_HISTORY_FRESH_MS removed with isNativeHistoryFreshEnough.
|
|
37
|
+
// Hardcoded native-transcript provider allow-list. Deprecated. Kept only as a
|
|
38
|
+
// last-resort fallback when ProviderModule is not yet loaded; on every hit we
|
|
39
|
+
// warn so the dependency on this set is visible. A2 deletes the set entirely
|
|
40
|
+
// and routes solely through canonicalHistory.contractVersion +
|
|
41
|
+
// isNativeSourceCanonicalHistory().
|
|
34
42
|
const CLI_NATIVE_TRANSCRIPT_PROVIDERS = new Set(['codex-cli', 'claude-cli', 'hermes-cli', 'antigravity-cli']);
|
|
43
|
+
const warnedLegacyNativeAllowlistHits = new Set<string>();
|
|
44
|
+
function warnLegacyNativeAllowlistHit(providerType: string): void {
|
|
45
|
+
if (warnedLegacyNativeAllowlistHits.has(providerType)) return;
|
|
46
|
+
warnedLegacyNativeAllowlistHits.add(providerType);
|
|
47
|
+
// eslint-disable-next-line no-console
|
|
48
|
+
console.warn(
|
|
49
|
+
`[chat-commands] supportsCliNativeTranscript fell back to the hardcoded `
|
|
50
|
+
+ `CLI_NATIVE_TRANSCRIPT_PROVIDERS set for "${providerType}". `
|
|
51
|
+
+ `The provider module was unavailable or did not declare canonicalHistory. `
|
|
52
|
+
+ `Set canonicalHistory.contractVersion in the provider.json to remove this dependency.`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
35
55
|
const recentSendByTarget = new Map<string, number>();
|
|
36
56
|
|
|
37
57
|
interface ApprovalSelectableInstance extends ProviderInstance {
|
|
@@ -288,7 +308,15 @@ function readHistorySessionIdFromMessages(messages: ChatMessage[]): string | und
|
|
|
288
308
|
function shouldPreserveNativeIdentity(providerType: string, sessionId: string, message: ChatMessage): boolean {
|
|
289
309
|
const providerUnitKey = typeof message.providerUnitKey === 'string' ? message.providerUnitKey.trim() : '';
|
|
290
310
|
const turnKey = typeof message._turnKey === 'string' ? message._turnKey.trim() : '';
|
|
291
|
-
if (!providerUnitKey
|
|
311
|
+
if (!providerUnitKey) return false;
|
|
312
|
+
// (A2.3) v2 stamped identity is producer-owned and globally stable; trust it
|
|
313
|
+
// unconditionally. Producers may omit _turnKey (the daemon recomputes it
|
|
314
|
+
// from the current ordering), so do not require turnKey for v2 messages.
|
|
315
|
+
if (providerUnitKey.startsWith('v2:') || providerUnitKey.startsWith('v2-pty:')) {
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
// v1 identity always required both keys to be present.
|
|
319
|
+
if (!turnKey) return false;
|
|
292
320
|
if (providerType === 'hermes-cli' && sessionId) {
|
|
293
321
|
return providerUnitKey.startsWith(`${providerType}:native:${sessionId}:`)
|
|
294
322
|
&& turnKey.startsWith(`${providerType}:native-turn:${sessionId}:`);
|
|
@@ -323,6 +351,18 @@ function normalizeNativeHistoryMessages(providerType: string, messages: ChatMess
|
|
|
323
351
|
const meta = message.meta && typeof message.meta === 'object' ? message.meta as Record<string, unknown> : undefined;
|
|
324
352
|
const isSystemSessionStart = role === 'system' || kind === 'system' || kind === 'session_start';
|
|
325
353
|
const isActivity = role === 'assistant' && (kind === 'tool' || kind === 'terminal' || kind === 'thought');
|
|
354
|
+
// (A2.3) sequence emit. Producer-supplied wins (v2-stamped messages
|
|
355
|
+
// bring their own monotonic sequence); otherwise derive from
|
|
356
|
+
// receivedAt/timestamp; otherwise positional. Always present on the
|
|
357
|
+
// output so consumers (ChatSourceMachine) have a stable ordering key.
|
|
358
|
+
const existingSequence = typeof (message as any).sequence === 'number'
|
|
359
|
+
&& Number.isFinite((message as any).sequence)
|
|
360
|
+
? (message as any).sequence
|
|
361
|
+
: null;
|
|
362
|
+
const tsCandidate = Number(message.receivedAt || message.timestamp || 0);
|
|
363
|
+
const sequence = existingSequence !== null
|
|
364
|
+
? existingSequence
|
|
365
|
+
: (tsCandidate > 0 ? tsCandidate : index);
|
|
326
366
|
return {
|
|
327
367
|
...message,
|
|
328
368
|
role: role === 'human' ? 'user' : (role || 'assistant'),
|
|
@@ -332,6 +372,7 @@ function normalizeNativeHistoryMessages(providerType: string, messages: ChatMess
|
|
|
332
372
|
&& preserveNativeIdentity
|
|
333
373
|
? message.bubbleId.trim()
|
|
334
374
|
: `bubble:${providerUnitKey}`,
|
|
375
|
+
sequence,
|
|
335
376
|
_turnKey: preserveNativeIdentity
|
|
336
377
|
? existingTurnKey
|
|
337
378
|
: `${providerType}:native-turn:${nativeIdentitySessionId || 'workspace'}:${turnIndex}`,
|
|
@@ -420,28 +461,336 @@ function buildCliMessageSourceProvenance(args: {
|
|
|
420
461
|
};
|
|
421
462
|
}
|
|
422
463
|
|
|
423
|
-
|
|
464
|
+
/**
|
|
465
|
+
* Map a ChatSourceMachine transition cause back to the v1 messageSource
|
|
466
|
+
* `fallbackReason` vocabulary so legacy consumers (web-cloud, tests, mesh
|
|
467
|
+
* debug bundles) keep parsing strings they already know. A3 replaces the
|
|
468
|
+
* caller surface with stateTransition/lockState, after which this map can be
|
|
469
|
+
* deleted.
|
|
470
|
+
*
|
|
471
|
+
* Returns undefined when the cause does not correspond to a fallback (i.e.
|
|
472
|
+
* the source is native-history and there is nothing to explain).
|
|
473
|
+
*/
|
|
474
|
+
function causeToLegacyFallbackReason(
|
|
475
|
+
cause: ChatSourceTransitionCause,
|
|
476
|
+
selected: 'native-history' | 'pty-parser',
|
|
477
|
+
extraDetail?: { unavailableReason?: string; nativeSource?: string },
|
|
478
|
+
): string | undefined {
|
|
479
|
+
if (selected === 'native-history') return undefined;
|
|
480
|
+
switch (cause) {
|
|
481
|
+
case 'initial':
|
|
482
|
+
return 'native_history_not_checked';
|
|
483
|
+
case 'native_progressed':
|
|
484
|
+
// Selected pty-parser despite a progressed observation — that
|
|
485
|
+
// means we held PtyOnly stickily (peak unmet or non-superset).
|
|
486
|
+
return 'native_history_not_selected';
|
|
487
|
+
case 'native_regressed_shrunk':
|
|
488
|
+
return 'native_history_empty';
|
|
489
|
+
case 'native_regressed_unsafe_mapping':
|
|
490
|
+
return 'native_history_not_safely_mapped';
|
|
491
|
+
case 'native_regressed_coverage_partial':
|
|
492
|
+
return 'native_history_partial';
|
|
493
|
+
case 'native_regressed_coverage_unavailable':
|
|
494
|
+
return 'native_history_unavailable';
|
|
495
|
+
case 'native_unavailable_read_error':
|
|
496
|
+
return extraDetail?.unavailableReason
|
|
497
|
+
? `native_history_unavailable:${extraDetail.unavailableReason}`
|
|
498
|
+
: 'native_history_unavailable';
|
|
499
|
+
case 'native_unavailable_provider_unsupported':
|
|
500
|
+
return 'provider_native_transcript_not_supported';
|
|
501
|
+
case 'native_unavailable_empty':
|
|
502
|
+
return 'native_history_empty';
|
|
503
|
+
case 'native_unavailable_not_native_source':
|
|
504
|
+
return extraDetail?.nativeSource
|
|
505
|
+
? `native_history_source_${extraDetail.nativeSource}`
|
|
506
|
+
: 'native_history_unavailable';
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Translate a native-history fetch result + provider/adapter context into a
|
|
512
|
+
* ChatSourceObservation and drive ChatSourceRegistry. Returns the decision
|
|
513
|
+
* together with the legacy messageSource payload so call sites can produce
|
|
514
|
+
* a v1-compatible response without duplicating the registry plumbing.
|
|
515
|
+
*
|
|
516
|
+
* This is the replacement for the 300-line if-ladder that previously lived
|
|
517
|
+
* inline in handleReadChat. It is intentionally split out for two reasons:
|
|
518
|
+
* (1) we will call it from two places (CLI adapter branch + history-only
|
|
519
|
+
* branch) instead of duplicating the ladder, (2) tests can drive it with
|
|
520
|
+
* synthetic native-history results to verify the cause→fallbackReason
|
|
521
|
+
* mapping without booting the whole readChat pipeline.
|
|
522
|
+
*/
|
|
523
|
+
function decideCliReadChatSource(args: {
|
|
424
524
|
providerType: string;
|
|
425
525
|
provider?: ProviderModule;
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
nativeMessageCount: number;
|
|
526
|
+
sessionId: string;
|
|
527
|
+
nativeHistoryResult: any | null;
|
|
528
|
+
nativeHistoryError?: unknown;
|
|
430
529
|
safeMapping: boolean;
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
530
|
+
sessionWorkspace?: string;
|
|
531
|
+
intendedWorkspace?: string;
|
|
532
|
+
ptyMessages: ChatMessage[];
|
|
533
|
+
ptyStatusApprovalOnly: boolean;
|
|
534
|
+
}): {
|
|
535
|
+
decision: ChatSourceDecision;
|
|
536
|
+
messageSource: Record<string, unknown>;
|
|
537
|
+
nativeMessages: ChatMessage[];
|
|
538
|
+
nativeSelected: boolean;
|
|
539
|
+
} {
|
|
540
|
+
const supportsNative = supportsCliNativeTranscript(args.providerType, args.provider);
|
|
541
|
+
const observation = buildObservationForCli(args, supportsNative);
|
|
542
|
+
const sessionKey = chatSourceSessionKey(args.providerType, args.sessionId);
|
|
543
|
+
const decision = CHAT_SOURCE_REGISTRY.observe(sessionKey, observation);
|
|
544
|
+
|
|
545
|
+
const nativeMessages: ChatMessage[] = observation.kind === 'native_present'
|
|
546
|
+
? extractNativeMessagesFromResult(args.providerType, args.nativeHistoryResult)
|
|
547
|
+
: [];
|
|
548
|
+
|
|
549
|
+
const nativeSource = typeof args.nativeHistoryResult?.source === 'string'
|
|
550
|
+
? args.nativeHistoryResult.source
|
|
551
|
+
: undefined;
|
|
552
|
+
const sourcePath = typeof args.nativeHistoryResult?.sourcePath === 'string'
|
|
553
|
+
? args.nativeHistoryResult.sourcePath
|
|
554
|
+
: undefined;
|
|
555
|
+
const sourceMtimeMs = typeof args.nativeHistoryResult?.sourceMtimeMs === 'number'
|
|
556
|
+
? args.nativeHistoryResult.sourceMtimeMs
|
|
557
|
+
: undefined;
|
|
558
|
+
const coverageHint = typeof args.nativeHistoryResult?.nativeHistoryCoverage === 'string'
|
|
559
|
+
? args.nativeHistoryResult.nativeHistoryCoverage
|
|
560
|
+
: undefined;
|
|
561
|
+
const partialReason = typeof args.nativeHistoryResult?.partialReason === 'string'
|
|
562
|
+
? args.nativeHistoryResult.partialReason
|
|
563
|
+
: undefined;
|
|
564
|
+
const unavailableReason = typeof args.nativeHistoryResult?.unavailableReason === 'string'
|
|
565
|
+
? args.nativeHistoryResult.unavailableReason
|
|
566
|
+
: args.nativeHistoryError
|
|
567
|
+
? `error:${(args.nativeHistoryError as any)?.message || String(args.nativeHistoryError)}`
|
|
568
|
+
: undefined;
|
|
569
|
+
const nativeHandle = typeof args.nativeHistoryResult?.providerSessionId === 'string'
|
|
570
|
+
? args.nativeHistoryResult.providerSessionId
|
|
571
|
+
: undefined;
|
|
572
|
+
const transcriptWorkspace = typeof args.nativeHistoryResult?.workspace === 'string'
|
|
573
|
+
? args.nativeHistoryResult.workspace
|
|
574
|
+
: nativeMessages.map((m: any) => typeof m?.workspace === 'string' ? m.workspace.trim() : '').find(Boolean);
|
|
575
|
+
|
|
576
|
+
const fallbackReason = causeToLegacyFallbackReason(decision.transition.cause, decision.selected, {
|
|
577
|
+
unavailableReason,
|
|
578
|
+
nativeSource: nativeSource && nativeSource !== 'provider-native' ? nativeSource : undefined,
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
// ptyStatusApprovalOnly: when the machine selected native-history we
|
|
582
|
+
// suppress PTY content so the dashboard does not double-show messages
|
|
583
|
+
// already in the native transcript. When the machine selected
|
|
584
|
+
// pty-parser, PTY is the authoritative source — do NOT suppress it.
|
|
585
|
+
// Callers used to hard-code this to `nativeSelected first = true` which
|
|
586
|
+
// suppressed PTY content even when native was empty/unavailable, leaving
|
|
587
|
+
// the dashboard with zero visible messages (the codex generating/waiting
|
|
588
|
+
// approval stuck state). Trust the machine here, not the caller hint.
|
|
589
|
+
const ptyStatusApprovalOnly = decision.selected === 'native-history'
|
|
590
|
+
? true
|
|
591
|
+
: args.ptyStatusApprovalOnly;
|
|
592
|
+
|
|
593
|
+
const messageSource = buildCliMessageSourceProvenance({
|
|
594
|
+
selected: decision.selected,
|
|
595
|
+
provider: args.providerType,
|
|
596
|
+
nativeHandle,
|
|
597
|
+
sessionWorkspace: args.sessionWorkspace,
|
|
598
|
+
intendedWorkspace: args.intendedWorkspace,
|
|
599
|
+
transcriptWorkspace,
|
|
600
|
+
fallbackReason,
|
|
601
|
+
nativeSource,
|
|
602
|
+
sourcePath,
|
|
603
|
+
sourceMtimeMs,
|
|
604
|
+
nativeHistoryCoverage: coverageHint,
|
|
605
|
+
partialReason,
|
|
606
|
+
unavailableReason,
|
|
607
|
+
nativeMessages,
|
|
608
|
+
ptyMessages: args.ptyMessages,
|
|
609
|
+
returnedMessages: decision.selected === 'native-history' ? nativeMessages : args.ptyMessages,
|
|
610
|
+
safeMapping: args.safeMapping,
|
|
611
|
+
// freshEnough is a v1 concept the machine does not model directly.
|
|
612
|
+
// We surface lockState.locked here so v1 consumers reading
|
|
613
|
+
// staleness.freshEnough still get a meaningful boolean.
|
|
614
|
+
freshEnough: decision.lockState.locked,
|
|
615
|
+
ptyStatusApprovalOnly,
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
return {
|
|
619
|
+
decision,
|
|
620
|
+
messageSource,
|
|
621
|
+
nativeMessages,
|
|
622
|
+
nativeSelected: decision.selected === 'native-history',
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function buildObservationForCli(
|
|
627
|
+
args: {
|
|
628
|
+
providerType: string;
|
|
629
|
+
sessionId: string;
|
|
630
|
+
nativeHistoryResult: any | null;
|
|
631
|
+
nativeHistoryError?: unknown;
|
|
632
|
+
safeMapping: boolean;
|
|
633
|
+
},
|
|
634
|
+
supportsNative: boolean,
|
|
635
|
+
): ChatSourceObservation {
|
|
636
|
+
if (!supportsNative) {
|
|
637
|
+
return { kind: 'native_unavailable', reason: 'provider_not_supported' };
|
|
638
|
+
}
|
|
639
|
+
if (args.nativeHistoryError) {
|
|
640
|
+
return { kind: 'native_unavailable', reason: 'read_error' };
|
|
641
|
+
}
|
|
642
|
+
const result = args.nativeHistoryResult;
|
|
643
|
+
if (!result || typeof result !== 'object') {
|
|
644
|
+
return { kind: 'native_unavailable', reason: 'read_error' };
|
|
645
|
+
}
|
|
646
|
+
const source = typeof result.source === 'string' ? result.source : '';
|
|
647
|
+
if (source && source !== 'provider-native') {
|
|
648
|
+
// 'native-unavailable' or other producer-side declined source.
|
|
649
|
+
return { kind: 'native_unavailable', reason: source === 'native-unavailable' ? 'empty' : 'not_native_source' };
|
|
650
|
+
}
|
|
651
|
+
const messages = Array.isArray(result.messages) ? result.messages : [];
|
|
652
|
+
if (messages.length === 0) {
|
|
653
|
+
return { kind: 'native_unavailable', reason: 'empty' };
|
|
654
|
+
}
|
|
655
|
+
const coverage = typeof result.nativeHistoryCoverage === 'string'
|
|
656
|
+
? result.nativeHistoryCoverage
|
|
657
|
+
: 'tail';
|
|
658
|
+
if (coverage === 'unavailable') {
|
|
659
|
+
return { kind: 'native_unavailable', reason: 'coverage_unavailable' };
|
|
660
|
+
}
|
|
661
|
+
return buildV1NativePresentObservation({
|
|
662
|
+
providerType: args.providerType,
|
|
663
|
+
sessionId: args.sessionId,
|
|
664
|
+
messages,
|
|
665
|
+
coverage: coverage === 'full' || coverage === 'tail' || coverage === 'current-turn' || coverage === 'partial'
|
|
666
|
+
? coverage
|
|
667
|
+
: 'tail',
|
|
668
|
+
safeMapping: args.safeMapping,
|
|
669
|
+
});
|
|
443
670
|
}
|
|
444
671
|
|
|
672
|
+
function extractNativeMessagesFromResult(providerType: string, result: any): ChatMessage[] {
|
|
673
|
+
if (!result || !Array.isArray(result.messages)) return [];
|
|
674
|
+
return normalizeNativeHistoryMessages(
|
|
675
|
+
providerType,
|
|
676
|
+
result.messages as ChatMessage[],
|
|
677
|
+
typeof result.providerSessionId === 'string' ? result.providerSessionId : undefined,
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* ptyStatusApprovalOnly is true when the daemon should treat PTY content as
|
|
683
|
+
* status/approval signal only (not as chat messages). v1 set this to `true`
|
|
684
|
+
* whenever native-history was selected as the source, and `false` otherwise.
|
|
685
|
+
* The machine equivalent: when native is the source we want PTY suppressed.
|
|
686
|
+
*/
|
|
687
|
+
function primaryPtyApprovalOnlyFor(_cliType: string, nativeSelected: boolean): boolean {
|
|
688
|
+
return nativeSelected;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Codex-only unsafe-native fallback: when the primary native fetch produced
|
|
693
|
+
* unsafe-mapping data, v1 attempted to recover by reading exact runtime
|
|
694
|
+
* mirror messages, runtime input ACK messages, or by trusting the current-
|
|
695
|
+
* runtime PTY when safely attributed. None of this is the machine's
|
|
696
|
+
* responsibility — the machine already decided pty-parser. This helper
|
|
697
|
+
* preserves the daemon-side message selection and annotates messageSource.
|
|
698
|
+
*/
|
|
699
|
+
function applyUnsafeNativeDaemonFallback(args: {
|
|
700
|
+
providerType: string;
|
|
701
|
+
adapter: CliAdapter;
|
|
702
|
+
helpers: CommandHelpers;
|
|
703
|
+
readChatArgs: any;
|
|
704
|
+
sessionWorkspace?: string;
|
|
705
|
+
intendedWorkspace?: string;
|
|
706
|
+
ptyMessages: ChatMessage[];
|
|
707
|
+
nativeHistoryLimit: number;
|
|
708
|
+
provider?: ProviderModule;
|
|
709
|
+
messageSourceRef: { set(value: Record<string, unknown>): void; get(): Record<string, unknown> };
|
|
710
|
+
apply(selection: {
|
|
711
|
+
messages: ChatMessage[];
|
|
712
|
+
transcriptAuthority?: 'provider' | 'daemon';
|
|
713
|
+
coverage?: 'full' | 'tail' | 'current-turn';
|
|
714
|
+
status?: string;
|
|
715
|
+
}): void;
|
|
716
|
+
activeModal: unknown;
|
|
717
|
+
returnedStatus: string;
|
|
718
|
+
coverage?: 'full' | 'tail' | 'current-turn';
|
|
719
|
+
}): void {
|
|
720
|
+
if (args.adapter.cliType !== 'codex-cli') {
|
|
721
|
+
// Only codex-cli had v1 daemon mirror recovery. Other providers skip.
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
const ms = args.messageSourceRef.get();
|
|
725
|
+
const fallbackReason = typeof ms.fallbackReason === 'string' ? ms.fallbackReason : '';
|
|
726
|
+
if (!isUnsafeNativeTranscriptFallback(fallbackReason)) {
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
const safeCurrentRuntimePtyMessages = isCurrentRuntimePtySafelyAttributed({
|
|
730
|
+
adapter: args.adapter,
|
|
731
|
+
helpers: args.helpers,
|
|
732
|
+
readChatArgs: args.readChatArgs,
|
|
733
|
+
sessionWorkspace: args.sessionWorkspace,
|
|
734
|
+
intendedWorkspace: args.intendedWorkspace,
|
|
735
|
+
ptyMessages: args.ptyMessages,
|
|
736
|
+
});
|
|
737
|
+
if (safeCurrentRuntimePtyMessages) {
|
|
738
|
+
args.apply({
|
|
739
|
+
messages: args.ptyMessages,
|
|
740
|
+
transcriptAuthority: 'daemon',
|
|
741
|
+
coverage: args.coverage || 'current-turn',
|
|
742
|
+
status: args.returnedStatus,
|
|
743
|
+
});
|
|
744
|
+
const next = { ...ms, selectedDaemonSource: 'current-runtime-pty', transcriptAuthority: 'daemon', runtimeMappingSafe: true };
|
|
745
|
+
args.messageSourceRef.set(next);
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
const safeRuntimeAckMessages = selectRuntimeInputAckMessages(args.ptyMessages);
|
|
749
|
+
if (safeRuntimeAckMessages.length > 0) {
|
|
750
|
+
args.apply({
|
|
751
|
+
messages: safeRuntimeAckMessages,
|
|
752
|
+
transcriptAuthority: 'daemon',
|
|
753
|
+
coverage: 'tail',
|
|
754
|
+
status: coerceUnsafeNativeFallbackStatus(args.returnedStatus, args.activeModal),
|
|
755
|
+
});
|
|
756
|
+
const next = { ...ms, ptyStatusApprovalOnly: true };
|
|
757
|
+
args.messageSourceRef.set(next);
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
const exactRuntimeMirrorMessages = readExactRuntimeMirrorMessages({
|
|
761
|
+
providerType: args.providerType,
|
|
762
|
+
targetSessionId: typeof args.readChatArgs?.targetSessionId === 'string' ? args.readChatArgs.targetSessionId : undefined,
|
|
763
|
+
currentSessionId: typeof (args.helpers.currentSession as any)?.sessionId === 'string' ? (args.helpers.currentSession as any).sessionId : undefined,
|
|
764
|
+
tailLimit: args.nativeHistoryLimit,
|
|
765
|
+
historyBehavior: args.provider?.historyBehavior,
|
|
766
|
+
});
|
|
767
|
+
if (exactRuntimeMirrorMessages.length > 0) {
|
|
768
|
+
args.apply({
|
|
769
|
+
messages: exactRuntimeMirrorMessages,
|
|
770
|
+
transcriptAuthority: 'daemon',
|
|
771
|
+
coverage: 'tail',
|
|
772
|
+
status: coerceUnsafeNativeFallbackStatus(args.returnedStatus, args.activeModal),
|
|
773
|
+
});
|
|
774
|
+
const next = { ...ms, selectedDaemonSource: 'exact-runtime-mirror', transcriptAuthority: 'daemon', ptyStatusApprovalOnly: true };
|
|
775
|
+
args.messageSourceRef.set(next);
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
// No daemon mirror available — keep PTY messages as-is (still pty-parser
|
|
779
|
+
// selection); just coerce status for waiting_approval consistency.
|
|
780
|
+
args.apply({
|
|
781
|
+
messages: args.ptyMessages,
|
|
782
|
+
coverage: args.coverage,
|
|
783
|
+
status: coerceUnsafeNativeFallbackStatus(args.returnedStatus, args.activeModal),
|
|
784
|
+
});
|
|
785
|
+
const next = { ...ms, ptyStatusApprovalOnly: true };
|
|
786
|
+
args.messageSourceRef.set(next);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// (A2.2) buildNativeHistoryFallbackReason removed. ChatSourceMachine emits a
|
|
790
|
+
// ChatSourceTransitionCause; causeToLegacyFallbackReason maps it back to the
|
|
791
|
+
// v1 vocabulary for response compatibility. A3 deletes the v1 vocabulary
|
|
792
|
+
// entirely and surfaces stateTransition/lockState directly.
|
|
793
|
+
|
|
445
794
|
function isUnsafeNativeTranscriptFallback(reason?: string): boolean {
|
|
446
795
|
const value = String(reason || '').trim();
|
|
447
796
|
return value.startsWith('native_history_unavailable')
|
|
@@ -551,8 +900,21 @@ function isCurrentRuntimePtySafelyAttributed(args: {
|
|
|
551
900
|
}
|
|
552
901
|
|
|
553
902
|
function supportsCliNativeTranscript(providerType: string, provider?: ProviderModule): boolean {
|
|
554
|
-
|
|
555
|
-
|
|
903
|
+
// Preferred path: the provider module declares canonicalHistory in its
|
|
904
|
+
// provider.json. We trust that declaration regardless of the legacy
|
|
905
|
+
// allow-list. A2 will additionally require canonicalHistory.contractVersion
|
|
906
|
+
// to be a supported value (transcript-v2.ts).
|
|
907
|
+
if (provider?.category === 'cli' && isNativeSourceCanonicalHistory(provider?.canonicalHistory)) {
|
|
908
|
+
return true;
|
|
909
|
+
}
|
|
910
|
+
// Last-resort fallback for early call sites where the provider module is
|
|
911
|
+
// not yet loaded. Warn once per provider type so this dependency is visible
|
|
912
|
+
// and can be removed in A2.
|
|
913
|
+
if (CLI_NATIVE_TRANSCRIPT_PROVIDERS.has(providerType)) {
|
|
914
|
+
warnLegacyNativeAllowlistHit(providerType);
|
|
915
|
+
return true;
|
|
916
|
+
}
|
|
917
|
+
return false;
|
|
556
918
|
}
|
|
557
919
|
|
|
558
920
|
function getComparableVisibleText(message: ChatMessage | undefined): string {
|
|
@@ -650,6 +1012,7 @@ function readCliProviderNativeHistory(agentStr: string, args: {
|
|
|
650
1012
|
excludeRecentCount: number;
|
|
651
1013
|
historyBehavior?: ProviderModule['historyBehavior'];
|
|
652
1014
|
scripts?: ProviderScripts;
|
|
1015
|
+
excludeInProgressTurn?: boolean;
|
|
653
1016
|
}): ReturnType<typeof readProviderChatHistory> & { lookup: 'session' | 'workspace' } {
|
|
654
1017
|
if (!args.historySessionId) {
|
|
655
1018
|
return {
|
|
@@ -669,6 +1032,7 @@ function readCliProviderNativeHistory(agentStr: string, args: {
|
|
|
669
1032
|
excludeRecentCount: args.excludeRecentCount,
|
|
670
1033
|
historyBehavior: args.historyBehavior,
|
|
671
1034
|
scripts: args.scripts as any,
|
|
1035
|
+
excludeInProgressTurn: args.excludeInProgressTurn,
|
|
672
1036
|
});
|
|
673
1037
|
// Native transcripts are keyed by provider/runtime session identity. Falling
|
|
674
1038
|
// back to workspace makes concurrent local Codex/Hermes sessions alias each
|
|
@@ -700,18 +1064,12 @@ function readLiveCodexWorkspaceNativeHistory(agentStr: string, args: {
|
|
|
700
1064
|
return { ...(history as any), lookup: 'workspace' };
|
|
701
1065
|
}
|
|
702
1066
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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
|
-
}
|
|
1067
|
+
// (A2.2) isNativeHistoryFreshEnough removed. The v1 freshness comparison
|
|
1068
|
+
// (native_newest vs pty_newest with a 5-minute mtime grace window) was the
|
|
1069
|
+
// direct cause of the plipping behaviour: PTY arrived every turn so native
|
|
1070
|
+
// looked stale by default. ChatSourceMachine never compares native vs PTY
|
|
1071
|
+
// freshness — the lock holds across arbitrary PTY arrival. See
|
|
1072
|
+
// chat/source-machine.ts for the new semantics.
|
|
715
1073
|
|
|
716
1074
|
function shouldPreserveReadChatPayloadField(key: string): boolean {
|
|
717
1075
|
return key === 'messageSource' || key === 'transcriptProvenance';
|
|
@@ -797,6 +1155,15 @@ function normalizeReadChatCommandStatus(status: unknown, activeModal: unknown):
|
|
|
797
1155
|
case 'disconnected':
|
|
798
1156
|
case 'not_monitored':
|
|
799
1157
|
return 'error';
|
|
1158
|
+
case 'waiting_approval':
|
|
1159
|
+
// The contract validator requires activeModal+buttons whenever
|
|
1160
|
+
// status is waiting_approval. If a producer/coercer set this
|
|
1161
|
+
// status without staging the modal yet (a race we hit with
|
|
1162
|
+
// codex-cli during tool approval setup), downgrade to a
|
|
1163
|
+
// generating-like status so readChat still returns successfully.
|
|
1164
|
+
// The next poll will pick up the modal once the provider has
|
|
1165
|
+
// emitted it.
|
|
1166
|
+
return hasNonEmptyModalButtons(activeModal) ? 'waiting_approval' : 'generating';
|
|
800
1167
|
default:
|
|
801
1168
|
return raw;
|
|
802
1169
|
}
|
|
@@ -865,6 +1232,64 @@ function finalizeStreamingMessagesWhenIdle(messages: ChatMessage[], status: stri
|
|
|
865
1232
|
});
|
|
866
1233
|
}
|
|
867
1234
|
|
|
1235
|
+
/**
|
|
1236
|
+
* Collapse adjacent PTY messages whose canonical (whitespace-stripped)
|
|
1237
|
+
* content is identical, OR whose turn key + role/kind match.
|
|
1238
|
+
*
|
|
1239
|
+
* The PTY parser of some providers (hermes-cli observed in the wild)
|
|
1240
|
+
* emits the same logical assistant turn twice when the terminal re-wraps
|
|
1241
|
+
* the text at a different column. The two emissions differ in newline
|
|
1242
|
+
* position — and sometimes in a single inserted space next to punctuation
|
|
1243
|
+
* (e.g. `(수정 2개), upstream` vs `(수정 2개 ), upstream`), so a simple
|
|
1244
|
+
* `\s+ -> ' '` normalize cannot collapse them.
|
|
1245
|
+
*
|
|
1246
|
+
* Strategy:
|
|
1247
|
+
* 1. If both messages carry the same _turnKey + role + kind, they are
|
|
1248
|
+
* the same logical turn by construction. Collapse.
|
|
1249
|
+
* 2. Otherwise compare with all whitespace stripped — wrap variants
|
|
1250
|
+
* collapse to identical strings.
|
|
1251
|
+
*
|
|
1252
|
+
* Native-history paths run through pageHistoryRecords and already
|
|
1253
|
+
* collapse on a normalized signature; this helper is the PTY equivalent
|
|
1254
|
+
* the readChat sync path was missing.
|
|
1255
|
+
*/
|
|
1256
|
+
function collapseAdjacentDuplicateChatMessages(messages: ChatMessage[]): ChatMessage[] {
|
|
1257
|
+
if (!Array.isArray(messages) || messages.length <= 1) return messages;
|
|
1258
|
+
const result: ChatMessage[] = [];
|
|
1259
|
+
let prevRoleKind = '';
|
|
1260
|
+
let prevStripped = '';
|
|
1261
|
+
for (const message of messages) {
|
|
1262
|
+
const role = typeof message.role === 'string' ? message.role : '';
|
|
1263
|
+
const kind = typeof message.kind === 'string' ? message.kind : 'standard';
|
|
1264
|
+
const content = typeof message.content === 'string'
|
|
1265
|
+
? message.content
|
|
1266
|
+
: (Array.isArray(message.content) ? message.content.map((p: any) => typeof p?.text === 'string' ? p.text : '').join('') : '');
|
|
1267
|
+
const strippedContent = content.replace(/\s+/g, '');
|
|
1268
|
+
// Empty content or system messages are passed through untouched.
|
|
1269
|
+
if (!strippedContent || role === 'system') {
|
|
1270
|
+
result.push(message);
|
|
1271
|
+
prevRoleKind = '';
|
|
1272
|
+
prevStripped = '';
|
|
1273
|
+
continue;
|
|
1274
|
+
}
|
|
1275
|
+
const roleKind = `${role}:${kind}`;
|
|
1276
|
+
const sameStripped = strippedContent === prevStripped && roleKind === prevRoleKind;
|
|
1277
|
+
if (result.length > 0 && sameStripped) {
|
|
1278
|
+
// Adjacent duplicate after stripping all whitespace. Keep the
|
|
1279
|
+
// *later* copy because PTY's last emission usually has the most
|
|
1280
|
+
// complete formatting.
|
|
1281
|
+
result[result.length - 1] = message;
|
|
1282
|
+
prevRoleKind = roleKind;
|
|
1283
|
+
prevStripped = strippedContent;
|
|
1284
|
+
continue;
|
|
1285
|
+
}
|
|
1286
|
+
result.push(message);
|
|
1287
|
+
prevRoleKind = roleKind;
|
|
1288
|
+
prevStripped = strippedContent;
|
|
1289
|
+
}
|
|
1290
|
+
return result;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
868
1293
|
function buildReadChatCommandResult(payload: Record<string, any>, args: any): CommandResult {
|
|
869
1294
|
let validatedPayload: Record<string, any>;
|
|
870
1295
|
const debugReadChat = payload?.debugReadChat && typeof payload.debugReadChat === 'object'
|
|
@@ -1475,7 +1900,9 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
1475
1900
|
const activeModal = parsedRecord.activeModal ?? parsedRecord.modal ?? null;
|
|
1476
1901
|
const returnedStatus = normalizeCliReadChatStatus(parsedRecord.status, activeModal, adapter, adapterStatus, parsedRecord.messages);
|
|
1477
1902
|
const runtimeMessageMerger = getTargetInstance(h, args) as RuntimeChatMessageMerger | null;
|
|
1478
|
-
const parsedMessages =
|
|
1903
|
+
const parsedMessages = collapseAdjacentDuplicateChatMessages(
|
|
1904
|
+
finalizeStreamingMessagesWhenIdle(parsedRecord.messages as ChatMessage[], returnedStatus),
|
|
1905
|
+
);
|
|
1479
1906
|
const returnedMessages = runtimeMessageMerger?.category === 'cli'
|
|
1480
1907
|
&& runtimeMessageMerger.type === adapter.cliType
|
|
1481
1908
|
&& typeof runtimeMessageMerger.mergeRuntimeChatMessages === 'function'
|
|
@@ -1494,35 +1921,57 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
1494
1921
|
? adapter.workingDir
|
|
1495
1922
|
: undefined;
|
|
1496
1923
|
const intendedWorkspace = typeof args?.workspace === 'string' ? args.workspace : undefined;
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1924
|
+
// ───────────────────────────────────────────────────────────
|
|
1925
|
+
// Chat source decision via ChatSourceMachine (A2 big-bang).
|
|
1926
|
+
// Replaces the ~300-line if-ladder that mixed source decision
|
|
1927
|
+
// with native fetch, anchor mutation, and runtime mirror
|
|
1928
|
+
// selection. The machine decides only between native-history
|
|
1929
|
+
// and pty-parser; downstream selection of which message array
|
|
1930
|
+
// to surface stays here.
|
|
1931
|
+
//
|
|
1932
|
+
// Behavioural changes vs v1:
|
|
1933
|
+
// - No more nativeHistoryAnchoredAt mutation on the adapter.
|
|
1934
|
+
// Lock state lives in CHAT_SOURCE_REGISTRY keyed by
|
|
1935
|
+
// (providerType, sessionId).
|
|
1936
|
+
// - No PTY-vs-native freshness comparison. The lock holds
|
|
1937
|
+
// across arbitrary PTY arrival; only native regression /
|
|
1938
|
+
// unavailability unlocks. This is the plipping fix.
|
|
1939
|
+
// - 6 trigger strings (native_history_partial / _stale /
|
|
1940
|
+
// _not_safely_mapped / _empty / _error / _unavailable)
|
|
1941
|
+
// collapse to 3 events with diagnostic causes preserved
|
|
1942
|
+
// and mapped back to legacy fallbackReason strings for
|
|
1943
|
+
// response compatibility.
|
|
1944
|
+
// - Codex live-workspace native probe and unsafe-native
|
|
1945
|
+
// daemon mirror fallbacks are preserved as additional
|
|
1946
|
+
// input rounds to the machine; they were never the source
|
|
1947
|
+
// decision itself, they were retries.
|
|
1948
|
+
// ───────────────────────────────────────────────────────────
|
|
1949
|
+
|
|
1950
|
+
const supportsNative = supportsCliNativeTranscript(providerType, provider)
|
|
1951
|
+
&& isNativeSourceCanonicalHistory(provider?.canonicalHistory);
|
|
1952
|
+
const agentStr = provider?.type || args?.agentType || getCurrentProviderType(h, adapter.cliType);
|
|
1953
|
+
const workspace = sessionWorkspace;
|
|
1954
|
+
const nativeHistoryLimit = Math.max(
|
|
1955
|
+
normalizeReadChatTailLimit(args) || 0,
|
|
1956
|
+
returnedMessages.length,
|
|
1957
|
+
200,
|
|
1958
|
+
);
|
|
1959
|
+
const nativeHistorySessionId = supportsNative
|
|
1960
|
+
? resolveCliNativeHistorySessionId(args, historySessionId, providerSessionId)
|
|
1961
|
+
: undefined;
|
|
1962
|
+
const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
|
|
1963
|
+
const exactNativeHistoryScope = Boolean(
|
|
1964
|
+
(typeof args?.historySessionId === 'string' && args.historySessionId.trim())
|
|
1965
|
+
|| (typeof args?.providerSessionId === 'string' && args.providerSessionId.trim())
|
|
1966
|
+
|| providerSessionId
|
|
1967
|
+
|| (nativeHistorySessionId && nativeHistorySessionId !== targetSessionId)
|
|
1968
|
+
|| ((h.currentSession as any)?.sessionId === args?.targetSessionId && typeof (h.currentSession as any)?.providerSessionId === 'string' && (h.currentSession as any).providerSessionId.trim())
|
|
1969
|
+
);
|
|
1507
1970
|
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
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;
|
|
1971
|
+
// 1. Fetch native history (or skip if provider does not support it).
|
|
1972
|
+
let nativeHistory: any | null = null;
|
|
1973
|
+
let nativeHistoryError: unknown | undefined;
|
|
1974
|
+
if (supportsNative) {
|
|
1526
1975
|
try {
|
|
1527
1976
|
nativeHistory = readCliProviderNativeHistory(agentStr, {
|
|
1528
1977
|
canonicalHistory: provider?.canonicalHistory,
|
|
@@ -1533,257 +1982,185 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
1533
1982
|
excludeRecentCount: 0,
|
|
1534
1983
|
historyBehavior: provider?.historyBehavior,
|
|
1535
1984
|
scripts: provider?.scripts as any,
|
|
1985
|
+
excludeInProgressTurn: returnedStatus === 'waiting_approval',
|
|
1536
1986
|
});
|
|
1537
1987
|
} catch (error: any) {
|
|
1538
|
-
|
|
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
|
-
});
|
|
1988
|
+
nativeHistoryError = error;
|
|
1549
1989
|
nativeHistory = null;
|
|
1550
1990
|
}
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
// 2. Compute safeMapping with the same rules the v1 code used so the
|
|
1994
|
+
// machine sees the same observation it always would have.
|
|
1995
|
+
const nativeMessages: ChatMessage[] = nativeHistory && Array.isArray(nativeHistory.messages)
|
|
1996
|
+
? normalizeNativeHistoryMessages(agentStr, nativeHistory.messages as ChatMessage[], nativeHistory.providerSessionId)
|
|
1997
|
+
: [];
|
|
1998
|
+
const historyProviderSessionId = typeof nativeHistory?.providerSessionId === 'string'
|
|
1999
|
+
? nativeHistory.providerSessionId
|
|
2000
|
+
: readHistorySessionIdFromMessages(nativeMessages) || nativeHistorySessionId || historySessionId;
|
|
2001
|
+
const lookup = nativeHistory?.lookup === 'workspace' ? 'workspace' : 'session';
|
|
2002
|
+
const nativeHistorySessionForMapping = adapter.cliType === 'antigravity-cli'
|
|
2003
|
+
&& historyProviderSessionId
|
|
2004
|
+
&& nativeHistorySessionId
|
|
2005
|
+
&& historyProviderSessionId !== nativeHistorySessionId
|
|
2006
|
+
? undefined
|
|
2007
|
+
: nativeHistorySessionId;
|
|
2008
|
+
const safeMapping = supportsNative && nativeHistory
|
|
2009
|
+
? hasSafeNativeHistoryMapping({
|
|
2010
|
+
historySessionId: lookup === 'workspace' ? undefined : nativeHistorySessionForMapping,
|
|
2011
|
+
providerSessionId: lookup === 'workspace' ? undefined : historyProviderSessionId || providerSessionId,
|
|
2012
|
+
workspace,
|
|
2013
|
+
nativeMessages,
|
|
2014
|
+
ptyMessages: returnedMessages,
|
|
2015
|
+
requireWorkspaceContentOverlap: lookup === 'workspace' && !exactNativeHistoryScope,
|
|
2016
|
+
})
|
|
2017
|
+
: false;
|
|
1551
2018
|
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
2019
|
+
// 3. Drive ChatSourceMachine — one observation per readChat call,
|
|
2020
|
+
// keyed by (providerType, sessionKey-for-this-call). targetSessionId
|
|
2021
|
+
// is the most specific session anchor we have; fall back to
|
|
2022
|
+
// historySessionId so we never leak state across distinct sessions.
|
|
2023
|
+
const machineSessionKey = String(
|
|
2024
|
+
args?.targetSessionId
|
|
2025
|
+
|| providerSessionId
|
|
2026
|
+
|| historySessionId
|
|
2027
|
+
|| (h.currentSession as any)?.sessionId
|
|
2028
|
+
|| ''
|
|
2029
|
+
);
|
|
2030
|
+
const primary = decideCliReadChatSource({
|
|
2031
|
+
providerType,
|
|
2032
|
+
provider,
|
|
2033
|
+
sessionId: machineSessionKey,
|
|
2034
|
+
nativeHistoryResult: nativeHistory,
|
|
2035
|
+
nativeHistoryError,
|
|
2036
|
+
safeMapping,
|
|
2037
|
+
sessionWorkspace,
|
|
2038
|
+
intendedWorkspace,
|
|
2039
|
+
ptyMessages: returnedMessages,
|
|
2040
|
+
// Start with PTY visible; decideCliReadChatSource flips this
|
|
2041
|
+
// to true when the machine actually selects native-history.
|
|
2042
|
+
ptyStatusApprovalOnly: false,
|
|
2043
|
+
});
|
|
2044
|
+
let messageSource: Record<string, unknown> = primary.messageSource;
|
|
2045
|
+
|
|
2046
|
+
if (primary.nativeSelected) {
|
|
2047
|
+
selectedMessages = finalizeStreamingMessagesWhenIdle(primary.nativeMessages, returnedStatus);
|
|
2048
|
+
selectedProviderSessionId = historyProviderSessionId || providerSessionId;
|
|
2049
|
+
selectedTranscriptAuthority = 'provider';
|
|
2050
|
+
selectedCoverage = nativeHistory?.hasMore ? 'tail' : 'full';
|
|
2051
|
+
} else if (supportsNative) {
|
|
2052
|
+
// Native not selected. Two preserved v1 fallbacks before settling
|
|
2053
|
+
// on PTY: (a) Codex-only live workspace native probe; (b) unsafe-
|
|
2054
|
+
// native daemon mirror selection. The machine sees each retry as
|
|
2055
|
+
// an additional observation.
|
|
2056
|
+
const liveCurrentRuntimePtySafe = isCurrentRuntimePtySafelyAttributed({
|
|
2057
|
+
adapter,
|
|
2058
|
+
helpers: h,
|
|
2059
|
+
readChatArgs: args,
|
|
2060
|
+
sessionWorkspace,
|
|
2061
|
+
intendedWorkspace,
|
|
2062
|
+
ptyMessages: returnedMessages,
|
|
2063
|
+
});
|
|
2064
|
+
const mayProbeLiveCodexWorkspaceNative = adapter.cliType === 'codex-cli'
|
|
2065
|
+
&& liveCurrentRuntimePtySafe
|
|
2066
|
+
&& !(typeof args?.providerSessionId === 'string' && args.providerSessionId.trim())
|
|
2067
|
+
&& !(providerSessionId && providerSessionId.trim())
|
|
2068
|
+
&& (!historyProviderSessionId || historyProviderSessionId === nativeHistorySessionId || historyProviderSessionId === historySessionId);
|
|
2069
|
+
const liveWorkspaceNativeHistory = mayProbeLiveCodexWorkspaceNative
|
|
2070
|
+
? readLiveCodexWorkspaceNativeHistory(agentStr, {
|
|
2071
|
+
canonicalHistory: provider?.canonicalHistory,
|
|
1581
2072
|
workspace,
|
|
1582
|
-
|
|
2073
|
+
offset: 0,
|
|
2074
|
+
limit: nativeHistoryLimit,
|
|
2075
|
+
excludeRecentCount: 0,
|
|
2076
|
+
historyBehavior: provider?.historyBehavior,
|
|
2077
|
+
scripts: provider?.scripts as any,
|
|
2078
|
+
})
|
|
2079
|
+
: null;
|
|
2080
|
+
const liveWorkspaceNativeMessages = Array.isArray((liveWorkspaceNativeHistory as any)?.messages)
|
|
2081
|
+
? normalizeNativeHistoryMessages(agentStr, (liveWorkspaceNativeHistory as any).messages as ChatMessage[], (liveWorkspaceNativeHistory as any)?.providerSessionId)
|
|
2082
|
+
: [];
|
|
2083
|
+
const liveWorkspaceNativeProviderSessionId = typeof (liveWorkspaceNativeHistory as any)?.providerSessionId === 'string'
|
|
2084
|
+
? (liveWorkspaceNativeHistory as any).providerSessionId
|
|
2085
|
+
: readHistorySessionIdFromMessages(liveWorkspaceNativeMessages);
|
|
2086
|
+
const liveWorkspaceNativeSafeMapping = liveWorkspaceNativeMessages.length > 0
|
|
2087
|
+
&& hasSafeNativeHistoryMapping({
|
|
2088
|
+
workspace,
|
|
2089
|
+
nativeMessages: liveWorkspaceNativeMessages,
|
|
1583
2090
|
ptyMessages: returnedMessages,
|
|
1584
|
-
requireWorkspaceContentOverlap:
|
|
2091
|
+
requireWorkspaceContentOverlap: true,
|
|
1585
2092
|
});
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
2093
|
+
if (liveWorkspaceNativeHistory) {
|
|
2094
|
+
const liveDecision = decideCliReadChatSource({
|
|
2095
|
+
providerType,
|
|
2096
|
+
provider,
|
|
2097
|
+
// Distinct session key so a transient codex live-probe does not
|
|
2098
|
+
// clobber the primary session's lock. The machine treats this
|
|
2099
|
+
// as its own session; the primary session's state is untouched.
|
|
2100
|
+
sessionId: `${machineSessionKey}::live-workspace`,
|
|
2101
|
+
nativeHistoryResult: liveWorkspaceNativeHistory,
|
|
2102
|
+
safeMapping: liveWorkspaceNativeSafeMapping,
|
|
2103
|
+
sessionWorkspace,
|
|
2104
|
+
intendedWorkspace,
|
|
1589
2105
|
ptyMessages: returnedMessages,
|
|
2106
|
+
ptyStatusApprovalOnly: true,
|
|
1590
2107
|
});
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
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;
|
|
2108
|
+
if (liveDecision.nativeSelected) {
|
|
2109
|
+
selectedMessages = finalizeStreamingMessagesWhenIdle(liveDecision.nativeMessages, returnedStatus);
|
|
2110
|
+
selectedProviderSessionId = liveWorkspaceNativeProviderSessionId || providerSessionId;
|
|
1600
2111
|
selectedTranscriptAuthority = 'provider';
|
|
1601
|
-
selectedCoverage = (
|
|
1602
|
-
messageSource =
|
|
1603
|
-
|
|
1604
|
-
|
|
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
|
-
});
|
|
2112
|
+
selectedCoverage = (liveWorkspaceNativeHistory as any).hasMore ? 'tail' : 'full';
|
|
2113
|
+
messageSource = liveDecision.messageSource;
|
|
2114
|
+
(messageSource as any).selectedDaemonSource = 'live-workspace-native-history';
|
|
2115
|
+
(messageSource as any).runtimeMappingSafe = true;
|
|
1622
2116
|
} else {
|
|
1623
|
-
|
|
2117
|
+
// Live probe also rejected: apply unsafe-native daemon mirror
|
|
2118
|
+
// selection (codex-only) using the primary decision's
|
|
2119
|
+
// fallbackReason.
|
|
2120
|
+
applyUnsafeNativeDaemonFallback({
|
|
2121
|
+
providerType,
|
|
1624
2122
|
adapter,
|
|
1625
2123
|
helpers: h,
|
|
1626
2124
|
readChatArgs: args,
|
|
1627
2125
|
sessionWorkspace,
|
|
1628
2126
|
intendedWorkspace,
|
|
1629
2127
|
ptyMessages: returnedMessages,
|
|
2128
|
+
nativeHistoryLimit,
|
|
2129
|
+
provider,
|
|
2130
|
+
messageSourceRef: { set(value) { messageSource = value; }, get() { return messageSource; } },
|
|
2131
|
+
apply(selection) {
|
|
2132
|
+
selectedMessages = selection.messages;
|
|
2133
|
+
selectedTranscriptAuthority = selection.transcriptAuthority;
|
|
2134
|
+
selectedCoverage = selection.coverage ?? coverage;
|
|
2135
|
+
selectedStatus = selection.status ?? returnedStatus;
|
|
2136
|
+
},
|
|
2137
|
+
activeModal,
|
|
2138
|
+
returnedStatus,
|
|
2139
|
+
coverage,
|
|
1630
2140
|
});
|
|
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
2141
|
}
|
|
2142
|
+
} else {
|
|
2143
|
+
applyUnsafeNativeDaemonFallback({
|
|
2144
|
+
providerType,
|
|
2145
|
+
adapter,
|
|
2146
|
+
helpers: h,
|
|
2147
|
+
readChatArgs: args,
|
|
2148
|
+
sessionWorkspace,
|
|
2149
|
+
intendedWorkspace,
|
|
2150
|
+
ptyMessages: returnedMessages,
|
|
2151
|
+
nativeHistoryLimit,
|
|
2152
|
+
provider,
|
|
2153
|
+
messageSourceRef: { set(value) { messageSource = value; }, get() { return messageSource; } },
|
|
2154
|
+
apply(selection) {
|
|
2155
|
+
selectedMessages = selection.messages;
|
|
2156
|
+
selectedTranscriptAuthority = selection.transcriptAuthority;
|
|
2157
|
+
selectedCoverage = selection.coverage ?? coverage;
|
|
2158
|
+
selectedStatus = selection.status ?? returnedStatus;
|
|
2159
|
+
},
|
|
2160
|
+
activeModal,
|
|
2161
|
+
returnedStatus,
|
|
2162
|
+
coverage,
|
|
2163
|
+
});
|
|
1787
2164
|
}
|
|
1788
2165
|
}
|
|
1789
2166
|
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}`);
|
|
@@ -1818,6 +2195,11 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
1818
2195
|
...(selectedCoverage ? { coverage: selectedCoverage } : {}),
|
|
1819
2196
|
}, args);
|
|
1820
2197
|
}
|
|
2198
|
+
// History-only path (no adapter). Same source-decision contract as
|
|
2199
|
+
// the adapter path above, but with no PTY messages — the machine
|
|
2200
|
+
// simply decides whether native is usable; if not we return the
|
|
2201
|
+
// history we have plus a `native_history_not_safely_available`
|
|
2202
|
+
// error response when the provider requires native source.
|
|
1821
2203
|
const historyLimit = normalizeReadChatTailLimit(args);
|
|
1822
2204
|
try {
|
|
1823
2205
|
const agentStr = provider?.type || args?.agentType || getCurrentProviderType(h);
|
|
@@ -1825,13 +2207,9 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
1825
2207
|
? (h.currentSession as any).workspace
|
|
1826
2208
|
: undefined;
|
|
1827
2209
|
const intendedWorkspace = typeof args?.workspace === 'string' ? args.workspace : undefined;
|
|
1828
|
-
const
|
|
1829
|
-
|
|
1830
|
-
|
|
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)
|
|
2210
|
+
const supportsNative = supportsCliNativeTranscript(agentStr, provider)
|
|
2211
|
+
&& isNativeSourceCanonicalHistory(provider?.canonicalHistory);
|
|
2212
|
+
const history = supportsNative
|
|
1835
2213
|
? readCliProviderNativeHistory(agentStr, {
|
|
1836
2214
|
canonicalHistory: provider?.canonicalHistory,
|
|
1837
2215
|
historySessionId,
|
|
@@ -1843,35 +2221,23 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
1843
2221
|
scripts: provider?.scripts as any,
|
|
1844
2222
|
})
|
|
1845
2223
|
: readProviderChatHistory(agentStr, {
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
const lookup = (history as any)
|
|
2224
|
+
canonicalHistory: provider?.canonicalHistory,
|
|
2225
|
+
historySessionId,
|
|
2226
|
+
workspace,
|
|
2227
|
+
offset: 0,
|
|
2228
|
+
limit: historyLimit,
|
|
2229
|
+
excludeRecentCount: 0,
|
|
2230
|
+
historyBehavior: provider?.historyBehavior,
|
|
2231
|
+
scripts: provider?.scripts as any,
|
|
2232
|
+
});
|
|
2233
|
+
const lookup = (history as any)?.lookup === 'workspace' ? 'workspace' : 'session';
|
|
1856
2234
|
const historyMessages = Array.isArray((history as any)?.messages)
|
|
1857
2235
|
? normalizeNativeHistoryMessages(agentStr, (history as any).messages as ChatMessage[], (history as any)?.providerSessionId)
|
|
1858
2236
|
: [];
|
|
1859
2237
|
const historyProviderSessionId = typeof (history as any)?.providerSessionId === 'string'
|
|
1860
2238
|
? (history as any).providerSessionId
|
|
1861
2239
|
: readHistorySessionIdFromMessages(historyMessages) || historySessionId;
|
|
1862
|
-
const
|
|
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)
|
|
2240
|
+
const safeMapping = supportsNative
|
|
1875
2241
|
? hasSafeNativeHistoryMapping({
|
|
1876
2242
|
historySessionId: lookup === 'workspace' ? undefined : historySessionId,
|
|
1877
2243
|
providerSessionId: lookup === 'workspace' ? undefined : historyProviderSessionId,
|
|
@@ -1879,60 +2245,41 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
1879
2245
|
nativeMessages: historyMessages,
|
|
1880
2246
|
})
|
|
1881
2247
|
: false;
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
2248
|
+
|
|
2249
|
+
const machineSessionKey = String(
|
|
2250
|
+
args?.targetSessionId
|
|
2251
|
+
|| historyProviderSessionId
|
|
2252
|
+
|| historySessionId
|
|
2253
|
+
|| (h.currentSession as any)?.sessionId
|
|
2254
|
+
|| ''
|
|
2255
|
+
);
|
|
2256
|
+
const decision = decideCliReadChatSource({
|
|
2257
|
+
providerType: agentStr,
|
|
2258
|
+
provider,
|
|
2259
|
+
sessionId: machineSessionKey,
|
|
2260
|
+
nativeHistoryResult: history,
|
|
2261
|
+
safeMapping,
|
|
1892
2262
|
sessionWorkspace: workspace,
|
|
1893
2263
|
intendedWorkspace,
|
|
1894
|
-
|
|
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,
|
|
2264
|
+
ptyMessages: [],
|
|
1917
2265
|
ptyStatusApprovalOnly: false,
|
|
1918
2266
|
});
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
if (requiresNativeSource && !nativeSelected) {
|
|
2267
|
+
|
|
2268
|
+
if (supportsNative && !decision.nativeSelected) {
|
|
1922
2269
|
return {
|
|
1923
2270
|
success: false,
|
|
1924
2271
|
code: 'native_history_not_safely_available',
|
|
1925
2272
|
error: 'Provider-native history was not safely available for the requested CLI session.',
|
|
1926
2273
|
providerSessionId: historyProviderSessionId,
|
|
1927
|
-
messageSource,
|
|
1928
|
-
transcriptProvenance: messageSource,
|
|
2274
|
+
messageSource: decision.messageSource,
|
|
2275
|
+
transcriptProvenance: decision.messageSource,
|
|
1929
2276
|
};
|
|
1930
2277
|
}
|
|
1931
2278
|
return buildReadChatCommandResult({
|
|
1932
2279
|
messages: historyMessages,
|
|
1933
2280
|
status: 'idle',
|
|
1934
|
-
messageSource,
|
|
1935
|
-
transcriptProvenance: messageSource,
|
|
2281
|
+
messageSource: decision.messageSource,
|
|
2282
|
+
transcriptProvenance: decision.messageSource,
|
|
1936
2283
|
...(typeof (history as any)?.title === 'string' ? { title: (history as any).title } : {}),
|
|
1937
2284
|
...(historyProviderSessionId ? { providerSessionId: historyProviderSessionId } : {}),
|
|
1938
2285
|
...(((provider?.historyBehavior as any)?.transcriptAuthority === 'provider' || (provider?.historyBehavior as any)?.transcriptAuthority === 'daemon')
|