@adhdev/daemon-core 0.9.82-rc.14 → 0.9.82-rc.140
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/chat/subscription-updates.d.ts +1 -0
- package/dist/cli-adapter-types.d.ts +5 -1
- package/dist/cli-adapters/cli-script-runner.d.ts +45 -0
- package/dist/cli-adapters/cli-state-engine.d.ts +178 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +87 -63
- package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +21 -0
- package/dist/commands/router.d.ts +22 -0
- package/dist/config/chat-history.d.ts +5 -0
- package/dist/config/config.d.ts +5 -0
- package/dist/config/mesh-config.d.ts +68 -1
- package/dist/git/git-commands.d.ts +5 -1
- package/dist/index.d.ts +18 -6
- package/dist/index.js +10431 -2827
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10376 -2811
- package/dist/index.mjs.map +1 -1
- package/dist/installer.d.ts +1 -4
- package/dist/launch.d.ts +1 -1
- package/dist/logging/async-batch-writer.d.ts +10 -0
- package/dist/mesh/beads-db.d.ts +72 -0
- package/dist/mesh/contracts.d.ts +164 -0
- package/dist/mesh/coordinator-registry.d.ts +25 -0
- package/dist/mesh/mesh-active-work.d.ts +90 -0
- package/dist/mesh/mesh-events.d.ts +77 -5
- package/dist/mesh/mesh-fast-forward.d.ts +39 -0
- package/dist/mesh/mesh-host-ownership.d.ts +9 -0
- package/dist/mesh/mesh-ledger.d.ts +58 -1
- package/dist/mesh/mesh-refine-status.d.ts +26 -0
- package/dist/mesh/mesh-work-queue.d.ts +44 -5
- package/dist/mesh/preview-freshness.d.ts +18 -0
- package/dist/mesh/refine-config.d.ts +193 -0
- package/dist/mesh/worktree-bootstrap-config.d.ts +113 -0
- package/dist/providers/approval-utils.d.ts +9 -0
- package/dist/providers/chat-message-normalization.d.ts +1 -0
- package/dist/providers/cli-provider-instance.d.ts +6 -1
- 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 +67 -0
- package/dist/shared-types.d.ts +12 -0
- package/dist/status/reporter.d.ts +2 -0
- package/dist/status/snapshot.d.ts +1 -0
- package/dist/types.d.ts +5 -0
- package/package.json +3 -1
- package/src/boot/daemon-lifecycle.ts +3 -0
- package/src/chat/source-machine.ts +534 -0
- package/src/chat/source-resolver.ts +0 -0
- package/src/chat/subscription-updates.ts +14 -1
- package/src/cli-adapter-types.d.ts +1 -0
- package/src/cli-adapter-types.ts +3 -1
- package/src/cli-adapters/cli-script-runner.ts +145 -0
- package/src/cli-adapters/cli-state-engine.ts +1083 -0
- package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +630 -1137
- package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/src/cli-adapters/provider-cli-parse.ts +13 -0
- package/src/cli-adapters/provider-cli-runtime.ts +3 -1
- package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
- package/src/cli-adapters/provider-cli-shared.ts +51 -11
- 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 +1428 -50
- package/src/commands/cli-manager.ts +145 -3
- package/src/commands/handler.ts +8 -1
- package/src/commands/mesh-coordinator.ts +13 -143
- package/src/commands/router.ts +3271 -427
- package/src/config/chat-history.ts +79 -24
- package/src/config/config.ts +12 -0
- package/src/config/mesh-config.ts +249 -2
- package/src/config/recent-activity.ts +8 -2
- package/src/daemon/dev-cli-debug.ts +10 -1
- package/src/detection/ide-detector.ts +26 -16
- package/src/git/git-commands.ts +17 -5
- package/src/git/git-worktree.ts +8 -1
- package/src/index.ts +45 -5
- package/src/installer.d.ts +1 -1
- package/src/installer.ts +8 -6
- package/src/launch.d.ts +1 -1
- package/src/launch.ts +37 -28
- package/src/logging/async-batch-writer.ts +55 -0
- package/src/logging/logger.ts +2 -1
- package/src/mesh/beads-db.ts +479 -0
- package/src/mesh/contracts.ts +329 -0
- package/src/mesh/coordinator-prompt.ts +40 -22
- package/src/mesh/coordinator-registry.ts +75 -0
- package/src/mesh/mesh-active-work.ts +437 -0
- package/src/mesh/mesh-events.ts +799 -56
- package/src/mesh/mesh-fast-forward.ts +430 -0
- package/src/mesh/mesh-host-ownership.ts +73 -0
- package/src/mesh/mesh-ledger.ts +457 -104
- package/src/mesh/mesh-refine-status.ts +144 -0
- package/src/mesh/mesh-work-queue.ts +216 -158
- package/src/mesh/preview-freshness.ts +118 -0
- package/src/mesh/refine-config.ts +366 -0
- package/src/mesh/worktree-bootstrap-config.ts +247 -0
- package/src/providers/approval-utils.ts +39 -5
- package/src/providers/chat-message-normalization.ts +7 -12
- package/src/providers/cli-provider-instance.ts +362 -41
- package/src/providers/contracts.ts +19 -0
- package/src/providers/ide-provider-instance.ts +17 -3
- package/src/providers/provider-loader.ts +31 -11
- package/src/providers/provider-schema.ts +12 -0
- package/src/providers/read-chat-contract.ts +76 -16
- package/src/providers/transcript-v2.ts +567 -0
- package/src/providers/version-archive.ts +38 -20
- package/src/repo-mesh-types.ts +77 -0
- package/src/shared-types.ts +9 -0
- package/src/status/builders.ts +23 -6
- package/src/status/reporter.ts +15 -0
- package/src/status/snapshot.ts +35 -11
- package/src/system/host-memory.ts +29 -12
- package/src/types.ts +5 -0
|
@@ -21,7 +21,7 @@ import { ChatHistoryWriter, isNativeSourceCanonicalHistory, materializeProviderN
|
|
|
21
21
|
import { LOG } from '../logging/logger.js';
|
|
22
22
|
import type { ChatMessage } from '../types.js';
|
|
23
23
|
import { buildPersistedProviderEffectMessage, normalizeProviderEffects } from './control-effects.js';
|
|
24
|
-
import { formatAutoApprovalMessage, pickApprovalButton } from './approval-utils.js';
|
|
24
|
+
import { formatAutoApprovalMessage, pickApprovalButton, looksLikeActiveApprovalPromptText } from './approval-utils.js';
|
|
25
25
|
import { getCliScriptCommand, parseCliScriptResult } from './cli-script-results.js';
|
|
26
26
|
import { mergeProviderPatchState, resolveProviderStateSurface } from './provider-patch-state.js';
|
|
27
27
|
import { normalizeProviderSessionId } from './provider-session-id.js';
|
|
@@ -43,6 +43,24 @@ type CompletedDebouncePending = {
|
|
|
43
43
|
loggedBlockReason?: string;
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
+
function isIdleStatus(value: unknown): boolean {
|
|
47
|
+
const status = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
48
|
+
return !status || status === 'idle' || status === 'ready';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
function getMessageTime(message: unknown): number {
|
|
53
|
+
if (!message || typeof message !== 'object') return 0;
|
|
54
|
+
const record = message as { receivedAt?: unknown; timestamp?: unknown };
|
|
55
|
+
const value = Number(record.receivedAt ?? record.timestamp ?? 0);
|
|
56
|
+
return Number.isFinite(value) ? value : 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type CompletedFinalizationBlock = {
|
|
60
|
+
reason: string;
|
|
61
|
+
terminal?: boolean;
|
|
62
|
+
};
|
|
63
|
+
|
|
46
64
|
const COMPLETED_FINALIZATION_RETRY_MS = 1000;
|
|
47
65
|
const COMPLETED_FINALIZATION_MAX_WAIT_MS = 30_000;
|
|
48
66
|
|
|
@@ -312,6 +330,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
312
330
|
private lastApprovalEventAt = 0;
|
|
313
331
|
private autoApproveBusy = false;
|
|
314
332
|
private autoApproveBusyTimer: NodeJS.Timeout | null = null;
|
|
333
|
+
private lastAutoApprovalSignature = '';
|
|
315
334
|
private controlValues: Record<string, string | number | boolean> = {};
|
|
316
335
|
private summaryMetadata: unknown = undefined;
|
|
317
336
|
private appliedEffectKeys = new Set<string>();
|
|
@@ -371,6 +390,9 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
371
390
|
this.launchMode = options?.launchMode || 'new';
|
|
372
391
|
this.onProviderSessionResolved = options?.onProviderSessionResolved;
|
|
373
392
|
this.adapter = new ProviderCliAdapter(provider as CliProviderModule, workingDir, cliArgs, options?.extraEnv || {}, transportFactory);
|
|
393
|
+
if (this.providerSessionId) {
|
|
394
|
+
this.adapter.updateRuntimeMeta({ providerSessionId: this.providerSessionId });
|
|
395
|
+
}
|
|
374
396
|
this.monitor = new StatusMonitor();
|
|
375
397
|
this.historyWriter = new ChatHistoryWriter();
|
|
376
398
|
}
|
|
@@ -412,7 +434,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
412
434
|
await this.adapter.spawn();
|
|
413
435
|
await this.enforceFreshSessionLaunchIfNeeded();
|
|
414
436
|
this.maybeAppendRuntimeRecoveryMessage(this.adapter.getRuntimeMetadata());
|
|
415
|
-
if (this.providerSessionId) {
|
|
437
|
+
if (this.providerSessionId && this.shouldHydrateExistingProviderHistory()) {
|
|
416
438
|
this.restorePersistedHistoryFromCurrentSession();
|
|
417
439
|
}
|
|
418
440
|
if (this.providerSessionId && this.launchMode === 'resume') {
|
|
@@ -492,8 +514,14 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
492
514
|
if (typeof this.adapter.getScriptParsedStatus === 'function') {
|
|
493
515
|
try {
|
|
494
516
|
parsedStatus = this.adapter.getScriptParsedStatus() || null;
|
|
495
|
-
|
|
496
|
-
|
|
517
|
+
const parsedErrorMessage = typeof parsedStatus?.errorMessage === 'string' && parsedStatus.errorMessage.trim()
|
|
518
|
+
? parsedStatus.errorMessage.trim()
|
|
519
|
+
: undefined;
|
|
520
|
+
const parsedErrorReason = typeof parsedStatus?.errorReason === 'string' && parsedStatus.errorReason.trim()
|
|
521
|
+
? parsedStatus.errorReason.trim() as ProviderErrorReason
|
|
522
|
+
: undefined;
|
|
523
|
+
this.errorMessage = parsedErrorMessage;
|
|
524
|
+
this.errorReason = parsedErrorReason;
|
|
497
525
|
} catch (error: any) {
|
|
498
526
|
parseErrorMessage = error?.message || String(error);
|
|
499
527
|
this.errorMessage = parseErrorMessage;
|
|
@@ -503,22 +531,39 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
503
531
|
this.errorMessage = undefined;
|
|
504
532
|
this.errorReason = undefined;
|
|
505
533
|
}
|
|
534
|
+
const adapterProviderSessionId = normalizeProviderSessionId(
|
|
535
|
+
this.provider,
|
|
536
|
+
typeof adapterStatus?.providerSessionId === 'string' ? adapterStatus.providerSessionId : '',
|
|
537
|
+
);
|
|
506
538
|
const autoApproveActive = this.maybeAutoApproveStatus(adapterStatus, Date.now());
|
|
507
|
-
const visibleStatus = parseErrorMessage
|
|
539
|
+
const visibleStatus = parseErrorMessage || parsedStatus?.status === 'error'
|
|
508
540
|
? 'error'
|
|
509
541
|
: (autoApproveActive ? 'generating' : adapterStatus.status);
|
|
542
|
+
const runtime = this.adapter.getRuntimeMetadata();
|
|
543
|
+
this.maybeAppendRuntimeRecoveryMessage(runtime);
|
|
544
|
+
let parsedMessages = Array.isArray(parsedStatus?.messages)
|
|
545
|
+
? parsedStatus.messages
|
|
546
|
+
: [];
|
|
510
547
|
const parsedProviderSessionId = normalizeProviderSessionId(
|
|
511
548
|
this.provider,
|
|
512
549
|
typeof parsedStatus?.providerSessionId === 'string' ? parsedStatus.providerSessionId : '',
|
|
513
550
|
);
|
|
514
|
-
|
|
551
|
+
const suppressFreshLaunchStartupReplay = this.shouldSuppressFreshLaunchStartupReplay(
|
|
552
|
+
parsedMessages,
|
|
553
|
+
parsedStatus,
|
|
554
|
+
adapterStatus,
|
|
555
|
+
parsedProviderSessionId,
|
|
556
|
+
);
|
|
557
|
+
if (adapterProviderSessionId && !suppressFreshLaunchStartupReplay) {
|
|
558
|
+
this.promoteProviderSessionId(adapterProviderSessionId);
|
|
559
|
+
}
|
|
560
|
+
if (parsedProviderSessionId && !suppressFreshLaunchStartupReplay) {
|
|
515
561
|
this.promoteProviderSessionId(parsedProviderSessionId);
|
|
516
562
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
: [];
|
|
563
|
+
if (suppressFreshLaunchStartupReplay) {
|
|
564
|
+
parsedMessages = [];
|
|
565
|
+
}
|
|
566
|
+
const activeChatId = this.providerSessionId || runtime?.runtimeId || this.instanceId;
|
|
522
567
|
const historyMessageCount = Number.isFinite(parsedStatus?.historyMessageCount)
|
|
523
568
|
? Math.max(0, Number(parsedStatus.historyMessageCount))
|
|
524
569
|
: null;
|
|
@@ -528,7 +573,18 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
528
573
|
: [];
|
|
529
574
|
}
|
|
530
575
|
const mergedMessages = this.mergeConversationMessages(parsedMessages);
|
|
531
|
-
const canonicalBackedHistory = this.
|
|
576
|
+
const canonicalBackedHistory = this.shouldHydrateExistingProviderHistory()
|
|
577
|
+
? this.syncCanonicalSavedHistoryIfNeeded()
|
|
578
|
+
: false;
|
|
579
|
+
const statusMessages = canonicalBackedHistory && this.lastPersistedHistoryMessages.length > 0
|
|
580
|
+
? this.lastPersistedHistoryMessages.map((message) => ({
|
|
581
|
+
role: message.role,
|
|
582
|
+
content: message.content,
|
|
583
|
+
kind: message.kind,
|
|
584
|
+
senderName: message.senderName,
|
|
585
|
+
receivedAt: message.receivedAt,
|
|
586
|
+
}))
|
|
587
|
+
: mergedMessages;
|
|
532
588
|
|
|
533
589
|
const dirName = this.workingDir.split('/').filter(Boolean).pop() || 'session';
|
|
534
590
|
const parsedChatStatus = typeof parsedStatus?.status === 'string' && parsedStatus.status.trim()
|
|
@@ -572,7 +628,12 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
572
628
|
}
|
|
573
629
|
}
|
|
574
630
|
|
|
575
|
-
this.applyProviderResponse(
|
|
631
|
+
this.applyProviderResponse(
|
|
632
|
+
suppressFreshLaunchStartupReplay && parsedStatus && typeof parsedStatus === 'object'
|
|
633
|
+
? { ...parsedStatus, providerSessionId: undefined }
|
|
634
|
+
: parsedStatus,
|
|
635
|
+
{ phase: 'immediate' },
|
|
636
|
+
);
|
|
576
637
|
const surface = resolveProviderStateSurface({
|
|
577
638
|
summaryMetadata: this.summaryMetadata as any,
|
|
578
639
|
controlValues: this.controlValues,
|
|
@@ -592,10 +653,10 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
592
653
|
status: visibleStatus,
|
|
593
654
|
mode: this.presentationMode,
|
|
594
655
|
activeChat: {
|
|
595
|
-
id:
|
|
656
|
+
id: activeChatId,
|
|
596
657
|
title: parsedStatus?.title || dirName,
|
|
597
658
|
status: activeChatStatus,
|
|
598
|
-
messages:
|
|
659
|
+
messages: statusMessages,
|
|
599
660
|
activeModal: autoApproveActive ? null : (parsedStatus?.activeModal ?? adapterStatus.activeModal),
|
|
600
661
|
inputContent: '',
|
|
601
662
|
},
|
|
@@ -695,6 +756,34 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
695
756
|
}
|
|
696
757
|
}
|
|
697
758
|
|
|
759
|
+
recordAcknowledgedUserInput(input: InputEnvelope | string): void {
|
|
760
|
+
const content = typeof input === 'string'
|
|
761
|
+
? input.trim()
|
|
762
|
+
: buildCliStructuredInputPrompt(input).trim();
|
|
763
|
+
if (!content) return;
|
|
764
|
+
|
|
765
|
+
const receivedAt = Date.now();
|
|
766
|
+
const dedupKey = `user_input_ack:${crypto
|
|
767
|
+
.createHash('sha256')
|
|
768
|
+
.update(`${this.instanceId}:${content}:${receivedAt}`)
|
|
769
|
+
.digest('hex')
|
|
770
|
+
.slice(0, 24)}`;
|
|
771
|
+
this.appendRuntimeMessage(buildChatMessage({
|
|
772
|
+
role: 'user',
|
|
773
|
+
senderName: 'User',
|
|
774
|
+
kind: 'standard',
|
|
775
|
+
content,
|
|
776
|
+
receivedAt,
|
|
777
|
+
timestamp: receivedAt,
|
|
778
|
+
source: 'runtime_input_ack',
|
|
779
|
+
meta: {
|
|
780
|
+
runtimeInputAck: true,
|
|
781
|
+
provider: this.type,
|
|
782
|
+
workspace: this.workingDir,
|
|
783
|
+
},
|
|
784
|
+
} as ChatMessage), dedupKey);
|
|
785
|
+
}
|
|
786
|
+
|
|
698
787
|
dispose(): void {
|
|
699
788
|
this.adapter.shutdown();
|
|
700
789
|
this.monitor.reset();
|
|
@@ -740,7 +829,60 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
740
829
|
const lastVisible = visibleMessages[visibleMessages.length - 1] as ChatMessage | undefined;
|
|
741
830
|
const role = typeof lastVisible?.role === 'string' ? lastVisible.role.trim().toLowerCase() : '';
|
|
742
831
|
const content = lastVisible ? flattenContent(lastVisible.content).trim() : '';
|
|
743
|
-
|
|
832
|
+
if (role !== 'assistant' || !content) return false;
|
|
833
|
+
// Guard: if the last assistant message looks like an active approval/input prompt,
|
|
834
|
+
// it is not a real completion — the session is still awaiting user input.
|
|
835
|
+
if (looksLikeActiveApprovalPromptText(content)) return false;
|
|
836
|
+
return true;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
private buildCompletedFinalizationDiagnostic(args: {
|
|
840
|
+
blockReason: string;
|
|
841
|
+
latestStatus?: any;
|
|
842
|
+
latestVisibleStatus: string;
|
|
843
|
+
waitedMs: number;
|
|
844
|
+
pending: CompletedDebouncePending;
|
|
845
|
+
emittedAfterFinalizationTimeout: boolean;
|
|
846
|
+
}): Record<string, unknown> {
|
|
847
|
+
let parsed: any = null;
|
|
848
|
+
let parseError: string | undefined;
|
|
849
|
+
try {
|
|
850
|
+
parsed = this.adapter.getScriptParsedStatus();
|
|
851
|
+
} catch (error: any) {
|
|
852
|
+
parseError = error?.message || String(error);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
const visibleMessages = (Array.isArray(parsed?.messages) ? parsed.messages : [])
|
|
856
|
+
.filter((message: any) => isUserFacingChatMessage(message as ChatMessage));
|
|
857
|
+
const lastVisible = visibleMessages[visibleMessages.length - 1] as ChatMessage | undefined;
|
|
858
|
+
const lastVisibleRole = typeof lastVisible?.role === 'string' ? lastVisible.role.trim().toLowerCase() : null;
|
|
859
|
+
const lastVisibleKind = typeof (lastVisible as any)?.kind === 'string' ? (lastVisible as any).kind : null;
|
|
860
|
+
const lastVisibleContentLength = lastVisible ? flattenContent(lastVisible.content).trim().length : 0;
|
|
861
|
+
|
|
862
|
+
return {
|
|
863
|
+
providerType: this.type,
|
|
864
|
+
sessionId: this.instanceId,
|
|
865
|
+
providerSessionId: this.providerSessionId || null,
|
|
866
|
+
workspace: this.workingDir,
|
|
867
|
+
blockReason: args.blockReason,
|
|
868
|
+
emittedAfterFinalizationTimeout: args.emittedAfterFinalizationTimeout,
|
|
869
|
+
waitedMs: args.waitedMs,
|
|
870
|
+
maxWaitMs: COMPLETED_FINALIZATION_MAX_WAIT_MS,
|
|
871
|
+
adapterStatus: typeof args.latestStatus?.status === 'string' ? args.latestStatus.status : null,
|
|
872
|
+
latestVisibleStatus: args.latestVisibleStatus,
|
|
873
|
+
parsedStatus: typeof parsed?.status === 'string' ? parsed.status : (parseError ? 'parse_error' : 'unknown'),
|
|
874
|
+
parseError: parseError || undefined,
|
|
875
|
+
finalAssistantPresent: this.completionHasFinalAssistantMessage(parsed?.messages),
|
|
876
|
+
visibleMessageCount: visibleMessages.length,
|
|
877
|
+
lastVisibleRole,
|
|
878
|
+
lastVisibleKind,
|
|
879
|
+
lastVisibleContentLength,
|
|
880
|
+
pendingStartedAt: this.generatingStartedAt || null,
|
|
881
|
+
pendingFirstObservedAt: args.pending.firstObservedAt,
|
|
882
|
+
pendingTimestamp: args.pending.timestamp,
|
|
883
|
+
pendingDurationSec: args.pending.duration,
|
|
884
|
+
previousBlockReason: args.pending.loggedBlockReason || null,
|
|
885
|
+
};
|
|
744
886
|
}
|
|
745
887
|
|
|
746
888
|
private hasAdapterPendingResponse(): boolean {
|
|
@@ -765,32 +907,62 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
765
907
|
if (!isCliGeneratingLikeStatus(parsedRawStatus)) return false;
|
|
766
908
|
if (adapterRawStatus !== 'idle') return false;
|
|
767
909
|
if (hasNonEmptyCliModalButtons(parsedStatus?.activeModal ?? parsedStatus?.modal)) return false;
|
|
768
|
-
|
|
910
|
+
if (this.hasAdapterPendingResponse()) return false;
|
|
911
|
+
// Do not suppress when the adapter's raw response buffer is still non-empty.
|
|
912
|
+
// This catches the case where isWaitingForResponse has already flipped to false
|
|
913
|
+
// (so getPartialResponse() returns '') but the provider's native parser still
|
|
914
|
+
// reports generating because it's parsing buffered content. Suppressing the
|
|
915
|
+
// finalization block here would emit a false completion event while the provider
|
|
916
|
+
// session is still actively processing its response stream.
|
|
917
|
+
const adapterAny = this.adapter as any;
|
|
918
|
+
if (typeof adapterAny?.responseBuffer === 'string' && adapterAny.responseBuffer.trim()) return false;
|
|
919
|
+
return true;
|
|
769
920
|
}
|
|
770
921
|
|
|
771
|
-
private
|
|
772
|
-
if (latestVisibleStatus !== 'idle') return `status:${latestVisibleStatus}
|
|
922
|
+
private getCompletedFinalizationBlock(latestVisibleStatus: string): CompletedFinalizationBlock | null {
|
|
923
|
+
if (latestVisibleStatus !== 'idle') return { reason: `status:${latestVisibleStatus}`, terminal: true };
|
|
773
924
|
|
|
774
925
|
const adapterAny = this.adapter as any;
|
|
775
|
-
if (adapterAny?.isWaitingForResponse === true) return 'adapter_waiting_for_response';
|
|
776
|
-
if (adapterAny?.currentTurnScope) return 'adapter_turn_scope_active';
|
|
926
|
+
if (adapterAny?.isWaitingForResponse === true) return { reason: 'adapter_waiting_for_response', terminal: true };
|
|
927
|
+
if (adapterAny?.currentTurnScope) return { reason: 'adapter_turn_scope_active', terminal: true };
|
|
928
|
+
if (this.hasAdapterPendingResponse()) return { reason: 'adapter_pending_response', terminal: true };
|
|
777
929
|
|
|
778
930
|
const partial = typeof this.adapter.getPartialResponse === 'function'
|
|
779
931
|
? this.adapter.getPartialResponse()
|
|
780
932
|
: '';
|
|
781
|
-
if (typeof partial === 'string' && partial.trim()) return 'partial_response_pending';
|
|
933
|
+
if (typeof partial === 'string' && partial.trim()) return { reason: 'partial_response_pending', terminal: true };
|
|
782
934
|
|
|
783
935
|
let parsed: any;
|
|
784
936
|
try {
|
|
785
937
|
parsed = this.adapter.getScriptParsedStatus();
|
|
786
938
|
} catch (error: any) {
|
|
787
|
-
return `parse_error:${error?.message || String(error)}
|
|
939
|
+
return { reason: `parse_error:${error?.message || String(error)}` };
|
|
788
940
|
}
|
|
789
941
|
|
|
790
942
|
const parsedStatus = typeof parsed?.status === 'string' ? parsed.status : 'unknown';
|
|
791
|
-
if (parsedStatus !== 'idle')
|
|
792
|
-
|
|
793
|
-
|
|
943
|
+
if (parsedStatus !== 'idle') {
|
|
944
|
+
const adapterStatus = this.adapter.getStatus({ allowParse: false });
|
|
945
|
+
if (this.shouldSuppressStaleParsedBusyStatus(parsed, adapterStatus)) return null;
|
|
946
|
+
return { reason: `parsed_status:${parsedStatus}`, terminal: isCliGeneratingLikeStatus(parsedStatus) };
|
|
947
|
+
}
|
|
948
|
+
if (parsed?.activeModal || parsed?.modal) return { reason: 'parsed_modal_active', terminal: true };
|
|
949
|
+
if (!this.completionHasFinalAssistantMessage(parsed?.messages)) return { reason: 'missing_final_assistant' };
|
|
950
|
+
|
|
951
|
+
// Guard: if the screen still shows an approval/choice prompt as the last visible text,
|
|
952
|
+
// the turn is not complete even if the parsed status says idle and there is an assistant
|
|
953
|
+
// message. This catches the case where waiting_approval→idle transitions occur before
|
|
954
|
+
// the modal has been resolved (e.g. the PTY rendered the prompt but no button press fired).
|
|
955
|
+
try {
|
|
956
|
+
const screenText = typeof (this.adapter as any).getScreenText === 'function'
|
|
957
|
+
? String((this.adapter as any).getScreenText() || '')
|
|
958
|
+
: '';
|
|
959
|
+
if (screenText) {
|
|
960
|
+
const tailLines = screenText.split(/\r?\n/).slice(-16).join('\n');
|
|
961
|
+
if (looksLikeActiveApprovalPromptText(tailLines)) {
|
|
962
|
+
return { reason: 'screen_shows_approval_prompt', terminal: false };
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
} catch { /* defensive: screen text read is best-effort */ }
|
|
794
966
|
|
|
795
967
|
return null;
|
|
796
968
|
}
|
|
@@ -817,10 +989,11 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
817
989
|
return;
|
|
818
990
|
}
|
|
819
991
|
|
|
820
|
-
const
|
|
821
|
-
if (
|
|
992
|
+
const block = this.getCompletedFinalizationBlock(latestVisibleStatus);
|
|
993
|
+
if (block) {
|
|
994
|
+
const blockReason = block.reason;
|
|
822
995
|
const waitedMs = Date.now() - pending.firstObservedAt;
|
|
823
|
-
if (waitedMs < COMPLETED_FINALIZATION_MAX_WAIT_MS) {
|
|
996
|
+
if (block.terminal || waitedMs < COMPLETED_FINALIZATION_MAX_WAIT_MS) {
|
|
824
997
|
if (pending.loggedBlockReason !== blockReason) {
|
|
825
998
|
LOG.info('CLI', `[${this.type}] waiting to emit completed until transcript finalizes (${blockReason})`);
|
|
826
999
|
pending.loggedBlockReason = blockReason;
|
|
@@ -828,7 +1001,25 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
828
1001
|
this.scheduleCompletedDebounceFlush(COMPLETED_FINALIZATION_RETRY_MS);
|
|
829
1002
|
return;
|
|
830
1003
|
}
|
|
831
|
-
|
|
1004
|
+
const completionDiagnostic = this.buildCompletedFinalizationDiagnostic({
|
|
1005
|
+
blockReason,
|
|
1006
|
+
latestStatus,
|
|
1007
|
+
latestVisibleStatus,
|
|
1008
|
+
waitedMs,
|
|
1009
|
+
pending,
|
|
1010
|
+
emittedAfterFinalizationTimeout: true,
|
|
1011
|
+
});
|
|
1012
|
+
LOG.warn('CLI', `[${this.type}] emitting completed event after ${waitedMs}ms without finalized assistant turn (${blockReason})`);
|
|
1013
|
+
this.pushEvent({
|
|
1014
|
+
event: 'agent:generating_completed',
|
|
1015
|
+
chatTitle: pending.chatTitle,
|
|
1016
|
+
duration: pending.duration,
|
|
1017
|
+
timestamp: pending.timestamp,
|
|
1018
|
+
finalSummary: blockReason.startsWith('parsed_status:')
|
|
1019
|
+
? ''
|
|
1020
|
+
: extractFinalSummaryFromMessages(this.adapter?.getScriptParsedStatus()?.messages),
|
|
1021
|
+
completionDiagnostic,
|
|
1022
|
+
});
|
|
832
1023
|
this.completedDebouncePending = null;
|
|
833
1024
|
this.completedDebounceTimer = null;
|
|
834
1025
|
this.generatingStartedAt = 0;
|
|
@@ -853,15 +1044,46 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
853
1044
|
// Guard re-entry: onStatusChange/getState can observe the same modal multiple
|
|
854
1045
|
// times while the PTY absorbs the approval key. Without this flag, repeated
|
|
855
1046
|
// snapshots would write stray keys into the input once the modal dismisses.
|
|
856
|
-
|
|
1047
|
+
// However, Claude Code can present a second approval immediately after the
|
|
1048
|
+
// first. Resolve a changed modal signature even while the previous write is
|
|
1049
|
+
// still inside the short busy window.
|
|
1050
|
+
if (!autoApproveActive) {
|
|
1051
|
+
this.lastAutoApprovalSignature = '';
|
|
1052
|
+
return autoApproveActive;
|
|
1053
|
+
}
|
|
1054
|
+
const modal = adapterStatus.activeModal;
|
|
1055
|
+
// (fix) Do not auto-approve when no concrete modal/buttons are present.
|
|
1056
|
+
// Claude TUI flaps between paints; without this guard adapterStatus
|
|
1057
|
+
// could report status=waiting_approval with activeModal=null (or with
|
|
1058
|
+
// an empty buttons array briefly) and we'd still call
|
|
1059
|
+
// resolveModal(-1) — which used to type "1" into the prompt
|
|
1060
|
+
// repeatedly. Skip until a real modal is captured.
|
|
1061
|
+
const buttons = Array.isArray(modal?.buttons)
|
|
1062
|
+
? modal.buttons.map((b: any) => String(b || '').trim()).filter(Boolean)
|
|
1063
|
+
: [];
|
|
1064
|
+
if (!modal || buttons.length === 0) {
|
|
1065
|
+
return autoApproveActive;
|
|
1066
|
+
}
|
|
1067
|
+
const { index: buttonIndex, label: buttonLabel } = pickApprovalButton(buttons, this.provider);
|
|
1068
|
+
if (buttonIndex < 0) {
|
|
1069
|
+
// No positive button matched — don't pick a random index, just
|
|
1070
|
+
// surface the modal so the user can decide.
|
|
1071
|
+
return autoApproveActive;
|
|
1072
|
+
}
|
|
1073
|
+
const signature = [
|
|
1074
|
+
typeof modal?.message === 'string' ? modal.message.trim() : '',
|
|
1075
|
+
buttons.join('|'),
|
|
1076
|
+
buttonIndex,
|
|
1077
|
+
].join('::');
|
|
1078
|
+
if (!this.autoApproveBusy || signature !== this.lastAutoApprovalSignature) {
|
|
857
1079
|
this.autoApproveBusy = true;
|
|
1080
|
+
this.lastAutoApprovalSignature = signature;
|
|
858
1081
|
if (this.autoApproveBusyTimer) clearTimeout(this.autoApproveBusyTimer);
|
|
859
1082
|
this.autoApproveBusyTimer = setTimeout(() => {
|
|
860
1083
|
this.autoApproveBusy = false;
|
|
861
1084
|
this.autoApproveBusyTimer = null;
|
|
1085
|
+
this.lastAutoApprovalSignature = '';
|
|
862
1086
|
}, 2000);
|
|
863
|
-
const modal = adapterStatus.activeModal;
|
|
864
|
-
const { index: buttonIndex, label: buttonLabel } = pickApprovalButton(modal?.buttons, this.provider);
|
|
865
1087
|
this.recordAutoApproval(modal?.message, buttonLabel, now);
|
|
866
1088
|
setTimeout(() => {
|
|
867
1089
|
this.adapter.resolveModal(buttonIndex);
|
|
@@ -876,6 +1098,13 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
876
1098
|
// during long-running CLI sessions. Keep this path on adapter-owned light
|
|
877
1099
|
// state only; rich provider parsing is reserved for getState/read_chat.
|
|
878
1100
|
const adapterStatus = this.adapter.getStatus({ allowParse: false });
|
|
1101
|
+
const adapterProviderSessionId = normalizeProviderSessionId(
|
|
1102
|
+
this.provider,
|
|
1103
|
+
typeof adapterStatus?.providerSessionId === 'string' ? adapterStatus.providerSessionId : '',
|
|
1104
|
+
);
|
|
1105
|
+
if (adapterProviderSessionId) {
|
|
1106
|
+
this.promoteProviderSessionId(adapterProviderSessionId);
|
|
1107
|
+
}
|
|
879
1108
|
const parsedStatus = null;
|
|
880
1109
|
const rawStatus = adapterStatus.status;
|
|
881
1110
|
const autoApproveActive = this.maybeAutoApproveStatus(adapterStatus, now);
|
|
@@ -943,12 +1172,30 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
943
1172
|
}
|
|
944
1173
|
} else if (newStatus === 'idle' && (this.lastStatus === 'generating' || this.lastStatus === 'waiting_approval')) {
|
|
945
1174
|
const duration = this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1000) : 0;
|
|
946
|
-
// If debounce still pending (generating lasted < 1s), cancel both events
|
|
1175
|
+
// If debounce still pending (generating lasted < 1s), cancel both UI events.
|
|
1176
|
+
// Still emit agent:generating_completed so mesh orchestration can record
|
|
1177
|
+
// task_completed for direct dispatches that complete faster than the debounce.
|
|
947
1178
|
if (this.generatingDebouncePending) {
|
|
948
|
-
|
|
1179
|
+
const shortDurationMs = this.generatingStartedAt ? now - this.generatingStartedAt : 0;
|
|
1180
|
+
LOG.info('CLI', `[${this.type}] suppressed short generating (${shortDurationMs}ms)`);
|
|
949
1181
|
if (this.generatingDebounceTimer) { clearTimeout(this.generatingDebounceTimer); this.generatingDebounceTimer = null; }
|
|
950
1182
|
this.generatingDebouncePending = null;
|
|
951
1183
|
this.generatingStartedAt = 0;
|
|
1184
|
+
// Emit completion for mesh task association even though the UI generating
|
|
1185
|
+
// started/completed pair is suppressed (too short for visible UI update).
|
|
1186
|
+
let shortFinalSummary: string | undefined;
|
|
1187
|
+
try { shortFinalSummary = extractFinalSummaryFromMessages(this.adapter?.getScriptParsedStatus()?.messages); } catch { /* best-effort */ }
|
|
1188
|
+
this.pushEvent({
|
|
1189
|
+
event: 'agent:generating_completed',
|
|
1190
|
+
chatTitle,
|
|
1191
|
+
duration: 0,
|
|
1192
|
+
timestamp: now,
|
|
1193
|
+
finalSummary: shortFinalSummary,
|
|
1194
|
+
completionDiagnostic: {
|
|
1195
|
+
reason: 'short_generating_suppressed',
|
|
1196
|
+
shortDurationMs,
|
|
1197
|
+
},
|
|
1198
|
+
});
|
|
952
1199
|
} else {
|
|
953
1200
|
// Debounce completed, then require the rich transcript path that read_chat
|
|
954
1201
|
// uses to show an idle turn whose last user-facing message is assistant.
|
|
@@ -957,6 +1204,23 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
957
1204
|
}
|
|
958
1205
|
} else if (newStatus === 'idle' && this.lastStatus === 'starting') {
|
|
959
1206
|
this.pushEvent({ event: 'agent:ready', chatTitle, timestamp: now });
|
|
1207
|
+
} else if (newStatus === 'error') {
|
|
1208
|
+
if (this.generatingDebounceTimer) { clearTimeout(this.generatingDebounceTimer); this.generatingDebounceTimer = null; }
|
|
1209
|
+
this.generatingDebouncePending = null;
|
|
1210
|
+
if (this.completedDebounceTimer) { clearTimeout(this.completedDebounceTimer); this.completedDebounceTimer = null; }
|
|
1211
|
+
this.completedDebouncePending = null;
|
|
1212
|
+
this.errorMessage = adapterStatus.errorMessage || this.errorMessage;
|
|
1213
|
+
this.errorReason = (adapterStatus.errorReason as ProviderErrorReason) || this.errorReason;
|
|
1214
|
+
this.pushEvent({
|
|
1215
|
+
event: 'agent:stopped',
|
|
1216
|
+
chatTitle,
|
|
1217
|
+
timestamp: now,
|
|
1218
|
+
finalSummary: adapterStatus.errorMessage || adapterStatus.errorReason || 'Provider reported an error',
|
|
1219
|
+
completionDiagnostic: {
|
|
1220
|
+
reason: adapterStatus.errorReason || 'provider_error',
|
|
1221
|
+
errorMessage: adapterStatus.errorMessage || undefined,
|
|
1222
|
+
},
|
|
1223
|
+
});
|
|
960
1224
|
} else if (newStatus === 'stopped') {
|
|
961
1225
|
// Cancel any pending debounce
|
|
962
1226
|
if (this.generatingDebounceTimer) { clearTimeout(this.generatingDebounceTimer); this.generatingDebounceTimer = null; }
|
|
@@ -977,7 +1241,31 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
977
1241
|
// Monitor check (cooldown based notification, IDE/CLI common)
|
|
978
1242
|
const agentKey = `${this.type}:cli`;
|
|
979
1243
|
const monitorEvents = this.monitor.check(agentKey, newStatus, now, progressFingerprint);
|
|
1244
|
+
const monitorParsedStatus: any = parsedStatus;
|
|
980
1245
|
for (const me of monitorEvents) {
|
|
1246
|
+
if (
|
|
1247
|
+
me.type === 'monitor:long_generating'
|
|
1248
|
+
&& this.completionHasFinalAssistantMessage(monitorParsedStatus?.messages)
|
|
1249
|
+
&& !this.hasAdapterPendingResponse()
|
|
1250
|
+
&& !hasNonEmptyCliModalButtons(monitorParsedStatus?.activeModal ?? monitorParsedStatus?.modal)
|
|
1251
|
+
) {
|
|
1252
|
+
this.pushEvent({
|
|
1253
|
+
event: 'agent:generating_completed',
|
|
1254
|
+
chatTitle,
|
|
1255
|
+
duration: this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1000) : undefined,
|
|
1256
|
+
timestamp: me.timestamp,
|
|
1257
|
+
finalSummary: extractFinalSummaryFromMessages(monitorParsedStatus?.messages),
|
|
1258
|
+
completionDiagnostic: {
|
|
1259
|
+
providerType: this.type,
|
|
1260
|
+
sessionId: this.instanceId,
|
|
1261
|
+
providerSessionId: this.providerSessionId || null,
|
|
1262
|
+
reconciliationReason: 'long_generating_monitor_final_summary',
|
|
1263
|
+
finalAssistantPresent: true,
|
|
1264
|
+
},
|
|
1265
|
+
});
|
|
1266
|
+
this.generatingStartedAt = 0;
|
|
1267
|
+
continue;
|
|
1268
|
+
}
|
|
981
1269
|
this.pushEvent({ event: me.type, agentKey: me.agentKey, message: me.message, elapsedSec: me.elapsedSec, timestamp: me.timestamp });
|
|
982
1270
|
}
|
|
983
1271
|
}
|
|
@@ -1258,12 +1546,28 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1258
1546
|
index,
|
|
1259
1547
|
source: 'parsed',
|
|
1260
1548
|
}));
|
|
1549
|
+
const getRole = (message: ChatMessage): string => typeof message.role === 'string'
|
|
1550
|
+
? message.role.trim().toLowerCase()
|
|
1551
|
+
: '';
|
|
1261
1552
|
const runtimeEntries: MergeEntry[] = this.runtimeMessages.map((entry, index) => ({
|
|
1262
1553
|
message: entry.message,
|
|
1263
1554
|
index: parsedMessages.length + index,
|
|
1264
|
-
source: 'runtime',
|
|
1555
|
+
source: 'runtime' as const,
|
|
1265
1556
|
runtimeKey: entry.key,
|
|
1266
|
-
}))
|
|
1557
|
+
})).filter((entry) => {
|
|
1558
|
+
const meta = entry.message.meta && typeof entry.message.meta === 'object' && !Array.isArray(entry.message.meta)
|
|
1559
|
+
? entry.message.meta as Record<string, unknown>
|
|
1560
|
+
: {};
|
|
1561
|
+
if (meta.runtimeInputAck !== true) return true;
|
|
1562
|
+
const runtimeText = flattenContent(entry.message.content).replace(/\s+/g, ' ').trim();
|
|
1563
|
+
if (!runtimeText) return false;
|
|
1564
|
+
return !parsedEntries.some((parsedEntry) => {
|
|
1565
|
+
const parsedRole = getRole(parsedEntry.message);
|
|
1566
|
+
if (parsedRole !== 'user' && parsedRole !== 'human') return false;
|
|
1567
|
+
const parsedText = flattenContent(parsedEntry.message.content).replace(/\s+/g, ' ').trim();
|
|
1568
|
+
return parsedText === runtimeText;
|
|
1569
|
+
});
|
|
1570
|
+
});
|
|
1267
1571
|
const getTime = (message: ChatMessage): number => {
|
|
1268
1572
|
const value = typeof message.receivedAt === 'number'
|
|
1269
1573
|
? message.receivedAt
|
|
@@ -1273,9 +1577,6 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1273
1577
|
return Number.isFinite(value) && value > 0 ? value : 0;
|
|
1274
1578
|
};
|
|
1275
1579
|
|
|
1276
|
-
const getRole = (message: ChatMessage): string => typeof message.role === 'string'
|
|
1277
|
-
? message.role.trim().toLowerCase()
|
|
1278
|
-
: '';
|
|
1279
1580
|
const isRuntimeOverlay = (entry: MergeEntry): boolean => {
|
|
1280
1581
|
if (entry.source !== 'runtime') return false;
|
|
1281
1582
|
const key = typeof entry.runtimeKey === 'string' ? entry.runtimeKey.trim().toLowerCase() : '';
|
|
@@ -1341,7 +1642,9 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1341
1642
|
this.providerSessionId = nextSessionId;
|
|
1342
1643
|
this.historyWriter.promoteHistorySession(this.type, previousHistorySessionId, nextSessionId);
|
|
1343
1644
|
this.historyWriter.writeSessionStart(this.type, nextSessionId, this.workingDir, this.instanceId);
|
|
1344
|
-
this.
|
|
1645
|
+
if (this.shouldHydrateExistingProviderHistory()) {
|
|
1646
|
+
this.restorePersistedHistoryFromCurrentSession();
|
|
1647
|
+
}
|
|
1345
1648
|
this.adapter.updateRuntimeMeta({ providerSessionId: nextSessionId });
|
|
1346
1649
|
this.onProviderSessionResolved?.({
|
|
1347
1650
|
instanceId: this.instanceId,
|
|
@@ -1354,6 +1657,24 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1354
1657
|
LOG.info('CLI', `[${this.type}] discovered provider session id: ${nextSessionId}`);
|
|
1355
1658
|
}
|
|
1356
1659
|
|
|
1660
|
+
private shouldHydrateExistingProviderHistory(): boolean {
|
|
1661
|
+
return this.launchMode === 'resume' || this.launchMode === 'manual';
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
private shouldSuppressFreshLaunchStartupReplay(parsedMessages: unknown[], parsedStatus: any, adapterStatus: any, parsedProviderSessionId = ''): boolean {
|
|
1665
|
+
if (this.launchMode !== 'new') return false;
|
|
1666
|
+
if (this.providerSessionId) return false;
|
|
1667
|
+
if (!Array.isArray(parsedMessages) || parsedMessages.length === 0) return false;
|
|
1668
|
+
if (!isIdleStatus(adapterStatus?.status) || !isIdleStatus(parsedStatus?.status)) return false;
|
|
1669
|
+
if (parsedProviderSessionId) return true;
|
|
1670
|
+
|
|
1671
|
+
const newestMessageAt = parsedMessages.reduce<number>((newest, message) => Math.max(newest, getMessageTime(message)), 0);
|
|
1672
|
+
|
|
1673
|
+
// Untimestamped idle parser output during a fresh launch is usually the
|
|
1674
|
+
// provider's last workspace transcript before a new turn exists.
|
|
1675
|
+
return newestMessageAt === 0;
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1357
1678
|
private syncCanonicalSavedHistoryIfNeeded(): boolean {
|
|
1358
1679
|
if (!this.providerSessionId) return false;
|
|
1359
1680
|
const canonicalHistory = this.provider.canonicalHistory;
|
|
@@ -15,6 +15,14 @@ import type { ChatMessageKind } from './chat-message-normalization.js';
|
|
|
15
15
|
export type ReadChatTurnStatus = 'open' | 'waiting_approval' | 'complete' | 'error';
|
|
16
16
|
|
|
17
17
|
export interface ReadChatResult {
|
|
18
|
+
/**
|
|
19
|
+
* Declared chat contract version. Absent or `'1.0'` → legacy v1 payload
|
|
20
|
+
* (current shape). `'2.0'` → v2 payload conforming to transcript-v2.ts
|
|
21
|
+
* (ReadChatResultV2). Validators in read-chat-contract.ts route on this
|
|
22
|
+
* field. A1 only adds the field; A2 will make v2 the daemon-internal
|
|
23
|
+
* canonical form and reject unrecognised versions at provider load time.
|
|
24
|
+
*/
|
|
25
|
+
contractVersion?: import('./transcript-v2.js').ChatContractVersion;
|
|
18
26
|
messages: ChatMessage[];
|
|
19
27
|
status: AgentStatus;
|
|
20
28
|
activeModal?: ModalInfo | null;
|
|
@@ -702,6 +710,17 @@ export interface ProviderCanonicalHistoryConfig {
|
|
|
702
710
|
* Omitted mode defaults to 'native-source'.
|
|
703
711
|
*/
|
|
704
712
|
mode?: 'native-source' | 'materialized-mirror' | 'disabled';
|
|
713
|
+
/**
|
|
714
|
+
* Chat transcript contract version this provider's read_chat output
|
|
715
|
+
* conforms to. See transcript-v2.ts for the v2 invariants. Absent or `'1.0'`
|
|
716
|
+
* → legacy v1 payload (current behaviour). `'2.0'` → strict v2 payload
|
|
717
|
+
* (stable providerUnitKey/bubbleId/sequence, strict enums, honest coverage).
|
|
718
|
+
*
|
|
719
|
+
* A1 only surfaces this field; validators in read-chat-contract.ts route
|
|
720
|
+
* on it. A2 makes v2 the daemon-internal canonical form and rejects
|
|
721
|
+
* unrecognised values at provider load time.
|
|
722
|
+
*/
|
|
723
|
+
contractVersion?: import('./transcript-v2.js').ChatContractVersion;
|
|
705
724
|
}
|
|
706
725
|
|
|
707
726
|
/**
|