@adhdev/daemon-core 0.9.82-rc.18 → 0.9.82-rc.180
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/boot/process-hardening.d.ts +50 -0
- 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 +22 -2
- package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
- package/dist/cli-adapters/cli-state-engine.d.ts +178 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +106 -63
- package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +29 -0
- package/dist/cli-adapters/raw-terminal-io.d.ts +37 -0
- package/dist/commands/handler.d.ts +110 -0
- package/dist/commands/mesh-coordinator.d.ts +72 -1
- package/dist/commands/router.d.ts +22 -0
- package/dist/config/chat-history.d.ts +8 -0
- package/dist/config/config.d.ts +5 -0
- package/dist/config/mesh-config.d.ts +72 -1
- package/dist/git/git-commands.d.ts +5 -1
- package/dist/index.d.ts +35 -8
- package/dist/index.js +19937 -4800
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +20357 -5290
- package/dist/index.mjs.map +1 -1
- package/dist/installer.d.ts +1 -4
- package/dist/ipc/local-ipc-server.d.ts +91 -0
- 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-prompt.d.ts +30 -0
- package/dist/mesh/coordinator-registry.d.ts +59 -0
- package/dist/mesh/mesh-active-work.d.ts +90 -0
- package/dist/mesh/mesh-events.d.ts +97 -5
- package/dist/mesh/mesh-fast-forward.d.ts +41 -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 +54 -6
- 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 +13 -0
- package/dist/providers/cli-provider-instance.d.ts +15 -3
- package/dist/providers/contracts.d.ts +98 -5
- package/dist/providers/external-sources.d.ts +71 -0
- package/dist/providers/native-history/antigravity-cli-transcript.d.ts +100 -0
- package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
- package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
- package/dist/providers/native-history/dispatcher.d.ts +25 -0
- package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
- package/dist/providers/native-history/index.d.ts +13 -0
- package/dist/providers/provider-instance.d.ts +3 -0
- package/dist/providers/provider-loader.d.ts +26 -4
- package/dist/providers/provider-trust.d.ts +31 -0
- package/dist/providers/read-chat-contract.d.ts +29 -0
- package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
- package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +85 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +65 -0
- package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
- package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
- package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
- package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
- package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
- package/dist/providers/sdk/v1/index.d.ts +30 -0
- package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
- package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
- package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
- package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
- package/dist/providers/sdk/v1/types/common/index.d.ts +169 -0
- package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
- package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
- package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
- package/dist/providers/spec/adapter.d.ts +56 -0
- package/dist/providers/spec/cli-adapter.d.ts +95 -0
- package/dist/providers/spec/driver.d.ts +159 -0
- package/dist/providers/spec/evaluator.d.ts +47 -0
- package/dist/providers/spec/loader.d.ts +14 -0
- package/dist/providers/spec/native-history-executor.d.ts +39 -0
- package/dist/providers/spec/route.d.ts +4 -0
- package/dist/providers/spec/schema.gen.d.ts +511 -0
- package/dist/providers/spec/types.d.ts +212 -0
- package/dist/providers/transcript-v2.d.ts +176 -0
- package/dist/providers/types/interactive-prompt.d.ts +48 -0
- package/dist/repo-mesh-types.d.ts +102 -1
- package/dist/sessions/registry.d.ts +3 -0
- package/dist/shared-types.d.ts +42 -1
- 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 +7 -2
- package/src/agent-stream/poller.ts +2 -3
- package/src/boot/daemon-lifecycle.ts +17 -10
- package/src/boot/process-hardening.ts +89 -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 +21 -2
- package/src/cli-adapters/cli-script-runner.ts +421 -0
- package/src/cli-adapters/cli-state-engine.ts +1086 -0
- package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +670 -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 +59 -11
- package/src/cli-adapters/raw-terminal-io.ts +252 -0
- 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 +1597 -60
- package/src/commands/cli-manager.ts +214 -3
- package/src/commands/handler.ts +809 -2
- package/src/commands/mesh-coordinator.ts +231 -127
- package/src/commands/router.ts +3495 -502
- package/src/config/chat-history.ts +91 -24
- package/src/config/config.ts +12 -0
- package/src/config/mesh-config.ts +280 -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 +113 -7
- package/src/installer.d.ts +1 -1
- package/src/installer.ts +8 -6
- package/src/ipc/local-ipc-server.ts +278 -0
- 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 +486 -0
- package/src/mesh/contracts.ts +329 -0
- package/src/mesh/coordinator-prompt.ts +204 -30
- package/src/mesh/coordinator-registry.ts +121 -0
- package/src/mesh/mesh-active-work.ts +437 -0
- package/src/mesh/mesh-events.ts +982 -67
- package/src/mesh/mesh-fast-forward.ts +438 -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 +263 -159
- 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.d.ts +4 -0
- package/src/providers/approval-utils.ts +47 -5
- package/src/providers/chat-message-normalization.ts +4 -11
- package/src/providers/cli-provider-instance.ts +598 -55
- package/src/providers/contracts.ts +105 -5
- package/src/providers/external-sources.ts +218 -0
- package/src/providers/ide-provider-instance.ts +19 -5
- package/src/providers/native-history/antigravity-cli-transcript.ts +643 -0
- package/src/providers/native-history/claude-cli-transcript.ts +396 -0
- package/src/providers/native-history/codex-cli-transcript.ts +419 -0
- package/src/providers/native-history/dispatcher.ts +231 -0
- package/src/providers/native-history/hermes-cli-transcript.ts +230 -0
- package/src/providers/native-history/index.ts +30 -0
- package/src/providers/provider-instance.ts +3 -0
- package/src/providers/provider-loader.ts +585 -50
- package/src/providers/provider-schema.ts +31 -13
- package/src/providers/provider-trust.ts +114 -0
- package/src/providers/read-chat-contract.ts +76 -16
- package/src/providers/sdk/README.md +49 -0
- package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
- package/src/providers/sdk/v1/builders/cli/detect-status.ts +262 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval.ts +250 -0
- package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
- package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
- package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
- package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
- package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
- package/src/providers/sdk/v1/index.ts +152 -0
- package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
- package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
- package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
- package/src/providers/sdk/v1/schemas/cli/provider.schema.json +458 -0
- package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
- package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +125 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
- package/src/providers/sdk/v1/types/cli/index.ts +365 -0
- package/src/providers/sdk/v1/types/common/index.ts +210 -0
- package/src/providers/sdk/v1/validators/index.ts +19 -0
- package/src/providers/sdk/v1/validators/manifest.ts +110 -0
- package/src/providers/sdk/v1/validators/taint.ts +309 -0
- package/src/providers/spec/adapter.ts +168 -0
- package/src/providers/spec/cli-adapter.ts +453 -0
- package/src/providers/spec/driver.ts +540 -0
- package/src/providers/spec/evaluator.ts +298 -0
- package/src/providers/spec/loader.ts +130 -0
- package/src/providers/spec/native-history-executor.ts +706 -0
- package/src/providers/spec/route.ts +51 -0
- package/src/providers/spec/schema.gen.ts +511 -0
- package/src/providers/spec/schema.json +211 -0
- package/src/providers/spec/types.ts +231 -0
- package/src/providers/transcript-v2.ts +567 -0
- package/src/providers/types/interactive-prompt.ts +297 -0
- package/src/providers/version-archive.ts +38 -20
- package/src/repo-mesh-types.ts +112 -1
- package/src/sessions/registry.ts +3 -0
- package/src/shared-types.ts +45 -1
- package/src/status/builders.ts +24 -6
- package/src/status/reporter.ts +15 -0
- package/src/status/snapshot.ts +84 -25
- package/src/system/host-memory.ts +29 -12
- package/src/types.ts +5 -0
- package/dist/mesh/mesh-sync.d.ts +0 -53
- package/src/mesh/mesh-sync.ts +0 -111
|
@@ -13,15 +13,17 @@ import { createRequire } from 'node:module';
|
|
|
13
13
|
import { normalizeInputEnvelope, type ProviderModule, flattenContent, type InputEnvelope, type InputPart } from './contracts.js';
|
|
14
14
|
import { assertProviderSupportsDeclaredInput, getEffectiveMessageInputSupport } from './provider-input-support.js';
|
|
15
15
|
import type { ProviderInstance, ProviderState, ProviderEvent, InstanceContext, ProviderErrorReason, HotChatSessionState, SessionModalState } from './provider-instance.js';
|
|
16
|
+
import { normalizeInteractivePrompt, normalizeInteractivePromptResponse, type InteractivePrompt } from './types/interactive-prompt.js';
|
|
16
17
|
import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
|
|
17
18
|
import type { CliProviderModule } from '../cli-adapters/provider-cli-adapter.js';
|
|
19
|
+
import { createCliAdapter } from './spec/route.js';
|
|
18
20
|
import type { PtyRuntimeMetadata, PtyTransportFactory } from '../cli-adapters/pty-transport.js';
|
|
19
21
|
import { StatusMonitor } from './status-monitor.js';
|
|
20
22
|
import { ChatHistoryWriter, isNativeSourceCanonicalHistory, materializeProviderNativeHistory, readChatHistory, readProviderChatHistory } from '../config/chat-history.js';
|
|
21
23
|
import { LOG } from '../logging/logger.js';
|
|
22
24
|
import type { ChatMessage } from '../types.js';
|
|
23
25
|
import { buildPersistedProviderEffectMessage, normalizeProviderEffects } from './control-effects.js';
|
|
24
|
-
import { formatAutoApprovalMessage, pickApprovalButton } from './approval-utils.js';
|
|
26
|
+
import { formatAutoApprovalMessage, pickApprovalButton, pickAutoApprovalButton, looksLikeActiveApprovalPromptText } from './approval-utils.js';
|
|
25
27
|
import { getCliScriptCommand, parseCliScriptResult } from './cli-script-results.js';
|
|
26
28
|
import { mergeProviderPatchState, resolveProviderStateSurface } from './provider-patch-state.js';
|
|
27
29
|
import { normalizeProviderSessionId } from './provider-session-id.js';
|
|
@@ -40,7 +42,45 @@ type CompletedDebouncePending = {
|
|
|
40
42
|
duration: number;
|
|
41
43
|
timestamp: number;
|
|
42
44
|
firstObservedAt: number;
|
|
45
|
+
previousStatus: string;
|
|
43
46
|
loggedBlockReason?: string;
|
|
47
|
+
loggedTranscriptProbe?: boolean;
|
|
48
|
+
transcriptProbeHistory?: ExternalTranscriptProbe[];
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function isIdleStatus(value: unknown): boolean {
|
|
52
|
+
const status = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
53
|
+
return !status || status === 'idle' || status === 'ready';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
function getMessageTime(message: unknown): number {
|
|
58
|
+
if (!message || typeof message !== 'object') return 0;
|
|
59
|
+
const record = message as { receivedAt?: unknown; timestamp?: unknown };
|
|
60
|
+
const value = Number(record.receivedAt ?? record.timestamp ?? 0);
|
|
61
|
+
return Number.isFinite(value) ? value : 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
type CompletedFinalizationBlock = {
|
|
65
|
+
reason: string;
|
|
66
|
+
terminal?: boolean;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
type CompletionFinalAssistantEvidence = {
|
|
70
|
+
present: boolean;
|
|
71
|
+
messages: unknown[];
|
|
72
|
+
source: 'parsed' | 'external-native' | 'unavailable';
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
type ExternalTranscriptProbe = {
|
|
76
|
+
readAt: number;
|
|
77
|
+
msgCount: number;
|
|
78
|
+
lastRole: string | null;
|
|
79
|
+
lastKind: string | null;
|
|
80
|
+
contentLen: number;
|
|
81
|
+
sourcePath: string | null;
|
|
82
|
+
sourceMtimeMs: number | null;
|
|
83
|
+
mtimeAgeMs: number | null;
|
|
44
84
|
};
|
|
45
85
|
|
|
46
86
|
const COMPLETED_FINALIZATION_RETRY_MS = 1000;
|
|
@@ -309,9 +349,10 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
309
349
|
private monitor: StatusMonitor;
|
|
310
350
|
private generatingDebounceTimer: NodeJS.Timeout | null = null;
|
|
311
351
|
private generatingDebouncePending: { chatTitle: string; timestamp: number } | null = null;
|
|
312
|
-
private
|
|
352
|
+
private lastApprovalEventFingerprint = '';
|
|
313
353
|
private autoApproveBusy = false;
|
|
314
354
|
private autoApproveBusyTimer: NodeJS.Timeout | null = null;
|
|
355
|
+
private lastAutoApprovalSignature = '';
|
|
315
356
|
private controlValues: Record<string, string | number | boolean> = {};
|
|
316
357
|
private summaryMetadata: unknown = undefined;
|
|
317
358
|
private appliedEffectKeys = new Set<string>();
|
|
@@ -330,6 +371,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
330
371
|
private suppressIdleHistoryReplay = false;
|
|
331
372
|
private errorMessage: string | undefined = undefined;
|
|
332
373
|
private errorReason: ProviderErrorReason | undefined = undefined;
|
|
374
|
+
private activeInteractivePrompt: InteractivePrompt | null = null;
|
|
333
375
|
|
|
334
376
|
private presentationMode: 'terminal' | 'chat';
|
|
335
377
|
private providerSessionId?: string;
|
|
@@ -370,7 +412,10 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
370
412
|
this.providerSessionId = options?.providerSessionId;
|
|
371
413
|
this.launchMode = options?.launchMode || 'new';
|
|
372
414
|
this.onProviderSessionResolved = options?.onProviderSessionResolved;
|
|
373
|
-
this.adapter =
|
|
415
|
+
this.adapter = createCliAdapter(provider as CliProviderModule, workingDir, cliArgs, options?.extraEnv || {}, transportFactory) as ProviderCliAdapter;
|
|
416
|
+
if (this.providerSessionId) {
|
|
417
|
+
this.adapter.updateRuntimeMeta({ providerSessionId: this.providerSessionId });
|
|
418
|
+
}
|
|
374
419
|
this.monitor = new StatusMonitor();
|
|
375
420
|
this.historyWriter = new ChatHistoryWriter();
|
|
376
421
|
}
|
|
@@ -412,7 +457,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
412
457
|
await this.adapter.spawn();
|
|
413
458
|
await this.enforceFreshSessionLaunchIfNeeded();
|
|
414
459
|
this.maybeAppendRuntimeRecoveryMessage(this.adapter.getRuntimeMetadata());
|
|
415
|
-
if (this.providerSessionId) {
|
|
460
|
+
if (this.providerSessionId && this.shouldHydrateExistingProviderHistory()) {
|
|
416
461
|
this.restorePersistedHistoryFromCurrentSession();
|
|
417
462
|
}
|
|
418
463
|
if (this.providerSessionId && this.launchMode === 'resume') {
|
|
@@ -486,14 +531,38 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
486
531
|
}
|
|
487
532
|
|
|
488
533
|
getState(): ProviderState {
|
|
534
|
+
// TODO(phase5-sandbox): JS override scripts (detectStatus, parseApproval,
|
|
535
|
+
// parseSession) are currently invoked by CliScriptRunner.invoke() via direct
|
|
536
|
+
// function calls — the scripts run in the daemon process with full Node.js
|
|
537
|
+
// access and no resource limits.
|
|
538
|
+
//
|
|
539
|
+
// When Phase 5 lands, CliScriptRunner should route these calls through a
|
|
540
|
+
// SandboxedScriptRunner (see providers/sdk/v1/sandbox/script-runner.ts) so
|
|
541
|
+
// that each call gets a fresh isolated-vm context with a 50 ms CPU limit and
|
|
542
|
+
// a 32 MB memory cap. The execution path to change is:
|
|
543
|
+
// CliScriptRunner.invoke() → SandboxedScriptRunner.run(scriptSource, context)
|
|
544
|
+
//
|
|
545
|
+
// This getState() call-site is NOT where the change goes — the wiring belongs
|
|
546
|
+
// in cli-script-runner.ts (CliScriptRunner.detectStatus / parseApproval /
|
|
547
|
+
// parseSession), with provider-loader.ts updated to store script source strings
|
|
548
|
+
// alongside the loaded function references for extended-legacy providers.
|
|
489
549
|
const adapterStatus = this.adapter.getStatus();
|
|
550
|
+
if (Object.prototype.hasOwnProperty.call(adapterStatus, 'activeInteractivePrompt')) {
|
|
551
|
+
this.activeInteractivePrompt = adapterStatus.activeInteractivePrompt ?? null;
|
|
552
|
+
}
|
|
490
553
|
let parsedStatus: any = null;
|
|
491
554
|
let parseErrorMessage: string | undefined;
|
|
492
555
|
if (typeof this.adapter.getScriptParsedStatus === 'function') {
|
|
493
556
|
try {
|
|
494
557
|
parsedStatus = this.adapter.getScriptParsedStatus() || null;
|
|
495
|
-
|
|
496
|
-
|
|
558
|
+
const parsedErrorMessage = typeof parsedStatus?.errorMessage === 'string' && parsedStatus.errorMessage.trim()
|
|
559
|
+
? parsedStatus.errorMessage.trim()
|
|
560
|
+
: undefined;
|
|
561
|
+
const parsedErrorReason = typeof parsedStatus?.errorReason === 'string' && parsedStatus.errorReason.trim()
|
|
562
|
+
? parsedStatus.errorReason.trim() as ProviderErrorReason
|
|
563
|
+
: undefined;
|
|
564
|
+
this.errorMessage = parsedErrorMessage;
|
|
565
|
+
this.errorReason = parsedErrorReason;
|
|
497
566
|
} catch (error: any) {
|
|
498
567
|
parseErrorMessage = error?.message || String(error);
|
|
499
568
|
this.errorMessage = parseErrorMessage;
|
|
@@ -503,22 +572,39 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
503
572
|
this.errorMessage = undefined;
|
|
504
573
|
this.errorReason = undefined;
|
|
505
574
|
}
|
|
575
|
+
const adapterProviderSessionId = normalizeProviderSessionId(
|
|
576
|
+
this.provider,
|
|
577
|
+
typeof adapterStatus?.providerSessionId === 'string' ? adapterStatus.providerSessionId : '',
|
|
578
|
+
);
|
|
506
579
|
const autoApproveActive = this.maybeAutoApproveStatus(adapterStatus, Date.now());
|
|
507
|
-
const visibleStatus = parseErrorMessage
|
|
580
|
+
const visibleStatus = parseErrorMessage || parsedStatus?.status === 'error'
|
|
508
581
|
? 'error'
|
|
509
582
|
: (autoApproveActive ? 'generating' : adapterStatus.status);
|
|
583
|
+
const runtime = this.adapter.getRuntimeMetadata();
|
|
584
|
+
this.maybeAppendRuntimeRecoveryMessage(runtime);
|
|
585
|
+
let parsedMessages = Array.isArray(parsedStatus?.messages)
|
|
586
|
+
? parsedStatus.messages
|
|
587
|
+
: [];
|
|
510
588
|
const parsedProviderSessionId = normalizeProviderSessionId(
|
|
511
589
|
this.provider,
|
|
512
590
|
typeof parsedStatus?.providerSessionId === 'string' ? parsedStatus.providerSessionId : '',
|
|
513
591
|
);
|
|
514
|
-
|
|
592
|
+
const suppressFreshLaunchStartupReplay = this.shouldSuppressFreshLaunchStartupReplay(
|
|
593
|
+
parsedMessages,
|
|
594
|
+
parsedStatus,
|
|
595
|
+
adapterStatus,
|
|
596
|
+
parsedProviderSessionId,
|
|
597
|
+
);
|
|
598
|
+
if (adapterProviderSessionId && !suppressFreshLaunchStartupReplay) {
|
|
599
|
+
this.promoteProviderSessionId(adapterProviderSessionId);
|
|
600
|
+
}
|
|
601
|
+
if (parsedProviderSessionId && !suppressFreshLaunchStartupReplay) {
|
|
515
602
|
this.promoteProviderSessionId(parsedProviderSessionId);
|
|
516
603
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
: [];
|
|
604
|
+
if (suppressFreshLaunchStartupReplay) {
|
|
605
|
+
parsedMessages = [];
|
|
606
|
+
}
|
|
607
|
+
const activeChatId = this.providerSessionId || runtime?.runtimeId || this.instanceId;
|
|
522
608
|
const historyMessageCount = Number.isFinite(parsedStatus?.historyMessageCount)
|
|
523
609
|
? Math.max(0, Number(parsedStatus.historyMessageCount))
|
|
524
610
|
: null;
|
|
@@ -528,7 +614,18 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
528
614
|
: [];
|
|
529
615
|
}
|
|
530
616
|
const mergedMessages = this.mergeConversationMessages(parsedMessages);
|
|
531
|
-
const canonicalBackedHistory = this.
|
|
617
|
+
const canonicalBackedHistory = this.shouldHydrateExistingProviderHistory()
|
|
618
|
+
? this.syncCanonicalSavedHistoryIfNeeded()
|
|
619
|
+
: false;
|
|
620
|
+
const statusMessages = canonicalBackedHistory && this.lastPersistedHistoryMessages.length > 0
|
|
621
|
+
? this.lastPersistedHistoryMessages.map((message) => ({
|
|
622
|
+
role: message.role,
|
|
623
|
+
content: message.content,
|
|
624
|
+
kind: message.kind,
|
|
625
|
+
senderName: message.senderName,
|
|
626
|
+
receivedAt: message.receivedAt,
|
|
627
|
+
}))
|
|
628
|
+
: mergedMessages;
|
|
532
629
|
|
|
533
630
|
const dirName = this.workingDir.split('/').filter(Boolean).pop() || 'session';
|
|
534
631
|
const parsedChatStatus = typeof parsedStatus?.status === 'string' && parsedStatus.status.trim()
|
|
@@ -572,7 +669,12 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
572
669
|
}
|
|
573
670
|
}
|
|
574
671
|
|
|
575
|
-
this.applyProviderResponse(
|
|
672
|
+
this.applyProviderResponse(
|
|
673
|
+
suppressFreshLaunchStartupReplay && parsedStatus && typeof parsedStatus === 'object'
|
|
674
|
+
? { ...parsedStatus, providerSessionId: undefined }
|
|
675
|
+
: parsedStatus,
|
|
676
|
+
{ phase: 'immediate' },
|
|
677
|
+
);
|
|
576
678
|
const surface = resolveProviderStateSurface({
|
|
577
679
|
summaryMetadata: this.summaryMetadata as any,
|
|
578
680
|
controlValues: this.controlValues,
|
|
@@ -592,13 +694,15 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
592
694
|
status: visibleStatus,
|
|
593
695
|
mode: this.presentationMode,
|
|
594
696
|
activeChat: {
|
|
595
|
-
id:
|
|
697
|
+
id: activeChatId,
|
|
596
698
|
title: parsedStatus?.title || dirName,
|
|
597
699
|
status: activeChatStatus,
|
|
598
|
-
messages:
|
|
700
|
+
messages: statusMessages,
|
|
599
701
|
activeModal: autoApproveActive ? null : (parsedStatus?.activeModal ?? adapterStatus.activeModal),
|
|
702
|
+
activeInteractivePrompt: this.activeInteractivePrompt,
|
|
600
703
|
inputContent: '',
|
|
601
704
|
},
|
|
705
|
+
activeInteractivePrompt: this.activeInteractivePrompt,
|
|
602
706
|
workspace: this.workingDir,
|
|
603
707
|
instanceId: this.instanceId,
|
|
604
708
|
providerSessionId: this.providerSessionId,
|
|
@@ -651,13 +755,17 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
651
755
|
};
|
|
652
756
|
}
|
|
653
757
|
|
|
654
|
-
getSessionModalState(): SessionModalState {
|
|
655
|
-
const adapterStatus = this.adapter.getStatus({ allowParse:
|
|
758
|
+
getSessionModalState(sessionId?: string): SessionModalState {
|
|
759
|
+
const adapterStatus = this.adapter.getStatus({ allowParse: true });
|
|
656
760
|
const autoApproveActive = adapterStatus.status === 'waiting_approval' && this.shouldAutoApprove();
|
|
657
761
|
const visibleStatus = autoApproveActive ? 'generating' : adapterStatus.status;
|
|
658
762
|
const dirName = this.workingDir.split('/').filter(Boolean).pop() || 'session';
|
|
659
763
|
return {
|
|
660
|
-
|
|
764
|
+
// Honor the caller-supplied sessionId — InstanceMgr rejects the
|
|
765
|
+
// projection when projected.id !== requested sessionId, and
|
|
766
|
+
// this.instanceId is the manager's internal key, not the public
|
|
767
|
+
// sessionId the dashboard subscribes by.
|
|
768
|
+
id: sessionId ?? this.instanceId,
|
|
661
769
|
status: visibleStatus,
|
|
662
770
|
title: dirName,
|
|
663
771
|
activeModal: autoApproveActive ? null : adapterStatus.activeModal,
|
|
@@ -690,11 +798,65 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
690
798
|
void this.adapter.resolveAction(data).catch((e: any) => {
|
|
691
799
|
LOG.warn('CLI', `[${this.type}] resolve_action failed: ${e?.message || e}`);
|
|
692
800
|
});
|
|
801
|
+
} else if (event === 'interactive_prompt' && data) {
|
|
802
|
+
const prompt = normalizeInteractivePrompt(data);
|
|
803
|
+
if (prompt) {
|
|
804
|
+
this.activeInteractivePrompt = prompt;
|
|
805
|
+
this.events.push({
|
|
806
|
+
event: 'interactive_prompt',
|
|
807
|
+
timestamp: Date.now(),
|
|
808
|
+
promptId: prompt.promptId,
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
} else if (event === 'interactive_prompt_response' && data) {
|
|
812
|
+
try {
|
|
813
|
+
const response = normalizeInteractivePromptResponse(data);
|
|
814
|
+
if (this.activeInteractivePrompt?.promptId === response.promptId) {
|
|
815
|
+
this.activeInteractivePrompt = null;
|
|
816
|
+
}
|
|
817
|
+
if (typeof this.adapter.setInteractivePromptResponse !== 'function') {
|
|
818
|
+
LOG.warn('CLI', `[${this.type}] interactive_prompt_response ignored: adapter does not support interactive prompts`);
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
void this.adapter.setInteractivePromptResponse(response).catch((e: any) => {
|
|
822
|
+
LOG.warn('CLI', `[${this.type}] interactive_prompt_response failed: ${e?.message || e}`);
|
|
823
|
+
});
|
|
824
|
+
} catch (e: any) {
|
|
825
|
+
LOG.warn('CLI', `[${this.type}] invalid interactive_prompt_response: ${e?.message || e}`);
|
|
826
|
+
}
|
|
693
827
|
} else if (event === 'provider_state_patch' && data && typeof data === 'object') {
|
|
694
828
|
this.applyProviderResponse(data, { phase: 'immediate' });
|
|
695
829
|
}
|
|
696
830
|
}
|
|
697
831
|
|
|
832
|
+
recordAcknowledgedUserInput(input: InputEnvelope | string): void {
|
|
833
|
+
const content = typeof input === 'string'
|
|
834
|
+
? input.trim()
|
|
835
|
+
: buildCliStructuredInputPrompt(input).trim();
|
|
836
|
+
if (!content) return;
|
|
837
|
+
|
|
838
|
+
const receivedAt = Date.now();
|
|
839
|
+
const dedupKey = `user_input_ack:${crypto
|
|
840
|
+
.createHash('sha256')
|
|
841
|
+
.update(`${this.instanceId}:${content}:${receivedAt}`)
|
|
842
|
+
.digest('hex')
|
|
843
|
+
.slice(0, 24)}`;
|
|
844
|
+
this.appendRuntimeMessage(buildChatMessage({
|
|
845
|
+
role: 'user',
|
|
846
|
+
senderName: 'User',
|
|
847
|
+
kind: 'standard',
|
|
848
|
+
content,
|
|
849
|
+
receivedAt,
|
|
850
|
+
timestamp: receivedAt,
|
|
851
|
+
source: 'runtime_input_ack',
|
|
852
|
+
meta: {
|
|
853
|
+
runtimeInputAck: true,
|
|
854
|
+
provider: this.type,
|
|
855
|
+
workspace: this.workingDir,
|
|
856
|
+
},
|
|
857
|
+
} as ChatMessage), dedupKey);
|
|
858
|
+
}
|
|
859
|
+
|
|
698
860
|
dispose(): void {
|
|
699
861
|
this.adapter.shutdown();
|
|
700
862
|
this.monitor.reset();
|
|
@@ -706,6 +868,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
706
868
|
|
|
707
869
|
private completedDebounceTimer: NodeJS.Timeout | null = null;
|
|
708
870
|
private completedDebouncePending: CompletedDebouncePending | null = null;
|
|
871
|
+
private lastExternalCompletionProbe: ExternalTranscriptProbe | null = null;
|
|
709
872
|
|
|
710
873
|
private async enforceFreshSessionLaunchIfNeeded(): Promise<void> {
|
|
711
874
|
const scriptName = getForcedNewSessionScriptName(this.provider, this.launchMode);
|
|
@@ -740,7 +903,157 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
740
903
|
const lastVisible = visibleMessages[visibleMessages.length - 1] as ChatMessage | undefined;
|
|
741
904
|
const role = typeof lastVisible?.role === 'string' ? lastVisible.role.trim().toLowerCase() : '';
|
|
742
905
|
const content = lastVisible ? flattenContent(lastVisible.content).trim() : '';
|
|
743
|
-
|
|
906
|
+
if (role !== 'assistant' || !content) return false;
|
|
907
|
+
// Guard: if the last assistant message looks like an active approval/input prompt,
|
|
908
|
+
// it is not a real completion — the session is still awaiting user input.
|
|
909
|
+
if (looksLikeActiveApprovalPromptText(content)) return false;
|
|
910
|
+
return true;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
private buildExternalTranscriptProbe(messages: unknown[], sourcePath?: string, sourceMtimeMs?: number): ExternalTranscriptProbe {
|
|
914
|
+
const visibleMessages = messages.filter((message: any) => isUserFacingChatMessage(message as ChatMessage));
|
|
915
|
+
const lastVisible = visibleMessages[visibleMessages.length - 1] as ChatMessage | undefined;
|
|
916
|
+
const readAt = Date.now();
|
|
917
|
+
const mtimeMs = Number(sourceMtimeMs) || 0;
|
|
918
|
+
return {
|
|
919
|
+
readAt,
|
|
920
|
+
msgCount: messages.length,
|
|
921
|
+
lastRole: typeof lastVisible?.role === 'string' ? lastVisible.role.trim().toLowerCase() : null,
|
|
922
|
+
lastKind: typeof (lastVisible as any)?.kind === 'string' ? (lastVisible as any).kind : null,
|
|
923
|
+
contentLen: lastVisible ? flattenContent(lastVisible.content).trim().length : 0,
|
|
924
|
+
sourcePath: typeof sourcePath === 'string' && sourcePath ? sourcePath : null,
|
|
925
|
+
sourceMtimeMs: mtimeMs || null,
|
|
926
|
+
mtimeAgeMs: mtimeMs ? Math.max(0, readAt - mtimeMs) : null,
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
private recordPendingTranscriptProbe(pending: CompletedDebouncePending): ExternalTranscriptProbe | null {
|
|
931
|
+
const probe = this.lastExternalCompletionProbe;
|
|
932
|
+
if (!probe) return null;
|
|
933
|
+
const history = pending.transcriptProbeHistory || [];
|
|
934
|
+
const last = history[history.length - 1];
|
|
935
|
+
if (!last || last.readAt !== probe.readAt || last.msgCount !== probe.msgCount || last.lastRole !== probe.lastRole || last.contentLen !== probe.contentLen) {
|
|
936
|
+
history.push(probe);
|
|
937
|
+
pending.transcriptProbeHistory = history.slice(-5);
|
|
938
|
+
}
|
|
939
|
+
return probe;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
private readExternalCompletionMessages(): unknown[] | null {
|
|
943
|
+
const adapterOwnsMessagesElsewhere = (this.adapter as any)?.chatMessagesOwnedExternally === true;
|
|
944
|
+
if (!adapterOwnsMessagesElsewhere) return null;
|
|
945
|
+
if (!this.providerSessionId) return null;
|
|
946
|
+
if (!isNativeSourceCanonicalHistory(this.provider.nativeHistory)) return null;
|
|
947
|
+
|
|
948
|
+
if (this.lastExternalCompletionProbe?.sourcePath) {
|
|
949
|
+
try { fs.statSync(this.lastExternalCompletionProbe.sourcePath); } catch { /* best-effort metadata refresh */ }
|
|
950
|
+
}
|
|
951
|
+
const restoredHistory = readProviderChatHistory(this.type, {
|
|
952
|
+
canonicalHistory: this.provider.nativeHistory,
|
|
953
|
+
historySessionId: this.providerSessionId,
|
|
954
|
+
workspace: this.workingDir,
|
|
955
|
+
offset: 0,
|
|
956
|
+
limit: Number.MAX_SAFE_INTEGER,
|
|
957
|
+
historyBehavior: this.provider.historyBehavior,
|
|
958
|
+
scripts: this.provider.scripts as any,
|
|
959
|
+
sessionStartedAtMs: this.startedAt,
|
|
960
|
+
forceRefresh: true,
|
|
961
|
+
});
|
|
962
|
+
if (restoredHistory.source !== 'provider-native') {
|
|
963
|
+
this.lastExternalCompletionProbe = null;
|
|
964
|
+
return null;
|
|
965
|
+
}
|
|
966
|
+
this.lastExternalCompletionProbe = this.buildExternalTranscriptProbe(
|
|
967
|
+
restoredHistory.messages,
|
|
968
|
+
restoredHistory.sourcePath,
|
|
969
|
+
restoredHistory.sourceMtimeMs,
|
|
970
|
+
);
|
|
971
|
+
return restoredHistory.messages;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
private completionFinalAssistantEvidence(parsedMessages: unknown): CompletionFinalAssistantEvidence {
|
|
975
|
+
if (this.completionHasFinalAssistantMessage(parsedMessages)) {
|
|
976
|
+
return {
|
|
977
|
+
present: true,
|
|
978
|
+
messages: Array.isArray(parsedMessages) ? parsedMessages : [],
|
|
979
|
+
source: 'parsed',
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
const externalMessages = this.readExternalCompletionMessages();
|
|
984
|
+
if (externalMessages) {
|
|
985
|
+
return {
|
|
986
|
+
present: this.completionHasFinalAssistantMessage(externalMessages),
|
|
987
|
+
messages: externalMessages,
|
|
988
|
+
source: 'external-native',
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
return {
|
|
993
|
+
present: false,
|
|
994
|
+
messages: Array.isArray(parsedMessages) ? parsedMessages : [],
|
|
995
|
+
source: 'unavailable',
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
private completionFinalSummary(parsedMessages: unknown): string | undefined {
|
|
1000
|
+
const evidence = this.completionFinalAssistantEvidence(parsedMessages);
|
|
1001
|
+
return extractFinalSummaryFromMessages(evidence.messages as any);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
private buildCompletedFinalizationDiagnostic(args: {
|
|
1005
|
+
blockReason: string;
|
|
1006
|
+
latestStatus?: any;
|
|
1007
|
+
latestVisibleStatus: string;
|
|
1008
|
+
waitedMs: number;
|
|
1009
|
+
pending: CompletedDebouncePending;
|
|
1010
|
+
emittedAfterFinalizationTimeout: boolean;
|
|
1011
|
+
}): Record<string, unknown> {
|
|
1012
|
+
let parsed: any = null;
|
|
1013
|
+
let parseError: string | undefined;
|
|
1014
|
+
try {
|
|
1015
|
+
parsed = this.adapter.getScriptParsedStatus();
|
|
1016
|
+
} catch (error: any) {
|
|
1017
|
+
parseError = error?.message || String(error);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
const evidence = this.completionFinalAssistantEvidence(parsed?.messages);
|
|
1021
|
+
if (evidence.source === 'external-native') {
|
|
1022
|
+
this.recordPendingTranscriptProbe(args.pending);
|
|
1023
|
+
}
|
|
1024
|
+
const visibleMessages = (Array.isArray(evidence.messages) ? evidence.messages : [])
|
|
1025
|
+
.filter((message: any) => isUserFacingChatMessage(message as ChatMessage));
|
|
1026
|
+
const lastVisible = visibleMessages[visibleMessages.length - 1] as ChatMessage | undefined;
|
|
1027
|
+
const lastVisibleRole = typeof lastVisible?.role === 'string' ? lastVisible.role.trim().toLowerCase() : null;
|
|
1028
|
+
const lastVisibleKind = typeof (lastVisible as any)?.kind === 'string' ? (lastVisible as any).kind : null;
|
|
1029
|
+
const lastVisibleContentLength = lastVisible ? flattenContent(lastVisible.content).trim().length : 0;
|
|
1030
|
+
|
|
1031
|
+
return {
|
|
1032
|
+
providerType: this.type,
|
|
1033
|
+
sessionId: this.instanceId,
|
|
1034
|
+
providerSessionId: this.providerSessionId || null,
|
|
1035
|
+
workspace: this.workingDir,
|
|
1036
|
+
blockReason: args.blockReason,
|
|
1037
|
+
emittedAfterFinalizationTimeout: args.emittedAfterFinalizationTimeout,
|
|
1038
|
+
waitedMs: args.waitedMs,
|
|
1039
|
+
maxWaitMs: COMPLETED_FINALIZATION_MAX_WAIT_MS,
|
|
1040
|
+
adapterStatus: typeof args.latestStatus?.status === 'string' ? args.latestStatus.status : null,
|
|
1041
|
+
latestVisibleStatus: args.latestVisibleStatus,
|
|
1042
|
+
parsedStatus: typeof parsed?.status === 'string' ? parsed.status : (parseError ? 'parse_error' : 'unknown'),
|
|
1043
|
+
parseError: parseError || undefined,
|
|
1044
|
+
finalAssistantPresent: evidence.present,
|
|
1045
|
+
finalAssistantEvidenceSource: evidence.source,
|
|
1046
|
+
visibleMessageCount: visibleMessages.length,
|
|
1047
|
+
lastVisibleRole,
|
|
1048
|
+
lastVisibleKind,
|
|
1049
|
+
lastVisibleContentLength,
|
|
1050
|
+
pendingStartedAt: this.generatingStartedAt || null,
|
|
1051
|
+
pendingFirstObservedAt: args.pending.firstObservedAt,
|
|
1052
|
+
pendingTimestamp: args.pending.timestamp,
|
|
1053
|
+
pendingDurationSec: args.pending.duration,
|
|
1054
|
+
previousBlockReason: args.pending.loggedBlockReason || null,
|
|
1055
|
+
transcriptProbeHistory: args.pending.transcriptProbeHistory || [],
|
|
1056
|
+
};
|
|
744
1057
|
}
|
|
745
1058
|
|
|
746
1059
|
private hasAdapterPendingResponse(): boolean {
|
|
@@ -765,32 +1078,90 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
765
1078
|
if (!isCliGeneratingLikeStatus(parsedRawStatus)) return false;
|
|
766
1079
|
if (adapterRawStatus !== 'idle') return false;
|
|
767
1080
|
if (hasNonEmptyCliModalButtons(parsedStatus?.activeModal ?? parsedStatus?.modal)) return false;
|
|
768
|
-
|
|
1081
|
+
if (this.hasAdapterPendingResponse()) return false;
|
|
1082
|
+
// Do not suppress when the adapter's raw response buffer is still non-empty.
|
|
1083
|
+
// This catches the case where isWaitingForResponse has already flipped to false
|
|
1084
|
+
// (so getPartialResponse() returns '') but the provider's native parser still
|
|
1085
|
+
// reports generating because it's parsing buffered content. Suppressing the
|
|
1086
|
+
// finalization block here would emit a false completion event while the provider
|
|
1087
|
+
// session is still actively processing its response stream.
|
|
1088
|
+
const adapterAny = this.adapter as any;
|
|
1089
|
+
if (typeof adapterAny?.responseBuffer === 'string' && adapterAny.responseBuffer.trim()) return false;
|
|
1090
|
+
return true;
|
|
769
1091
|
}
|
|
770
1092
|
|
|
771
|
-
private
|
|
772
|
-
if (latestVisibleStatus !== 'idle') return `status:${latestVisibleStatus}
|
|
1093
|
+
private getCompletedFinalizationBlock(latestVisibleStatus: string, pending: CompletedDebouncePending): CompletedFinalizationBlock | null {
|
|
1094
|
+
if (latestVisibleStatus !== 'idle') return { reason: `status:${latestVisibleStatus}`, terminal: true };
|
|
773
1095
|
|
|
774
1096
|
const adapterAny = this.adapter as any;
|
|
775
|
-
|
|
776
|
-
if (
|
|
1097
|
+
const approvalResolvedIdle = pending.previousStatus === 'waiting_approval';
|
|
1098
|
+
if (!approvalResolvedIdle) {
|
|
1099
|
+
if (adapterAny?.isWaitingForResponse === true) return { reason: 'adapter_waiting_for_response', terminal: true };
|
|
1100
|
+
if (adapterAny?.currentTurnScope) return { reason: 'adapter_turn_scope_active', terminal: true };
|
|
1101
|
+
if (this.hasAdapterPendingResponse()) return { reason: 'adapter_pending_response', terminal: true };
|
|
1102
|
+
}
|
|
777
1103
|
|
|
778
1104
|
const partial = typeof this.adapter.getPartialResponse === 'function'
|
|
779
1105
|
? this.adapter.getPartialResponse()
|
|
780
1106
|
: '';
|
|
781
|
-
if (typeof partial === 'string' && partial.trim()) return 'partial_response_pending';
|
|
1107
|
+
if (typeof partial === 'string' && partial.trim()) return { reason: 'partial_response_pending', terminal: true };
|
|
782
1108
|
|
|
783
1109
|
let parsed: any;
|
|
784
1110
|
try {
|
|
785
1111
|
parsed = this.adapter.getScriptParsedStatus();
|
|
786
1112
|
} catch (error: any) {
|
|
787
|
-
return `parse_error:${error?.message || String(error)}
|
|
1113
|
+
return { reason: `parse_error:${error?.message || String(error)}` };
|
|
788
1114
|
}
|
|
789
1115
|
|
|
790
1116
|
const parsedStatus = typeof parsed?.status === 'string' ? parsed.status : 'unknown';
|
|
791
|
-
if (parsedStatus !== 'idle')
|
|
792
|
-
|
|
793
|
-
|
|
1117
|
+
if (parsedStatus !== 'idle') {
|
|
1118
|
+
const adapterStatus = this.adapter.getStatus({ allowParse: false });
|
|
1119
|
+
if (this.shouldSuppressStaleParsedBusyStatus(parsed, adapterStatus)) return null;
|
|
1120
|
+
return { reason: `parsed_status:${parsedStatus}`, terminal: isCliGeneratingLikeStatus(parsedStatus) };
|
|
1121
|
+
}
|
|
1122
|
+
if (parsed?.activeModal || parsed?.modal) return { reason: 'parsed_modal_active', terminal: true };
|
|
1123
|
+
const adapterOwnsMessagesElsewhere = (this.adapter as any)?.chatMessagesOwnedExternally === true;
|
|
1124
|
+
const finalAssistantEvidence = this.completionFinalAssistantEvidence(parsed?.messages);
|
|
1125
|
+
if (!finalAssistantEvidence.present) {
|
|
1126
|
+
if (adapterOwnsMessagesElsewhere) {
|
|
1127
|
+
if (finalAssistantEvidence.source === 'external-native') {
|
|
1128
|
+
const probe = this.recordPendingTranscriptProbe(pending);
|
|
1129
|
+
if (probe && !pending.loggedTranscriptProbe) {
|
|
1130
|
+
LOG.info('CLI', `[${this.type}] external transcript probe: msgCount=${probe.msgCount} lastRole=${probe.lastRole || 'none'} lastKind=${probe.lastKind || 'none'} contentLen=${probe.contentLen} sourceMtime=${probe.sourceMtimeMs ?? 'unknown'} mtimeAge=${probe.mtimeAgeMs ?? 'unknown'}ms`);
|
|
1131
|
+
pending.loggedTranscriptProbe = true;
|
|
1132
|
+
}
|
|
1133
|
+
return { reason: 'missing_final_assistant', terminal: true };
|
|
1134
|
+
}
|
|
1135
|
+
// SpecCliAdapter never populates parsed.messages — chat history flows
|
|
1136
|
+
// through the daemon's native-history pipeline, not the status hook.
|
|
1137
|
+
// If that pipeline is unavailable, keep the old skip behavior for
|
|
1138
|
+
// providers that have not opted into strict final-assistant evidence.
|
|
1139
|
+
if ((this.provider as any).requiresFinalAssistantBeforeIdle === true) {
|
|
1140
|
+
return { reason: 'missing_final_assistant', terminal: true };
|
|
1141
|
+
}
|
|
1142
|
+
} else {
|
|
1143
|
+
return {
|
|
1144
|
+
reason: 'missing_final_assistant',
|
|
1145
|
+
terminal: (this.provider as any).requiresFinalAssistantBeforeIdle === true,
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
// Guard: if the screen still shows an approval/choice prompt as the last visible text,
|
|
1151
|
+
// the turn is not complete even if the parsed status says idle and there is an assistant
|
|
1152
|
+
// message. This catches the case where waiting_approval→idle transitions occur before
|
|
1153
|
+
// the modal has been resolved (e.g. the PTY rendered the prompt but no button press fired).
|
|
1154
|
+
try {
|
|
1155
|
+
const screenText = typeof (this.adapter as any).getScreenText === 'function'
|
|
1156
|
+
? String((this.adapter as any).getScreenText() || '')
|
|
1157
|
+
: '';
|
|
1158
|
+
if (screenText) {
|
|
1159
|
+
const tailLines = screenText.split(/\r?\n/).slice(-16).join('\n');
|
|
1160
|
+
if (looksLikeActiveApprovalPromptText(tailLines)) {
|
|
1161
|
+
return { reason: 'screen_shows_approval_prompt', terminal: approvalResolvedIdle };
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
} catch { /* defensive: screen text read is best-effort */ }
|
|
794
1165
|
|
|
795
1166
|
return null;
|
|
796
1167
|
}
|
|
@@ -817,10 +1188,11 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
817
1188
|
return;
|
|
818
1189
|
}
|
|
819
1190
|
|
|
820
|
-
const
|
|
821
|
-
if (
|
|
1191
|
+
const block = this.getCompletedFinalizationBlock(latestVisibleStatus, pending);
|
|
1192
|
+
if (block) {
|
|
1193
|
+
const blockReason = block.reason;
|
|
822
1194
|
const waitedMs = Date.now() - pending.firstObservedAt;
|
|
823
|
-
if (waitedMs < COMPLETED_FINALIZATION_MAX_WAIT_MS) {
|
|
1195
|
+
if (block.terminal || waitedMs < COMPLETED_FINALIZATION_MAX_WAIT_MS) {
|
|
824
1196
|
if (pending.loggedBlockReason !== blockReason) {
|
|
825
1197
|
LOG.info('CLI', `[${this.type}] waiting to emit completed until transcript finalizes (${blockReason})`);
|
|
826
1198
|
pending.loggedBlockReason = blockReason;
|
|
@@ -828,10 +1200,29 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
828
1200
|
this.scheduleCompletedDebounceFlush(COMPLETED_FINALIZATION_RETRY_MS);
|
|
829
1201
|
return;
|
|
830
1202
|
}
|
|
831
|
-
|
|
1203
|
+
const completionDiagnostic = this.buildCompletedFinalizationDiagnostic({
|
|
1204
|
+
blockReason,
|
|
1205
|
+
latestStatus,
|
|
1206
|
+
latestVisibleStatus,
|
|
1207
|
+
waitedMs,
|
|
1208
|
+
pending,
|
|
1209
|
+
emittedAfterFinalizationTimeout: true,
|
|
1210
|
+
});
|
|
1211
|
+
LOG.warn('CLI', `[${this.type}] emitting completed event after ${waitedMs}ms without finalized assistant turn (${blockReason})`);
|
|
1212
|
+
this.pushEvent({
|
|
1213
|
+
event: 'agent:generating_completed',
|
|
1214
|
+
chatTitle: pending.chatTitle,
|
|
1215
|
+
duration: pending.duration,
|
|
1216
|
+
timestamp: pending.timestamp,
|
|
1217
|
+
finalSummary: blockReason.startsWith('parsed_status:')
|
|
1218
|
+
? ''
|
|
1219
|
+
: this.completionFinalSummary(this.adapter?.getScriptParsedStatus()?.messages),
|
|
1220
|
+
completionDiagnostic,
|
|
1221
|
+
});
|
|
832
1222
|
this.completedDebouncePending = null;
|
|
833
1223
|
this.completedDebounceTimer = null;
|
|
834
1224
|
this.generatingStartedAt = 0;
|
|
1225
|
+
this.lastApprovalEventFingerprint = '';
|
|
835
1226
|
return;
|
|
836
1227
|
}
|
|
837
1228
|
|
|
@@ -841,11 +1232,12 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
841
1232
|
chatTitle: pending.chatTitle,
|
|
842
1233
|
duration: pending.duration,
|
|
843
1234
|
timestamp: pending.timestamp,
|
|
844
|
-
finalSummary:
|
|
1235
|
+
finalSummary: this.completionFinalSummary(this.adapter?.getScriptParsedStatus()?.messages),
|
|
845
1236
|
});
|
|
846
1237
|
this.completedDebouncePending = null;
|
|
847
1238
|
this.completedDebounceTimer = null;
|
|
848
1239
|
this.generatingStartedAt = 0;
|
|
1240
|
+
this.lastApprovalEventFingerprint = '';
|
|
849
1241
|
}
|
|
850
1242
|
|
|
851
1243
|
private maybeAutoApproveStatus(adapterStatus: any, now = Date.now()): boolean {
|
|
@@ -853,15 +1245,46 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
853
1245
|
// Guard re-entry: onStatusChange/getState can observe the same modal multiple
|
|
854
1246
|
// times while the PTY absorbs the approval key. Without this flag, repeated
|
|
855
1247
|
// snapshots would write stray keys into the input once the modal dismisses.
|
|
856
|
-
|
|
1248
|
+
// However, Claude Code can present a second approval immediately after the
|
|
1249
|
+
// first. Resolve a changed modal signature even while the previous write is
|
|
1250
|
+
// still inside the short busy window.
|
|
1251
|
+
if (!autoApproveActive) {
|
|
1252
|
+
this.lastAutoApprovalSignature = '';
|
|
1253
|
+
return autoApproveActive;
|
|
1254
|
+
}
|
|
1255
|
+
const modal = adapterStatus.activeModal;
|
|
1256
|
+
// (fix) Do not auto-approve when no concrete modal/buttons are present.
|
|
1257
|
+
// Claude TUI flaps between paints; without this guard adapterStatus
|
|
1258
|
+
// could report status=waiting_approval with activeModal=null (or with
|
|
1259
|
+
// an empty buttons array briefly) and we'd still call
|
|
1260
|
+
// resolveModal(-1) — which used to type "1" into the prompt
|
|
1261
|
+
// repeatedly. Skip until a real modal is captured.
|
|
1262
|
+
const buttons = Array.isArray(modal?.buttons)
|
|
1263
|
+
? modal.buttons.map((b: any) => String(b || '').trim()).filter(Boolean)
|
|
1264
|
+
: [];
|
|
1265
|
+
if (!modal || buttons.length === 0) {
|
|
1266
|
+
return autoApproveActive;
|
|
1267
|
+
}
|
|
1268
|
+
const { index: buttonIndex, label: buttonLabel } = pickAutoApprovalButton(buttons);
|
|
1269
|
+
if (buttonIndex < 0) {
|
|
1270
|
+
// No concrete button matched — don't pick a random index, just
|
|
1271
|
+
// surface the modal so the user can decide.
|
|
1272
|
+
return autoApproveActive;
|
|
1273
|
+
}
|
|
1274
|
+
const signature = [
|
|
1275
|
+
typeof modal?.message === 'string' ? modal.message.trim() : '',
|
|
1276
|
+
buttons.join('|'),
|
|
1277
|
+
buttonIndex,
|
|
1278
|
+
].join('::');
|
|
1279
|
+
if (!this.autoApproveBusy || signature !== this.lastAutoApprovalSignature) {
|
|
857
1280
|
this.autoApproveBusy = true;
|
|
1281
|
+
this.lastAutoApprovalSignature = signature;
|
|
858
1282
|
if (this.autoApproveBusyTimer) clearTimeout(this.autoApproveBusyTimer);
|
|
859
1283
|
this.autoApproveBusyTimer = setTimeout(() => {
|
|
860
1284
|
this.autoApproveBusy = false;
|
|
861
1285
|
this.autoApproveBusyTimer = null;
|
|
1286
|
+
this.lastAutoApprovalSignature = '';
|
|
862
1287
|
}, 2000);
|
|
863
|
-
const modal = adapterStatus.activeModal;
|
|
864
|
-
const { index: buttonIndex, label: buttonLabel } = pickApprovalButton(modal?.buttons, this.provider);
|
|
865
1288
|
this.recordAutoApproval(modal?.message, buttonLabel, now);
|
|
866
1289
|
setTimeout(() => {
|
|
867
1290
|
this.adapter.resolveModal(buttonIndex);
|
|
@@ -876,6 +1299,13 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
876
1299
|
// during long-running CLI sessions. Keep this path on adapter-owned light
|
|
877
1300
|
// state only; rich provider parsing is reserved for getState/read_chat.
|
|
878
1301
|
const adapterStatus = this.adapter.getStatus({ allowParse: false });
|
|
1302
|
+
const adapterProviderSessionId = normalizeProviderSessionId(
|
|
1303
|
+
this.provider,
|
|
1304
|
+
typeof adapterStatus?.providerSessionId === 'string' ? adapterStatus.providerSessionId : '',
|
|
1305
|
+
);
|
|
1306
|
+
if (adapterProviderSessionId) {
|
|
1307
|
+
this.promoteProviderSessionId(adapterProviderSessionId);
|
|
1308
|
+
}
|
|
879
1309
|
const parsedStatus = null;
|
|
880
1310
|
const rawStatus = adapterStatus.status;
|
|
881
1311
|
const autoApproveActive = this.maybeAutoApproveStatus(adapterStatus, now);
|
|
@@ -926,10 +1356,14 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
926
1356
|
if (!this.generatingStartedAt) this.generatingStartedAt = now;
|
|
927
1357
|
const modal = adapterStatus.activeModal;
|
|
928
1358
|
LOG.info('CLI', `[${this.type}] approval modal: "${modal?.message?.slice(0, 80) ?? 'none'}"`);
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1359
|
+
const approvalFingerprint = JSON.stringify({
|
|
1360
|
+
message: typeof modal?.message === 'string' ? modal.message.trim() : '',
|
|
1361
|
+
buttons: Array.isArray(modal?.buttons) ? modal.buttons.map((button: unknown) => String(button).trim()) : [],
|
|
1362
|
+
});
|
|
1363
|
+
// PTY redraws can briefly leave waiting_approval and then re-enter it.
|
|
1364
|
+
// Keep one coordinator event per logical modal until the turn completes.
|
|
1365
|
+
if (this.lastStatus !== 'waiting_approval' && approvalFingerprint !== this.lastApprovalEventFingerprint) {
|
|
1366
|
+
this.lastApprovalEventFingerprint = approvalFingerprint;
|
|
933
1367
|
this.appendRuntimeSystemMessage(
|
|
934
1368
|
this.formatApprovalRequestMessage(modal?.message, modal?.buttons),
|
|
935
1369
|
`approval_request:${now}`,
|
|
@@ -943,20 +1377,72 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
943
1377
|
}
|
|
944
1378
|
} else if (newStatus === 'idle' && (this.lastStatus === 'generating' || this.lastStatus === 'waiting_approval')) {
|
|
945
1379
|
const duration = this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1000) : 0;
|
|
946
|
-
// If debounce still pending (generating lasted < 1s), cancel both events
|
|
1380
|
+
// If debounce still pending (generating lasted < 1s), cancel both UI events.
|
|
1381
|
+
// Still emit agent:generating_completed so mesh orchestration can record
|
|
1382
|
+
// task_completed for direct dispatches that complete faster than the debounce.
|
|
947
1383
|
if (this.generatingDebouncePending) {
|
|
948
|
-
|
|
1384
|
+
const shortDurationMs = this.generatingStartedAt ? now - this.generatingStartedAt : 0;
|
|
1385
|
+
LOG.info('CLI', `[${this.type}] suppressed short generating (${shortDurationMs}ms)`);
|
|
949
1386
|
if (this.generatingDebounceTimer) { clearTimeout(this.generatingDebounceTimer); this.generatingDebounceTimer = null; }
|
|
950
1387
|
this.generatingDebouncePending = null;
|
|
951
1388
|
this.generatingStartedAt = 0;
|
|
1389
|
+
// Emit completion for mesh task association even though the UI generating
|
|
1390
|
+
// started/completed pair is suppressed (too short for visible UI update).
|
|
1391
|
+
let shortFinalSummary: string | undefined;
|
|
1392
|
+
let shortEvidenceSource: CompletionFinalAssistantEvidence['source'] = 'unavailable';
|
|
1393
|
+
try {
|
|
1394
|
+
const parsedMessages = this.adapter?.getScriptParsedStatus()?.messages;
|
|
1395
|
+
const evidence = this.completionFinalAssistantEvidence(parsedMessages);
|
|
1396
|
+
shortEvidenceSource = evidence.source;
|
|
1397
|
+
shortFinalSummary = extractFinalSummaryFromMessages(evidence.messages as any);
|
|
1398
|
+
} catch { /* best-effort */ }
|
|
1399
|
+
if (((this.provider as any).requiresFinalAssistantBeforeIdle === true || shortEvidenceSource === 'external-native') && !shortFinalSummary) {
|
|
1400
|
+
LOG.info('CLI', `[${this.type}] suppressed short completion without final assistant evidence`);
|
|
1401
|
+
} else {
|
|
1402
|
+
this.pushEvent({
|
|
1403
|
+
event: 'agent:generating_completed',
|
|
1404
|
+
chatTitle,
|
|
1405
|
+
duration: 0,
|
|
1406
|
+
timestamp: now,
|
|
1407
|
+
finalSummary: shortFinalSummary,
|
|
1408
|
+
completionDiagnostic: {
|
|
1409
|
+
reason: 'short_generating_suppressed',
|
|
1410
|
+
shortDurationMs,
|
|
1411
|
+
finalAssistantEvidenceSource: shortEvidenceSource,
|
|
1412
|
+
},
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
952
1415
|
} else {
|
|
953
1416
|
// Debounce completed, then require the rich transcript path that read_chat
|
|
954
1417
|
// uses to show an idle turn whose last user-facing message is assistant.
|
|
955
|
-
this.completedDebouncePending = {
|
|
1418
|
+
this.completedDebouncePending = {
|
|
1419
|
+
chatTitle,
|
|
1420
|
+
duration,
|
|
1421
|
+
timestamp: now,
|
|
1422
|
+
firstObservedAt: now,
|
|
1423
|
+
previousStatus: this.lastStatus,
|
|
1424
|
+
};
|
|
956
1425
|
this.scheduleCompletedDebounceFlush(3000);
|
|
957
1426
|
}
|
|
958
1427
|
} else if (newStatus === 'idle' && this.lastStatus === 'starting') {
|
|
959
1428
|
this.pushEvent({ event: 'agent:ready', chatTitle, timestamp: now });
|
|
1429
|
+
} else if (newStatus === 'error') {
|
|
1430
|
+
if (this.generatingDebounceTimer) { clearTimeout(this.generatingDebounceTimer); this.generatingDebounceTimer = null; }
|
|
1431
|
+
this.generatingDebouncePending = null;
|
|
1432
|
+
if (this.completedDebounceTimer) { clearTimeout(this.completedDebounceTimer); this.completedDebounceTimer = null; }
|
|
1433
|
+
this.completedDebouncePending = null;
|
|
1434
|
+
this.errorMessage = adapterStatus.errorMessage || this.errorMessage;
|
|
1435
|
+
this.errorReason = (adapterStatus.errorReason as ProviderErrorReason) || this.errorReason;
|
|
1436
|
+
this.pushEvent({
|
|
1437
|
+
event: 'agent:stopped',
|
|
1438
|
+
chatTitle,
|
|
1439
|
+
timestamp: now,
|
|
1440
|
+
finalSummary: adapterStatus.errorMessage || adapterStatus.errorReason || 'Provider reported an error',
|
|
1441
|
+
completionDiagnostic: {
|
|
1442
|
+
reason: adapterStatus.errorReason || 'provider_error',
|
|
1443
|
+
errorMessage: adapterStatus.errorMessage || undefined,
|
|
1444
|
+
},
|
|
1445
|
+
});
|
|
960
1446
|
} else if (newStatus === 'stopped') {
|
|
961
1447
|
// Cancel any pending debounce
|
|
962
1448
|
if (this.generatingDebounceTimer) { clearTimeout(this.generatingDebounceTimer); this.generatingDebounceTimer = null; }
|
|
@@ -977,7 +1463,31 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
977
1463
|
// Monitor check (cooldown based notification, IDE/CLI common)
|
|
978
1464
|
const agentKey = `${this.type}:cli`;
|
|
979
1465
|
const monitorEvents = this.monitor.check(agentKey, newStatus, now, progressFingerprint);
|
|
1466
|
+
const monitorParsedStatus: any = parsedStatus;
|
|
980
1467
|
for (const me of monitorEvents) {
|
|
1468
|
+
if (
|
|
1469
|
+
me.type === 'monitor:long_generating'
|
|
1470
|
+
&& this.completionHasFinalAssistantMessage(monitorParsedStatus?.messages)
|
|
1471
|
+
&& !this.hasAdapterPendingResponse()
|
|
1472
|
+
&& !hasNonEmptyCliModalButtons(monitorParsedStatus?.activeModal ?? monitorParsedStatus?.modal)
|
|
1473
|
+
) {
|
|
1474
|
+
this.pushEvent({
|
|
1475
|
+
event: 'agent:generating_completed',
|
|
1476
|
+
chatTitle,
|
|
1477
|
+
duration: this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1000) : undefined,
|
|
1478
|
+
timestamp: me.timestamp,
|
|
1479
|
+
finalSummary: extractFinalSummaryFromMessages(monitorParsedStatus?.messages),
|
|
1480
|
+
completionDiagnostic: {
|
|
1481
|
+
providerType: this.type,
|
|
1482
|
+
sessionId: this.instanceId,
|
|
1483
|
+
providerSessionId: this.providerSessionId || null,
|
|
1484
|
+
reconciliationReason: 'long_generating_monitor_final_summary',
|
|
1485
|
+
finalAssistantPresent: true,
|
|
1486
|
+
},
|
|
1487
|
+
});
|
|
1488
|
+
this.generatingStartedAt = 0;
|
|
1489
|
+
continue;
|
|
1490
|
+
}
|
|
981
1491
|
this.pushEvent({ event: me.type, agentKey: me.agentKey, message: me.message, elapsedSec: me.elapsedSec, timestamp: me.timestamp });
|
|
982
1492
|
}
|
|
983
1493
|
}
|
|
@@ -1258,12 +1768,28 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1258
1768
|
index,
|
|
1259
1769
|
source: 'parsed',
|
|
1260
1770
|
}));
|
|
1771
|
+
const getRole = (message: ChatMessage): string => typeof message.role === 'string'
|
|
1772
|
+
? message.role.trim().toLowerCase()
|
|
1773
|
+
: '';
|
|
1261
1774
|
const runtimeEntries: MergeEntry[] = this.runtimeMessages.map((entry, index) => ({
|
|
1262
1775
|
message: entry.message,
|
|
1263
1776
|
index: parsedMessages.length + index,
|
|
1264
|
-
source: 'runtime',
|
|
1777
|
+
source: 'runtime' as const,
|
|
1265
1778
|
runtimeKey: entry.key,
|
|
1266
|
-
}))
|
|
1779
|
+
})).filter((entry) => {
|
|
1780
|
+
const meta = entry.message.meta && typeof entry.message.meta === 'object' && !Array.isArray(entry.message.meta)
|
|
1781
|
+
? entry.message.meta as Record<string, unknown>
|
|
1782
|
+
: {};
|
|
1783
|
+
if (meta.runtimeInputAck !== true) return true;
|
|
1784
|
+
const runtimeText = flattenContent(entry.message.content).replace(/\s+/g, ' ').trim();
|
|
1785
|
+
if (!runtimeText) return false;
|
|
1786
|
+
return !parsedEntries.some((parsedEntry) => {
|
|
1787
|
+
const parsedRole = getRole(parsedEntry.message);
|
|
1788
|
+
if (parsedRole !== 'user' && parsedRole !== 'human') return false;
|
|
1789
|
+
const parsedText = flattenContent(parsedEntry.message.content).replace(/\s+/g, ' ').trim();
|
|
1790
|
+
return parsedText === runtimeText;
|
|
1791
|
+
});
|
|
1792
|
+
});
|
|
1267
1793
|
const getTime = (message: ChatMessage): number => {
|
|
1268
1794
|
const value = typeof message.receivedAt === 'number'
|
|
1269
1795
|
? message.receivedAt
|
|
@@ -1273,9 +1799,6 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1273
1799
|
return Number.isFinite(value) && value > 0 ? value : 0;
|
|
1274
1800
|
};
|
|
1275
1801
|
|
|
1276
|
-
const getRole = (message: ChatMessage): string => typeof message.role === 'string'
|
|
1277
|
-
? message.role.trim().toLowerCase()
|
|
1278
|
-
: '';
|
|
1279
1802
|
const isRuntimeOverlay = (entry: MergeEntry): boolean => {
|
|
1280
1803
|
if (entry.source !== 'runtime') return false;
|
|
1281
1804
|
const key = typeof entry.runtimeKey === 'string' ? entry.runtimeKey.trim().toLowerCase() : '';
|
|
@@ -1341,7 +1864,9 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1341
1864
|
this.providerSessionId = nextSessionId;
|
|
1342
1865
|
this.historyWriter.promoteHistorySession(this.type, previousHistorySessionId, nextSessionId);
|
|
1343
1866
|
this.historyWriter.writeSessionStart(this.type, nextSessionId, this.workingDir, this.instanceId);
|
|
1344
|
-
this.
|
|
1867
|
+
if (this.shouldHydrateExistingProviderHistory()) {
|
|
1868
|
+
this.restorePersistedHistoryFromCurrentSession();
|
|
1869
|
+
}
|
|
1345
1870
|
this.adapter.updateRuntimeMeta({ providerSessionId: nextSessionId });
|
|
1346
1871
|
this.onProviderSessionResolved?.({
|
|
1347
1872
|
instanceId: this.instanceId,
|
|
@@ -1354,9 +1879,27 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1354
1879
|
LOG.info('CLI', `[${this.type}] discovered provider session id: ${nextSessionId}`);
|
|
1355
1880
|
}
|
|
1356
1881
|
|
|
1882
|
+
private shouldHydrateExistingProviderHistory(): boolean {
|
|
1883
|
+
return this.launchMode === 'resume' || this.launchMode === 'manual';
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
private shouldSuppressFreshLaunchStartupReplay(parsedMessages: unknown[], parsedStatus: any, adapterStatus: any, parsedProviderSessionId = ''): boolean {
|
|
1887
|
+
if (this.launchMode !== 'new') return false;
|
|
1888
|
+
if (this.providerSessionId) return false;
|
|
1889
|
+
if (!Array.isArray(parsedMessages) || parsedMessages.length === 0) return false;
|
|
1890
|
+
if (!isIdleStatus(adapterStatus?.status) || !isIdleStatus(parsedStatus?.status)) return false;
|
|
1891
|
+
if (parsedProviderSessionId) return true;
|
|
1892
|
+
|
|
1893
|
+
const newestMessageAt = parsedMessages.reduce<number>((newest, message) => Math.max(newest, getMessageTime(message)), 0);
|
|
1894
|
+
|
|
1895
|
+
// Untimestamped idle parser output during a fresh launch is usually the
|
|
1896
|
+
// provider's last workspace transcript before a new turn exists.
|
|
1897
|
+
return newestMessageAt === 0;
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1357
1900
|
private syncCanonicalSavedHistoryIfNeeded(): boolean {
|
|
1358
1901
|
if (!this.providerSessionId) return false;
|
|
1359
|
-
const canonicalHistory = this.provider.
|
|
1902
|
+
const canonicalHistory = this.provider.nativeHistory;
|
|
1360
1903
|
if (!canonicalHistory) return false;
|
|
1361
1904
|
|
|
1362
1905
|
if (isNativeSourceCanonicalHistory(canonicalHistory)) {
|
|
@@ -1418,9 +1961,9 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1418
1961
|
private restorePersistedHistoryFromCurrentSession(): void {
|
|
1419
1962
|
if (!this.providerSessionId) return;
|
|
1420
1963
|
this.syncCanonicalSavedHistoryIfNeeded();
|
|
1421
|
-
const restoredHistory = isNativeSourceCanonicalHistory(this.provider.
|
|
1964
|
+
const restoredHistory = isNativeSourceCanonicalHistory(this.provider.nativeHistory)
|
|
1422
1965
|
? readProviderChatHistory(this.type, {
|
|
1423
|
-
canonicalHistory: this.provider.
|
|
1966
|
+
canonicalHistory: this.provider.nativeHistory,
|
|
1424
1967
|
historySessionId: this.providerSessionId,
|
|
1425
1968
|
workspace: this.workingDir,
|
|
1426
1969
|
offset: 0,
|