@adhdev/daemon-core 0.9.82-rc.13 → 0.9.82-rc.130
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/subscription-updates.d.ts +1 -0
- package/dist/cli-adapter-types.d.ts +4 -1
- package/dist/cli-adapters/provider-cli-adapter.d.ts +25 -1
- package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +14 -0
- package/dist/commands/router.d.ts +22 -0
- package/dist/config/chat-history.d.ts +4 -0
- package/dist/config/mesh-config.d.ts +68 -1
- package/dist/git/git-commands.d.ts +5 -1
- package/dist/index.d.ts +15 -5
- package/dist/index.js +7461 -1380
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +7417 -1364
- 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 +18 -0
- package/dist/mesh/mesh-active-work.d.ts +73 -0
- package/dist/mesh/mesh-events.d.ts +54 -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 +38 -1
- package/dist/mesh/mesh-refine-status.d.ts +27 -0
- package/dist/mesh/mesh-work-queue.d.ts +27 -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 +115 -0
- package/dist/providers/chat-message-normalization.d.ts +1 -0
- package/dist/providers/cli-provider-instance.d.ts +6 -1
- package/dist/repo-mesh-types.d.ts +62 -0
- package/dist/status/reporter.d.ts +2 -0
- package/package.json +3 -1
- package/src/boot/daemon-lifecycle.ts +1 -0
- package/src/chat/subscription-updates.ts +5 -1
- package/src/cli-adapter-types.ts +2 -1
- package/src/cli-adapters/provider-cli-adapter.ts +473 -18
- package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/src/cli-adapters/provider-cli-parse.ts +4 -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 +32 -10
- package/src/commands/chat-commands.ts +960 -40
- package/src/commands/cli-manager.ts +138 -2
- package/src/commands/handler.ts +8 -1
- package/src/commands/mesh-coordinator.ts +13 -143
- package/src/commands/router.ts +3238 -423
- package/src/config/chat-history.ts +37 -9
- 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/index.ts +41 -4
- 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 +176 -0
- package/src/mesh/coordinator-prompt.ts +31 -8
- package/src/mesh/mesh-active-work.ts +295 -0
- package/src/mesh/mesh-events.ts +595 -48
- package/src/mesh/mesh-fast-forward.ts +430 -0
- package/src/mesh/mesh-host-ownership.ts +73 -0
- package/src/mesh/mesh-ledger.ts +138 -1
- package/src/mesh/mesh-refine-status.ts +145 -0
- package/src/mesh/mesh-work-queue.ts +199 -137
- package/src/mesh/preview-freshness.ts +118 -0
- package/src/mesh/refine-config.ts +366 -0
- package/src/mesh/worktree-bootstrap-config.ts +234 -0
- package/src/providers/approval-utils.ts +12 -5
- package/src/providers/chat-message-normalization.ts +7 -12
- package/src/providers/cli-provider-instance.ts +289 -36
- package/src/providers/ide-provider-instance.ts +17 -3
- package/src/providers/provider-loader.ts +10 -4
- package/src/providers/read-chat-contract.ts +1 -1
- package/src/providers/version-archive.ts +38 -20
- package/src/repo-mesh-types.ts +67 -0
- package/src/status/reporter.ts +15 -0
- package/src/system/host-memory.ts +29 -12
|
@@ -43,6 +43,23 @@ 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
|
+
function getMessageTime(message: unknown): number {
|
|
52
|
+
if (!message || typeof message !== 'object') return 0;
|
|
53
|
+
const record = message as { receivedAt?: unknown; timestamp?: unknown };
|
|
54
|
+
const value = Number(record.receivedAt ?? record.timestamp ?? 0);
|
|
55
|
+
return Number.isFinite(value) ? value : 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type CompletedFinalizationBlock = {
|
|
59
|
+
reason: string;
|
|
60
|
+
terminal?: boolean;
|
|
61
|
+
};
|
|
62
|
+
|
|
46
63
|
const COMPLETED_FINALIZATION_RETRY_MS = 1000;
|
|
47
64
|
const COMPLETED_FINALIZATION_MAX_WAIT_MS = 30_000;
|
|
48
65
|
|
|
@@ -312,6 +329,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
312
329
|
private lastApprovalEventAt = 0;
|
|
313
330
|
private autoApproveBusy = false;
|
|
314
331
|
private autoApproveBusyTimer: NodeJS.Timeout | null = null;
|
|
332
|
+
private lastAutoApprovalSignature = '';
|
|
315
333
|
private controlValues: Record<string, string | number | boolean> = {};
|
|
316
334
|
private summaryMetadata: unknown = undefined;
|
|
317
335
|
private appliedEffectKeys = new Set<string>();
|
|
@@ -412,7 +430,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
412
430
|
await this.adapter.spawn();
|
|
413
431
|
await this.enforceFreshSessionLaunchIfNeeded();
|
|
414
432
|
this.maybeAppendRuntimeRecoveryMessage(this.adapter.getRuntimeMetadata());
|
|
415
|
-
if (this.providerSessionId) {
|
|
433
|
+
if (this.providerSessionId && this.shouldHydrateExistingProviderHistory()) {
|
|
416
434
|
this.restorePersistedHistoryFromCurrentSession();
|
|
417
435
|
}
|
|
418
436
|
if (this.providerSessionId && this.launchMode === 'resume') {
|
|
@@ -492,8 +510,14 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
492
510
|
if (typeof this.adapter.getScriptParsedStatus === 'function') {
|
|
493
511
|
try {
|
|
494
512
|
parsedStatus = this.adapter.getScriptParsedStatus() || null;
|
|
495
|
-
|
|
496
|
-
|
|
513
|
+
const parsedErrorMessage = typeof parsedStatus?.errorMessage === 'string' && parsedStatus.errorMessage.trim()
|
|
514
|
+
? parsedStatus.errorMessage.trim()
|
|
515
|
+
: undefined;
|
|
516
|
+
const parsedErrorReason = typeof parsedStatus?.errorReason === 'string' && parsedStatus.errorReason.trim()
|
|
517
|
+
? parsedStatus.errorReason.trim() as ProviderErrorReason
|
|
518
|
+
: undefined;
|
|
519
|
+
this.errorMessage = parsedErrorMessage;
|
|
520
|
+
this.errorReason = parsedErrorReason;
|
|
497
521
|
} catch (error: any) {
|
|
498
522
|
parseErrorMessage = error?.message || String(error);
|
|
499
523
|
this.errorMessage = parseErrorMessage;
|
|
@@ -503,22 +527,39 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
503
527
|
this.errorMessage = undefined;
|
|
504
528
|
this.errorReason = undefined;
|
|
505
529
|
}
|
|
530
|
+
const adapterProviderSessionId = normalizeProviderSessionId(
|
|
531
|
+
this.provider,
|
|
532
|
+
typeof adapterStatus?.providerSessionId === 'string' ? adapterStatus.providerSessionId : '',
|
|
533
|
+
);
|
|
506
534
|
const autoApproveActive = this.maybeAutoApproveStatus(adapterStatus, Date.now());
|
|
507
|
-
const visibleStatus = parseErrorMessage
|
|
535
|
+
const visibleStatus = parseErrorMessage || parsedStatus?.status === 'error'
|
|
508
536
|
? 'error'
|
|
509
537
|
: (autoApproveActive ? 'generating' : adapterStatus.status);
|
|
538
|
+
const runtime = this.adapter.getRuntimeMetadata();
|
|
539
|
+
this.maybeAppendRuntimeRecoveryMessage(runtime);
|
|
540
|
+
let parsedMessages = Array.isArray(parsedStatus?.messages)
|
|
541
|
+
? parsedStatus.messages
|
|
542
|
+
: [];
|
|
510
543
|
const parsedProviderSessionId = normalizeProviderSessionId(
|
|
511
544
|
this.provider,
|
|
512
545
|
typeof parsedStatus?.providerSessionId === 'string' ? parsedStatus.providerSessionId : '',
|
|
513
546
|
);
|
|
514
|
-
|
|
547
|
+
const suppressFreshLaunchStartupReplay = this.shouldSuppressFreshLaunchStartupReplay(
|
|
548
|
+
parsedMessages,
|
|
549
|
+
parsedStatus,
|
|
550
|
+
adapterStatus,
|
|
551
|
+
parsedProviderSessionId,
|
|
552
|
+
);
|
|
553
|
+
if (adapterProviderSessionId && !suppressFreshLaunchStartupReplay) {
|
|
554
|
+
this.promoteProviderSessionId(adapterProviderSessionId);
|
|
555
|
+
}
|
|
556
|
+
if (parsedProviderSessionId && !suppressFreshLaunchStartupReplay) {
|
|
515
557
|
this.promoteProviderSessionId(parsedProviderSessionId);
|
|
516
558
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
: [];
|
|
559
|
+
if (suppressFreshLaunchStartupReplay) {
|
|
560
|
+
parsedMessages = [];
|
|
561
|
+
}
|
|
562
|
+
const activeChatId = this.providerSessionId || runtime?.runtimeId || this.instanceId;
|
|
522
563
|
const historyMessageCount = Number.isFinite(parsedStatus?.historyMessageCount)
|
|
523
564
|
? Math.max(0, Number(parsedStatus.historyMessageCount))
|
|
524
565
|
: null;
|
|
@@ -528,7 +569,18 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
528
569
|
: [];
|
|
529
570
|
}
|
|
530
571
|
const mergedMessages = this.mergeConversationMessages(parsedMessages);
|
|
531
|
-
const canonicalBackedHistory = this.
|
|
572
|
+
const canonicalBackedHistory = this.shouldHydrateExistingProviderHistory()
|
|
573
|
+
? this.syncCanonicalSavedHistoryIfNeeded()
|
|
574
|
+
: false;
|
|
575
|
+
const statusMessages = canonicalBackedHistory && this.lastPersistedHistoryMessages.length > 0
|
|
576
|
+
? this.lastPersistedHistoryMessages.map((message) => ({
|
|
577
|
+
role: message.role,
|
|
578
|
+
content: message.content,
|
|
579
|
+
kind: message.kind,
|
|
580
|
+
senderName: message.senderName,
|
|
581
|
+
receivedAt: message.receivedAt,
|
|
582
|
+
}))
|
|
583
|
+
: mergedMessages;
|
|
532
584
|
|
|
533
585
|
const dirName = this.workingDir.split('/').filter(Boolean).pop() || 'session';
|
|
534
586
|
const parsedChatStatus = typeof parsedStatus?.status === 'string' && parsedStatus.status.trim()
|
|
@@ -572,7 +624,12 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
572
624
|
}
|
|
573
625
|
}
|
|
574
626
|
|
|
575
|
-
this.applyProviderResponse(
|
|
627
|
+
this.applyProviderResponse(
|
|
628
|
+
suppressFreshLaunchStartupReplay && parsedStatus && typeof parsedStatus === 'object'
|
|
629
|
+
? { ...parsedStatus, providerSessionId: undefined }
|
|
630
|
+
: parsedStatus,
|
|
631
|
+
{ phase: 'immediate' },
|
|
632
|
+
);
|
|
576
633
|
const surface = resolveProviderStateSurface({
|
|
577
634
|
summaryMetadata: this.summaryMetadata as any,
|
|
578
635
|
controlValues: this.controlValues,
|
|
@@ -592,10 +649,10 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
592
649
|
status: visibleStatus,
|
|
593
650
|
mode: this.presentationMode,
|
|
594
651
|
activeChat: {
|
|
595
|
-
id:
|
|
652
|
+
id: activeChatId,
|
|
596
653
|
title: parsedStatus?.title || dirName,
|
|
597
654
|
status: activeChatStatus,
|
|
598
|
-
messages:
|
|
655
|
+
messages: statusMessages,
|
|
599
656
|
activeModal: autoApproveActive ? null : (parsedStatus?.activeModal ?? adapterStatus.activeModal),
|
|
600
657
|
inputContent: '',
|
|
601
658
|
},
|
|
@@ -695,6 +752,34 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
695
752
|
}
|
|
696
753
|
}
|
|
697
754
|
|
|
755
|
+
recordAcknowledgedUserInput(input: InputEnvelope | string): void {
|
|
756
|
+
const content = typeof input === 'string'
|
|
757
|
+
? input.trim()
|
|
758
|
+
: buildCliStructuredInputPrompt(input).trim();
|
|
759
|
+
if (!content) return;
|
|
760
|
+
|
|
761
|
+
const receivedAt = Date.now();
|
|
762
|
+
const dedupKey = `user_input_ack:${crypto
|
|
763
|
+
.createHash('sha256')
|
|
764
|
+
.update(`${this.instanceId}:${content}:${receivedAt}`)
|
|
765
|
+
.digest('hex')
|
|
766
|
+
.slice(0, 24)}`;
|
|
767
|
+
this.appendRuntimeMessage(buildChatMessage({
|
|
768
|
+
role: 'user',
|
|
769
|
+
senderName: 'User',
|
|
770
|
+
kind: 'standard',
|
|
771
|
+
content,
|
|
772
|
+
receivedAt,
|
|
773
|
+
timestamp: receivedAt,
|
|
774
|
+
source: 'runtime_input_ack',
|
|
775
|
+
meta: {
|
|
776
|
+
runtimeInputAck: true,
|
|
777
|
+
provider: this.type,
|
|
778
|
+
workspace: this.workingDir,
|
|
779
|
+
},
|
|
780
|
+
} as ChatMessage), dedupKey);
|
|
781
|
+
}
|
|
782
|
+
|
|
698
783
|
dispose(): void {
|
|
699
784
|
this.adapter.shutdown();
|
|
700
785
|
this.monitor.reset();
|
|
@@ -743,6 +828,55 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
743
828
|
return role === 'assistant' && !!content;
|
|
744
829
|
}
|
|
745
830
|
|
|
831
|
+
private buildCompletedFinalizationDiagnostic(args: {
|
|
832
|
+
blockReason: string;
|
|
833
|
+
latestStatus?: any;
|
|
834
|
+
latestVisibleStatus: string;
|
|
835
|
+
waitedMs: number;
|
|
836
|
+
pending: CompletedDebouncePending;
|
|
837
|
+
emittedAfterFinalizationTimeout: boolean;
|
|
838
|
+
}): Record<string, unknown> {
|
|
839
|
+
let parsed: any = null;
|
|
840
|
+
let parseError: string | undefined;
|
|
841
|
+
try {
|
|
842
|
+
parsed = this.adapter.getScriptParsedStatus();
|
|
843
|
+
} catch (error: any) {
|
|
844
|
+
parseError = error?.message || String(error);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
const visibleMessages = (Array.isArray(parsed?.messages) ? parsed.messages : [])
|
|
848
|
+
.filter((message: any) => isUserFacingChatMessage(message as ChatMessage));
|
|
849
|
+
const lastVisible = visibleMessages[visibleMessages.length - 1] as ChatMessage | undefined;
|
|
850
|
+
const lastVisibleRole = typeof lastVisible?.role === 'string' ? lastVisible.role.trim().toLowerCase() : null;
|
|
851
|
+
const lastVisibleKind = typeof (lastVisible as any)?.kind === 'string' ? (lastVisible as any).kind : null;
|
|
852
|
+
const lastVisibleContentLength = lastVisible ? flattenContent(lastVisible.content).trim().length : 0;
|
|
853
|
+
|
|
854
|
+
return {
|
|
855
|
+
providerType: this.type,
|
|
856
|
+
sessionId: this.instanceId,
|
|
857
|
+
providerSessionId: this.providerSessionId || null,
|
|
858
|
+
workspace: this.workingDir,
|
|
859
|
+
blockReason: args.blockReason,
|
|
860
|
+
emittedAfterFinalizationTimeout: args.emittedAfterFinalizationTimeout,
|
|
861
|
+
waitedMs: args.waitedMs,
|
|
862
|
+
maxWaitMs: COMPLETED_FINALIZATION_MAX_WAIT_MS,
|
|
863
|
+
adapterStatus: typeof args.latestStatus?.status === 'string' ? args.latestStatus.status : null,
|
|
864
|
+
latestVisibleStatus: args.latestVisibleStatus,
|
|
865
|
+
parsedStatus: typeof parsed?.status === 'string' ? parsed.status : (parseError ? 'parse_error' : 'unknown'),
|
|
866
|
+
parseError: parseError || undefined,
|
|
867
|
+
finalAssistantPresent: this.completionHasFinalAssistantMessage(parsed?.messages),
|
|
868
|
+
visibleMessageCount: visibleMessages.length,
|
|
869
|
+
lastVisibleRole,
|
|
870
|
+
lastVisibleKind,
|
|
871
|
+
lastVisibleContentLength,
|
|
872
|
+
pendingStartedAt: this.generatingStartedAt || null,
|
|
873
|
+
pendingFirstObservedAt: args.pending.firstObservedAt,
|
|
874
|
+
pendingTimestamp: args.pending.timestamp,
|
|
875
|
+
pendingDurationSec: args.pending.duration,
|
|
876
|
+
previousBlockReason: args.pending.loggedBlockReason || null,
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
|
|
746
880
|
private hasAdapterPendingResponse(): boolean {
|
|
747
881
|
const adapterAny = this.adapter as any;
|
|
748
882
|
if (adapterAny?.isWaitingForResponse === true) return true;
|
|
@@ -768,29 +902,34 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
768
902
|
return !this.hasAdapterPendingResponse();
|
|
769
903
|
}
|
|
770
904
|
|
|
771
|
-
private
|
|
772
|
-
if (latestVisibleStatus !== 'idle') return `status:${latestVisibleStatus}
|
|
905
|
+
private getCompletedFinalizationBlock(latestVisibleStatus: string): CompletedFinalizationBlock | null {
|
|
906
|
+
if (latestVisibleStatus !== 'idle') return { reason: `status:${latestVisibleStatus}`, terminal: true };
|
|
773
907
|
|
|
774
908
|
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';
|
|
909
|
+
if (adapterAny?.isWaitingForResponse === true) return { reason: 'adapter_waiting_for_response', terminal: true };
|
|
910
|
+
if (adapterAny?.currentTurnScope) return { reason: 'adapter_turn_scope_active', terminal: true };
|
|
911
|
+
if (this.hasAdapterPendingResponse()) return { reason: 'adapter_pending_response', terminal: true };
|
|
777
912
|
|
|
778
913
|
const partial = typeof this.adapter.getPartialResponse === 'function'
|
|
779
914
|
? this.adapter.getPartialResponse()
|
|
780
915
|
: '';
|
|
781
|
-
if (typeof partial === 'string' && partial.trim()) return 'partial_response_pending';
|
|
916
|
+
if (typeof partial === 'string' && partial.trim()) return { reason: 'partial_response_pending', terminal: true };
|
|
782
917
|
|
|
783
918
|
let parsed: any;
|
|
784
919
|
try {
|
|
785
920
|
parsed = this.adapter.getScriptParsedStatus();
|
|
786
921
|
} catch (error: any) {
|
|
787
|
-
return `parse_error:${error?.message || String(error)}
|
|
922
|
+
return { reason: `parse_error:${error?.message || String(error)}` };
|
|
788
923
|
}
|
|
789
924
|
|
|
790
925
|
const parsedStatus = typeof parsed?.status === 'string' ? parsed.status : 'unknown';
|
|
791
|
-
if (parsedStatus !== 'idle')
|
|
792
|
-
|
|
793
|
-
|
|
926
|
+
if (parsedStatus !== 'idle') {
|
|
927
|
+
const adapterStatus = this.adapter.getStatus({ allowParse: false });
|
|
928
|
+
if (this.shouldSuppressStaleParsedBusyStatus(parsed, adapterStatus)) return null;
|
|
929
|
+
return { reason: `parsed_status:${parsedStatus}`, terminal: isCliGeneratingLikeStatus(parsedStatus) };
|
|
930
|
+
}
|
|
931
|
+
if (parsed?.activeModal || parsed?.modal) return { reason: 'parsed_modal_active', terminal: true };
|
|
932
|
+
if (!this.completionHasFinalAssistantMessage(parsed?.messages)) return { reason: 'missing_final_assistant' };
|
|
794
933
|
|
|
795
934
|
return null;
|
|
796
935
|
}
|
|
@@ -817,10 +956,11 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
817
956
|
return;
|
|
818
957
|
}
|
|
819
958
|
|
|
820
|
-
const
|
|
821
|
-
if (
|
|
959
|
+
const block = this.getCompletedFinalizationBlock(latestVisibleStatus);
|
|
960
|
+
if (block) {
|
|
961
|
+
const blockReason = block.reason;
|
|
822
962
|
const waitedMs = Date.now() - pending.firstObservedAt;
|
|
823
|
-
if (waitedMs < COMPLETED_FINALIZATION_MAX_WAIT_MS) {
|
|
963
|
+
if (block.terminal || waitedMs < COMPLETED_FINALIZATION_MAX_WAIT_MS) {
|
|
824
964
|
if (pending.loggedBlockReason !== blockReason) {
|
|
825
965
|
LOG.info('CLI', `[${this.type}] waiting to emit completed until transcript finalizes (${blockReason})`);
|
|
826
966
|
pending.loggedBlockReason = blockReason;
|
|
@@ -828,7 +968,25 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
828
968
|
this.scheduleCompletedDebounceFlush(COMPLETED_FINALIZATION_RETRY_MS);
|
|
829
969
|
return;
|
|
830
970
|
}
|
|
831
|
-
|
|
971
|
+
const completionDiagnostic = this.buildCompletedFinalizationDiagnostic({
|
|
972
|
+
blockReason,
|
|
973
|
+
latestStatus,
|
|
974
|
+
latestVisibleStatus,
|
|
975
|
+
waitedMs,
|
|
976
|
+
pending,
|
|
977
|
+
emittedAfterFinalizationTimeout: true,
|
|
978
|
+
});
|
|
979
|
+
LOG.warn('CLI', `[${this.type}] emitting completed event after ${waitedMs}ms without finalized assistant turn (${blockReason})`);
|
|
980
|
+
this.pushEvent({
|
|
981
|
+
event: 'agent:generating_completed',
|
|
982
|
+
chatTitle: pending.chatTitle,
|
|
983
|
+
duration: pending.duration,
|
|
984
|
+
timestamp: pending.timestamp,
|
|
985
|
+
finalSummary: blockReason.startsWith('parsed_status:')
|
|
986
|
+
? ''
|
|
987
|
+
: extractFinalSummaryFromMessages(this.adapter?.getScriptParsedStatus()?.messages),
|
|
988
|
+
completionDiagnostic,
|
|
989
|
+
});
|
|
832
990
|
this.completedDebouncePending = null;
|
|
833
991
|
this.completedDebounceTimer = null;
|
|
834
992
|
this.generatingStartedAt = 0;
|
|
@@ -853,15 +1011,29 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
853
1011
|
// Guard re-entry: onStatusChange/getState can observe the same modal multiple
|
|
854
1012
|
// times while the PTY absorbs the approval key. Without this flag, repeated
|
|
855
1013
|
// snapshots would write stray keys into the input once the modal dismisses.
|
|
856
|
-
|
|
1014
|
+
// However, Claude Code can present a second approval immediately after the
|
|
1015
|
+
// first. Resolve a changed modal signature even while the previous write is
|
|
1016
|
+
// still inside the short busy window.
|
|
1017
|
+
if (!autoApproveActive) {
|
|
1018
|
+
this.lastAutoApprovalSignature = '';
|
|
1019
|
+
return autoApproveActive;
|
|
1020
|
+
}
|
|
1021
|
+
const modal = adapterStatus.activeModal;
|
|
1022
|
+
const { index: buttonIndex, label: buttonLabel } = pickApprovalButton(modal?.buttons, this.provider);
|
|
1023
|
+
const signature = [
|
|
1024
|
+
typeof modal?.message === 'string' ? modal.message.trim() : '',
|
|
1025
|
+
Array.isArray(modal?.buttons) ? modal.buttons.join('|') : '',
|
|
1026
|
+
buttonIndex,
|
|
1027
|
+
].join('::');
|
|
1028
|
+
if (!this.autoApproveBusy || signature !== this.lastAutoApprovalSignature) {
|
|
857
1029
|
this.autoApproveBusy = true;
|
|
1030
|
+
this.lastAutoApprovalSignature = signature;
|
|
858
1031
|
if (this.autoApproveBusyTimer) clearTimeout(this.autoApproveBusyTimer);
|
|
859
1032
|
this.autoApproveBusyTimer = setTimeout(() => {
|
|
860
1033
|
this.autoApproveBusy = false;
|
|
861
1034
|
this.autoApproveBusyTimer = null;
|
|
1035
|
+
this.lastAutoApprovalSignature = '';
|
|
862
1036
|
}, 2000);
|
|
863
|
-
const modal = adapterStatus.activeModal;
|
|
864
|
-
const { index: buttonIndex, label: buttonLabel } = pickApprovalButton(modal?.buttons, this.provider);
|
|
865
1037
|
this.recordAutoApproval(modal?.message, buttonLabel, now);
|
|
866
1038
|
setTimeout(() => {
|
|
867
1039
|
this.adapter.resolveModal(buttonIndex);
|
|
@@ -876,6 +1048,13 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
876
1048
|
// during long-running CLI sessions. Keep this path on adapter-owned light
|
|
877
1049
|
// state only; rich provider parsing is reserved for getState/read_chat.
|
|
878
1050
|
const adapterStatus = this.adapter.getStatus({ allowParse: false });
|
|
1051
|
+
const adapterProviderSessionId = normalizeProviderSessionId(
|
|
1052
|
+
this.provider,
|
|
1053
|
+
typeof adapterStatus?.providerSessionId === 'string' ? adapterStatus.providerSessionId : '',
|
|
1054
|
+
);
|
|
1055
|
+
if (adapterProviderSessionId) {
|
|
1056
|
+
this.promoteProviderSessionId(adapterProviderSessionId);
|
|
1057
|
+
}
|
|
879
1058
|
const parsedStatus = null;
|
|
880
1059
|
const rawStatus = adapterStatus.status;
|
|
881
1060
|
const autoApproveActive = this.maybeAutoApproveStatus(adapterStatus, now);
|
|
@@ -957,6 +1136,23 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
957
1136
|
}
|
|
958
1137
|
} else if (newStatus === 'idle' && this.lastStatus === 'starting') {
|
|
959
1138
|
this.pushEvent({ event: 'agent:ready', chatTitle, timestamp: now });
|
|
1139
|
+
} else if (newStatus === 'error') {
|
|
1140
|
+
if (this.generatingDebounceTimer) { clearTimeout(this.generatingDebounceTimer); this.generatingDebounceTimer = null; }
|
|
1141
|
+
this.generatingDebouncePending = null;
|
|
1142
|
+
if (this.completedDebounceTimer) { clearTimeout(this.completedDebounceTimer); this.completedDebounceTimer = null; }
|
|
1143
|
+
this.completedDebouncePending = null;
|
|
1144
|
+
this.errorMessage = adapterStatus.errorMessage || this.errorMessage;
|
|
1145
|
+
this.errorReason = (adapterStatus.errorReason as ProviderErrorReason) || this.errorReason;
|
|
1146
|
+
this.pushEvent({
|
|
1147
|
+
event: 'agent:stopped',
|
|
1148
|
+
chatTitle,
|
|
1149
|
+
timestamp: now,
|
|
1150
|
+
finalSummary: adapterStatus.errorMessage || adapterStatus.errorReason || 'Provider reported an error',
|
|
1151
|
+
completionDiagnostic: {
|
|
1152
|
+
reason: adapterStatus.errorReason || 'provider_error',
|
|
1153
|
+
errorMessage: adapterStatus.errorMessage || undefined,
|
|
1154
|
+
},
|
|
1155
|
+
});
|
|
960
1156
|
} else if (newStatus === 'stopped') {
|
|
961
1157
|
// Cancel any pending debounce
|
|
962
1158
|
if (this.generatingDebounceTimer) { clearTimeout(this.generatingDebounceTimer); this.generatingDebounceTimer = null; }
|
|
@@ -977,7 +1173,31 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
977
1173
|
// Monitor check (cooldown based notification, IDE/CLI common)
|
|
978
1174
|
const agentKey = `${this.type}:cli`;
|
|
979
1175
|
const monitorEvents = this.monitor.check(agentKey, newStatus, now, progressFingerprint);
|
|
1176
|
+
const monitorParsedStatus: any = parsedStatus;
|
|
980
1177
|
for (const me of monitorEvents) {
|
|
1178
|
+
if (
|
|
1179
|
+
me.type === 'monitor:long_generating'
|
|
1180
|
+
&& this.completionHasFinalAssistantMessage(monitorParsedStatus?.messages)
|
|
1181
|
+
&& !this.hasAdapterPendingResponse()
|
|
1182
|
+
&& !hasNonEmptyCliModalButtons(monitorParsedStatus?.activeModal ?? monitorParsedStatus?.modal)
|
|
1183
|
+
) {
|
|
1184
|
+
this.pushEvent({
|
|
1185
|
+
event: 'agent:generating_completed',
|
|
1186
|
+
chatTitle,
|
|
1187
|
+
duration: this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1000) : undefined,
|
|
1188
|
+
timestamp: me.timestamp,
|
|
1189
|
+
finalSummary: extractFinalSummaryFromMessages(monitorParsedStatus?.messages),
|
|
1190
|
+
completionDiagnostic: {
|
|
1191
|
+
providerType: this.type,
|
|
1192
|
+
sessionId: this.instanceId,
|
|
1193
|
+
providerSessionId: this.providerSessionId || null,
|
|
1194
|
+
reconciliationReason: 'long_generating_monitor_final_summary',
|
|
1195
|
+
finalAssistantPresent: true,
|
|
1196
|
+
},
|
|
1197
|
+
});
|
|
1198
|
+
this.generatingStartedAt = 0;
|
|
1199
|
+
continue;
|
|
1200
|
+
}
|
|
981
1201
|
this.pushEvent({ event: me.type, agentKey: me.agentKey, message: me.message, elapsedSec: me.elapsedSec, timestamp: me.timestamp });
|
|
982
1202
|
}
|
|
983
1203
|
}
|
|
@@ -1258,12 +1478,28 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1258
1478
|
index,
|
|
1259
1479
|
source: 'parsed',
|
|
1260
1480
|
}));
|
|
1481
|
+
const getRole = (message: ChatMessage): string => typeof message.role === 'string'
|
|
1482
|
+
? message.role.trim().toLowerCase()
|
|
1483
|
+
: '';
|
|
1261
1484
|
const runtimeEntries: MergeEntry[] = this.runtimeMessages.map((entry, index) => ({
|
|
1262
1485
|
message: entry.message,
|
|
1263
1486
|
index: parsedMessages.length + index,
|
|
1264
|
-
source: 'runtime',
|
|
1487
|
+
source: 'runtime' as const,
|
|
1265
1488
|
runtimeKey: entry.key,
|
|
1266
|
-
}))
|
|
1489
|
+
})).filter((entry) => {
|
|
1490
|
+
const meta = entry.message.meta && typeof entry.message.meta === 'object' && !Array.isArray(entry.message.meta)
|
|
1491
|
+
? entry.message.meta as Record<string, unknown>
|
|
1492
|
+
: {};
|
|
1493
|
+
if (meta.runtimeInputAck !== true) return true;
|
|
1494
|
+
const runtimeText = flattenContent(entry.message.content).replace(/\s+/g, ' ').trim();
|
|
1495
|
+
if (!runtimeText) return false;
|
|
1496
|
+
return !parsedEntries.some((parsedEntry) => {
|
|
1497
|
+
const parsedRole = getRole(parsedEntry.message);
|
|
1498
|
+
if (parsedRole !== 'user' && parsedRole !== 'human') return false;
|
|
1499
|
+
const parsedText = flattenContent(parsedEntry.message.content).replace(/\s+/g, ' ').trim();
|
|
1500
|
+
return parsedText === runtimeText;
|
|
1501
|
+
});
|
|
1502
|
+
});
|
|
1267
1503
|
const getTime = (message: ChatMessage): number => {
|
|
1268
1504
|
const value = typeof message.receivedAt === 'number'
|
|
1269
1505
|
? message.receivedAt
|
|
@@ -1273,9 +1509,6 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1273
1509
|
return Number.isFinite(value) && value > 0 ? value : 0;
|
|
1274
1510
|
};
|
|
1275
1511
|
|
|
1276
|
-
const getRole = (message: ChatMessage): string => typeof message.role === 'string'
|
|
1277
|
-
? message.role.trim().toLowerCase()
|
|
1278
|
-
: '';
|
|
1279
1512
|
const isRuntimeOverlay = (entry: MergeEntry): boolean => {
|
|
1280
1513
|
if (entry.source !== 'runtime') return false;
|
|
1281
1514
|
const key = typeof entry.runtimeKey === 'string' ? entry.runtimeKey.trim().toLowerCase() : '';
|
|
@@ -1341,7 +1574,9 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1341
1574
|
this.providerSessionId = nextSessionId;
|
|
1342
1575
|
this.historyWriter.promoteHistorySession(this.type, previousHistorySessionId, nextSessionId);
|
|
1343
1576
|
this.historyWriter.writeSessionStart(this.type, nextSessionId, this.workingDir, this.instanceId);
|
|
1344
|
-
this.
|
|
1577
|
+
if (this.shouldHydrateExistingProviderHistory()) {
|
|
1578
|
+
this.restorePersistedHistoryFromCurrentSession();
|
|
1579
|
+
}
|
|
1345
1580
|
this.adapter.updateRuntimeMeta({ providerSessionId: nextSessionId });
|
|
1346
1581
|
this.onProviderSessionResolved?.({
|
|
1347
1582
|
instanceId: this.instanceId,
|
|
@@ -1354,6 +1589,24 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1354
1589
|
LOG.info('CLI', `[${this.type}] discovered provider session id: ${nextSessionId}`);
|
|
1355
1590
|
}
|
|
1356
1591
|
|
|
1592
|
+
private shouldHydrateExistingProviderHistory(): boolean {
|
|
1593
|
+
return this.launchMode === 'resume' || this.launchMode === 'manual';
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
private shouldSuppressFreshLaunchStartupReplay(parsedMessages: unknown[], parsedStatus: any, adapterStatus: any, parsedProviderSessionId = ''): boolean {
|
|
1597
|
+
if (this.launchMode !== 'new') return false;
|
|
1598
|
+
if (this.providerSessionId) return false;
|
|
1599
|
+
if (!Array.isArray(parsedMessages) || parsedMessages.length === 0) return false;
|
|
1600
|
+
if (!isIdleStatus(adapterStatus?.status) || !isIdleStatus(parsedStatus?.status)) return false;
|
|
1601
|
+
if (parsedProviderSessionId) return true;
|
|
1602
|
+
|
|
1603
|
+
const newestMessageAt = parsedMessages.reduce<number>((newest, message) => Math.max(newest, getMessageTime(message)), 0);
|
|
1604
|
+
|
|
1605
|
+
// Untimestamped idle parser output during a fresh launch is usually the
|
|
1606
|
+
// provider's last workspace transcript before a new turn exists.
|
|
1607
|
+
return newestMessageAt === 0;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1357
1610
|
private syncCanonicalSavedHistoryIfNeeded(): boolean {
|
|
1358
1611
|
if (!this.providerSessionId) return false;
|
|
1359
1612
|
const canonicalHistory = this.provider.canonicalHistory;
|
|
@@ -43,6 +43,20 @@ type ReadChatPayload = {
|
|
|
43
43
|
[key: string]: unknown;
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
+
async function withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T> {
|
|
47
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
48
|
+
try {
|
|
49
|
+
return await Promise.race([
|
|
50
|
+
promise,
|
|
51
|
+
new Promise<never>((_, reject) => {
|
|
52
|
+
timer = setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
53
|
+
}),
|
|
54
|
+
]);
|
|
55
|
+
} finally {
|
|
56
|
+
if (timer) clearTimeout(timer);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
46
60
|
export class IdeProviderInstance implements ProviderInstance {
|
|
47
61
|
readonly type: string;
|
|
48
62
|
readonly category = 'ide' as const;
|
|
@@ -320,7 +334,7 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
320
334
|
if (webviewScript) {
|
|
321
335
|
const matchText = this.provider.webviewMatchText;
|
|
322
336
|
const matchFn = matchText ? (body: string) => body.includes(matchText) : undefined;
|
|
323
|
-
const webviewRaw = await cdp.evaluateInWebviewFrame(webviewScript, matchFn);
|
|
337
|
+
const webviewRaw = await withTimeout(cdp.evaluateInWebviewFrame(webviewScript, matchFn), 30000, 'evaluateInWebviewFrame');
|
|
324
338
|
if (webviewRaw) {
|
|
325
339
|
raw = typeof webviewRaw === 'string' ? (() => { try { return JSON.parse(webviewRaw); } catch { return null; } })() : webviewRaw;
|
|
326
340
|
}
|
|
@@ -331,7 +345,7 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
331
345
|
if (!raw) {
|
|
332
346
|
const readChatScript = this.getReadChatScript();
|
|
333
347
|
if (!readChatScript) return;
|
|
334
|
-
raw = await cdp.evaluate(readChatScript, 30000);
|
|
348
|
+
raw = await withTimeout(cdp.evaluate(readChatScript, 30000), 30000, 'evaluate.readChatScript');
|
|
335
349
|
if (typeof raw === 'string') {
|
|
336
350
|
try { raw = JSON.parse(raw); } catch { return; }
|
|
337
351
|
}
|
|
@@ -706,7 +720,7 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
706
720
|
);
|
|
707
721
|
|
|
708
722
|
LOG.info('IdeInstance', `[IdeInstance:${this.type}] autoApprove: executing resolveAction for "${targetButton}"`);
|
|
709
|
-
let rawResult = await cdp.evaluate(script, 10000);
|
|
723
|
+
let rawResult = await withTimeout(cdp.evaluate(script, 10000), 10000, 'evaluate.autoApprove');
|
|
710
724
|
if (typeof rawResult === 'string') {
|
|
711
725
|
try { rawResult = JSON.parse(rawResult); } catch { }
|
|
712
726
|
}
|
|
@@ -1067,13 +1067,17 @@ export class ProviderLoader {
|
|
|
1067
1067
|
awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 },
|
|
1068
1068
|
});
|
|
1069
1069
|
|
|
1070
|
+
let reloadTimer: ReturnType<typeof setTimeout> | null = null;
|
|
1070
1071
|
const handleChange = (filePath: string) => {
|
|
1071
1072
|
if (/[\/\\]fixtures[\/\\]/.test(filePath)) {
|
|
1072
1073
|
return;
|
|
1073
1074
|
}
|
|
1074
1075
|
if (filePath.endsWith('.js') || filePath.endsWith('.json')) {
|
|
1075
|
-
|
|
1076
|
-
|
|
1076
|
+
if (reloadTimer) clearTimeout(reloadTimer);
|
|
1077
|
+
reloadTimer = setTimeout(() => {
|
|
1078
|
+
this.log(`File changed: ${path.basename(filePath)}, reloading...`);
|
|
1079
|
+
this.reload();
|
|
1080
|
+
}, 300);
|
|
1077
1081
|
}
|
|
1078
1082
|
};
|
|
1079
1083
|
|
|
@@ -1130,7 +1134,9 @@ export class ProviderLoader {
|
|
|
1130
1134
|
return { updated: false };
|
|
1131
1135
|
}
|
|
1132
1136
|
const https = require('https') as typeof import('https');
|
|
1133
|
-
const {
|
|
1137
|
+
const { exec } = require('child_process') as typeof import('child_process');
|
|
1138
|
+
const { promisify } = require('util');
|
|
1139
|
+
const execAsync = promisify(exec);
|
|
1134
1140
|
|
|
1135
1141
|
const metaPath = path.join(this.upstreamDir, ProviderLoader.META_FILE);
|
|
1136
1142
|
let prevEtag = '';
|
|
@@ -1207,7 +1213,7 @@ export class ProviderLoader {
|
|
|
1207
1213
|
|
|
1208
1214
|
// Extract
|
|
1209
1215
|
fs.mkdirSync(tmpExtract, { recursive: true });
|
|
1210
|
-
|
|
1216
|
+
await execAsync(`tar -xzf "${tmpTar}" -C "${tmpExtract}"`, { timeout: 30000 });
|
|
1211
1217
|
|
|
1212
1218
|
// Tarball internal structure: adhdev-providers-main/ide/... → strip 1 level
|
|
1213
1219
|
const extracted = fs.readdirSync(tmpExtract);
|
|
@@ -2,7 +2,7 @@ import type { MessagePart, ModalInfo, ReadChatResult } from './contracts.js'
|
|
|
2
2
|
import { normalizeMessageParts } from './contracts.js'
|
|
3
3
|
import type { ChatBubbleState, ChatMessage } from '../types.js'
|
|
4
4
|
|
|
5
|
-
const VALID_STATUSES = ['idle', 'generating', 'waiting_approval', 'error', 'panel_hidden', 'streaming', 'long_generating'] as const
|
|
5
|
+
const VALID_STATUSES = ['idle', 'generating', 'waiting_approval', 'error', 'panel_hidden', 'starting', 'streaming', 'long_generating'] as const
|
|
6
6
|
const VALID_ROLES = ['user', 'assistant', 'system', 'human'] as const
|
|
7
7
|
const VALID_BUBBLE_STATES = ['draft', 'streaming', 'final', 'removed'] as const
|
|
8
8
|
const VALID_TURN_STATUSES = ['open', 'waiting_approval', 'complete', 'error'] as const
|