@adhdev/daemon-core 0.9.82-rc.17 → 0.9.82-rc.171
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 +7 -0
- package/dist/config/config.d.ts +5 -0
- package/dist/config/mesh-config.d.ts +71 -1
- package/dist/git/git-commands.d.ts +5 -1
- package/dist/index.d.ts +35 -6
- package/dist/index.js +18634 -3760
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +18538 -3732
- 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 +96 -5
- package/dist/mesh/mesh-fast-forward.d.ts +39 -0
- package/dist/mesh/mesh-host-ownership.d.ts +9 -0
- package/dist/mesh/mesh-ledger.d.ts +58 -1
- package/dist/mesh/mesh-refine-status.d.ts +26 -0
- package/dist/mesh/mesh-work-queue.d.ts +44 -5
- package/dist/mesh/preview-freshness.d.ts +18 -0
- package/dist/mesh/refine-config.d.ts +193 -0
- package/dist/mesh/worktree-bootstrap-config.d.ts +113 -0
- package/dist/providers/approval-utils.d.ts +13 -0
- package/dist/providers/cli-provider-instance.d.ts +9 -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 +24 -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 +149 -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 +100 -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 +3574 -502
- package/src/config/chat-history.ts +87 -24
- package/src/config/config.ts +12 -0
- package/src/config/mesh-config.ts +264 -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 -5
- 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 +479 -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 +879 -61
- package/src/mesh/mesh-fast-forward.ts +430 -0
- package/src/mesh/mesh-host-ownership.ts +73 -0
- package/src/mesh/mesh-ledger.ts +457 -104
- package/src/mesh/mesh-refine-status.ts +144 -0
- package/src/mesh/mesh-work-queue.ts +216 -158
- package/src/mesh/preview-freshness.ts +118 -0
- package/src/mesh/refine-config.ts +366 -0
- package/src/mesh/worktree-bootstrap-config.ts +247 -0
- package/src/providers/approval-utils.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 +456 -54
- 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 +227 -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 +430 -0
- package/src/providers/spec/driver.ts +502 -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 +623 -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 +110 -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
|
@@ -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,9 +42,28 @@ type CompletedDebouncePending = {
|
|
|
40
42
|
duration: number;
|
|
41
43
|
timestamp: number;
|
|
42
44
|
firstObservedAt: number;
|
|
45
|
+
previousStatus: string;
|
|
43
46
|
loggedBlockReason?: string;
|
|
44
47
|
};
|
|
45
48
|
|
|
49
|
+
function isIdleStatus(value: unknown): boolean {
|
|
50
|
+
const status = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
51
|
+
return !status || status === 'idle' || status === 'ready';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
function getMessageTime(message: unknown): number {
|
|
56
|
+
if (!message || typeof message !== 'object') return 0;
|
|
57
|
+
const record = message as { receivedAt?: unknown; timestamp?: unknown };
|
|
58
|
+
const value = Number(record.receivedAt ?? record.timestamp ?? 0);
|
|
59
|
+
return Number.isFinite(value) ? value : 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type CompletedFinalizationBlock = {
|
|
63
|
+
reason: string;
|
|
64
|
+
terminal?: boolean;
|
|
65
|
+
};
|
|
66
|
+
|
|
46
67
|
const COMPLETED_FINALIZATION_RETRY_MS = 1000;
|
|
47
68
|
const COMPLETED_FINALIZATION_MAX_WAIT_MS = 30_000;
|
|
48
69
|
|
|
@@ -309,9 +330,10 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
309
330
|
private monitor: StatusMonitor;
|
|
310
331
|
private generatingDebounceTimer: NodeJS.Timeout | null = null;
|
|
311
332
|
private generatingDebouncePending: { chatTitle: string; timestamp: number } | null = null;
|
|
312
|
-
private
|
|
333
|
+
private lastApprovalEventFingerprint = '';
|
|
313
334
|
private autoApproveBusy = false;
|
|
314
335
|
private autoApproveBusyTimer: NodeJS.Timeout | null = null;
|
|
336
|
+
private lastAutoApprovalSignature = '';
|
|
315
337
|
private controlValues: Record<string, string | number | boolean> = {};
|
|
316
338
|
private summaryMetadata: unknown = undefined;
|
|
317
339
|
private appliedEffectKeys = new Set<string>();
|
|
@@ -330,6 +352,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
330
352
|
private suppressIdleHistoryReplay = false;
|
|
331
353
|
private errorMessage: string | undefined = undefined;
|
|
332
354
|
private errorReason: ProviderErrorReason | undefined = undefined;
|
|
355
|
+
private activeInteractivePrompt: InteractivePrompt | null = null;
|
|
333
356
|
|
|
334
357
|
private presentationMode: 'terminal' | 'chat';
|
|
335
358
|
private providerSessionId?: string;
|
|
@@ -370,7 +393,10 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
370
393
|
this.providerSessionId = options?.providerSessionId;
|
|
371
394
|
this.launchMode = options?.launchMode || 'new';
|
|
372
395
|
this.onProviderSessionResolved = options?.onProviderSessionResolved;
|
|
373
|
-
this.adapter =
|
|
396
|
+
this.adapter = createCliAdapter(provider as CliProviderModule, workingDir, cliArgs, options?.extraEnv || {}, transportFactory) as ProviderCliAdapter;
|
|
397
|
+
if (this.providerSessionId) {
|
|
398
|
+
this.adapter.updateRuntimeMeta({ providerSessionId: this.providerSessionId });
|
|
399
|
+
}
|
|
374
400
|
this.monitor = new StatusMonitor();
|
|
375
401
|
this.historyWriter = new ChatHistoryWriter();
|
|
376
402
|
}
|
|
@@ -412,7 +438,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
412
438
|
await this.adapter.spawn();
|
|
413
439
|
await this.enforceFreshSessionLaunchIfNeeded();
|
|
414
440
|
this.maybeAppendRuntimeRecoveryMessage(this.adapter.getRuntimeMetadata());
|
|
415
|
-
if (this.providerSessionId) {
|
|
441
|
+
if (this.providerSessionId && this.shouldHydrateExistingProviderHistory()) {
|
|
416
442
|
this.restorePersistedHistoryFromCurrentSession();
|
|
417
443
|
}
|
|
418
444
|
if (this.providerSessionId && this.launchMode === 'resume') {
|
|
@@ -486,14 +512,38 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
486
512
|
}
|
|
487
513
|
|
|
488
514
|
getState(): ProviderState {
|
|
515
|
+
// TODO(phase5-sandbox): JS override scripts (detectStatus, parseApproval,
|
|
516
|
+
// parseSession) are currently invoked by CliScriptRunner.invoke() via direct
|
|
517
|
+
// function calls — the scripts run in the daemon process with full Node.js
|
|
518
|
+
// access and no resource limits.
|
|
519
|
+
//
|
|
520
|
+
// When Phase 5 lands, CliScriptRunner should route these calls through a
|
|
521
|
+
// SandboxedScriptRunner (see providers/sdk/v1/sandbox/script-runner.ts) so
|
|
522
|
+
// that each call gets a fresh isolated-vm context with a 50 ms CPU limit and
|
|
523
|
+
// a 32 MB memory cap. The execution path to change is:
|
|
524
|
+
// CliScriptRunner.invoke() → SandboxedScriptRunner.run(scriptSource, context)
|
|
525
|
+
//
|
|
526
|
+
// This getState() call-site is NOT where the change goes — the wiring belongs
|
|
527
|
+
// in cli-script-runner.ts (CliScriptRunner.detectStatus / parseApproval /
|
|
528
|
+
// parseSession), with provider-loader.ts updated to store script source strings
|
|
529
|
+
// alongside the loaded function references for extended-legacy providers.
|
|
489
530
|
const adapterStatus = this.adapter.getStatus();
|
|
531
|
+
if (Object.prototype.hasOwnProperty.call(adapterStatus, 'activeInteractivePrompt')) {
|
|
532
|
+
this.activeInteractivePrompt = adapterStatus.activeInteractivePrompt ?? null;
|
|
533
|
+
}
|
|
490
534
|
let parsedStatus: any = null;
|
|
491
535
|
let parseErrorMessage: string | undefined;
|
|
492
536
|
if (typeof this.adapter.getScriptParsedStatus === 'function') {
|
|
493
537
|
try {
|
|
494
538
|
parsedStatus = this.adapter.getScriptParsedStatus() || null;
|
|
495
|
-
|
|
496
|
-
|
|
539
|
+
const parsedErrorMessage = typeof parsedStatus?.errorMessage === 'string' && parsedStatus.errorMessage.trim()
|
|
540
|
+
? parsedStatus.errorMessage.trim()
|
|
541
|
+
: undefined;
|
|
542
|
+
const parsedErrorReason = typeof parsedStatus?.errorReason === 'string' && parsedStatus.errorReason.trim()
|
|
543
|
+
? parsedStatus.errorReason.trim() as ProviderErrorReason
|
|
544
|
+
: undefined;
|
|
545
|
+
this.errorMessage = parsedErrorMessage;
|
|
546
|
+
this.errorReason = parsedErrorReason;
|
|
497
547
|
} catch (error: any) {
|
|
498
548
|
parseErrorMessage = error?.message || String(error);
|
|
499
549
|
this.errorMessage = parseErrorMessage;
|
|
@@ -503,22 +553,39 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
503
553
|
this.errorMessage = undefined;
|
|
504
554
|
this.errorReason = undefined;
|
|
505
555
|
}
|
|
556
|
+
const adapterProviderSessionId = normalizeProviderSessionId(
|
|
557
|
+
this.provider,
|
|
558
|
+
typeof adapterStatus?.providerSessionId === 'string' ? adapterStatus.providerSessionId : '',
|
|
559
|
+
);
|
|
506
560
|
const autoApproveActive = this.maybeAutoApproveStatus(adapterStatus, Date.now());
|
|
507
|
-
const visibleStatus = parseErrorMessage
|
|
561
|
+
const visibleStatus = parseErrorMessage || parsedStatus?.status === 'error'
|
|
508
562
|
? 'error'
|
|
509
563
|
: (autoApproveActive ? 'generating' : adapterStatus.status);
|
|
564
|
+
const runtime = this.adapter.getRuntimeMetadata();
|
|
565
|
+
this.maybeAppendRuntimeRecoveryMessage(runtime);
|
|
566
|
+
let parsedMessages = Array.isArray(parsedStatus?.messages)
|
|
567
|
+
? parsedStatus.messages
|
|
568
|
+
: [];
|
|
510
569
|
const parsedProviderSessionId = normalizeProviderSessionId(
|
|
511
570
|
this.provider,
|
|
512
571
|
typeof parsedStatus?.providerSessionId === 'string' ? parsedStatus.providerSessionId : '',
|
|
513
572
|
);
|
|
514
|
-
|
|
573
|
+
const suppressFreshLaunchStartupReplay = this.shouldSuppressFreshLaunchStartupReplay(
|
|
574
|
+
parsedMessages,
|
|
575
|
+
parsedStatus,
|
|
576
|
+
adapterStatus,
|
|
577
|
+
parsedProviderSessionId,
|
|
578
|
+
);
|
|
579
|
+
if (adapterProviderSessionId && !suppressFreshLaunchStartupReplay) {
|
|
580
|
+
this.promoteProviderSessionId(adapterProviderSessionId);
|
|
581
|
+
}
|
|
582
|
+
if (parsedProviderSessionId && !suppressFreshLaunchStartupReplay) {
|
|
515
583
|
this.promoteProviderSessionId(parsedProviderSessionId);
|
|
516
584
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
: [];
|
|
585
|
+
if (suppressFreshLaunchStartupReplay) {
|
|
586
|
+
parsedMessages = [];
|
|
587
|
+
}
|
|
588
|
+
const activeChatId = this.providerSessionId || runtime?.runtimeId || this.instanceId;
|
|
522
589
|
const historyMessageCount = Number.isFinite(parsedStatus?.historyMessageCount)
|
|
523
590
|
? Math.max(0, Number(parsedStatus.historyMessageCount))
|
|
524
591
|
: null;
|
|
@@ -528,7 +595,18 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
528
595
|
: [];
|
|
529
596
|
}
|
|
530
597
|
const mergedMessages = this.mergeConversationMessages(parsedMessages);
|
|
531
|
-
const canonicalBackedHistory = this.
|
|
598
|
+
const canonicalBackedHistory = this.shouldHydrateExistingProviderHistory()
|
|
599
|
+
? this.syncCanonicalSavedHistoryIfNeeded()
|
|
600
|
+
: false;
|
|
601
|
+
const statusMessages = canonicalBackedHistory && this.lastPersistedHistoryMessages.length > 0
|
|
602
|
+
? this.lastPersistedHistoryMessages.map((message) => ({
|
|
603
|
+
role: message.role,
|
|
604
|
+
content: message.content,
|
|
605
|
+
kind: message.kind,
|
|
606
|
+
senderName: message.senderName,
|
|
607
|
+
receivedAt: message.receivedAt,
|
|
608
|
+
}))
|
|
609
|
+
: mergedMessages;
|
|
532
610
|
|
|
533
611
|
const dirName = this.workingDir.split('/').filter(Boolean).pop() || 'session';
|
|
534
612
|
const parsedChatStatus = typeof parsedStatus?.status === 'string' && parsedStatus.status.trim()
|
|
@@ -572,7 +650,12 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
572
650
|
}
|
|
573
651
|
}
|
|
574
652
|
|
|
575
|
-
this.applyProviderResponse(
|
|
653
|
+
this.applyProviderResponse(
|
|
654
|
+
suppressFreshLaunchStartupReplay && parsedStatus && typeof parsedStatus === 'object'
|
|
655
|
+
? { ...parsedStatus, providerSessionId: undefined }
|
|
656
|
+
: parsedStatus,
|
|
657
|
+
{ phase: 'immediate' },
|
|
658
|
+
);
|
|
576
659
|
const surface = resolveProviderStateSurface({
|
|
577
660
|
summaryMetadata: this.summaryMetadata as any,
|
|
578
661
|
controlValues: this.controlValues,
|
|
@@ -592,13 +675,15 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
592
675
|
status: visibleStatus,
|
|
593
676
|
mode: this.presentationMode,
|
|
594
677
|
activeChat: {
|
|
595
|
-
id:
|
|
678
|
+
id: activeChatId,
|
|
596
679
|
title: parsedStatus?.title || dirName,
|
|
597
680
|
status: activeChatStatus,
|
|
598
|
-
messages:
|
|
681
|
+
messages: statusMessages,
|
|
599
682
|
activeModal: autoApproveActive ? null : (parsedStatus?.activeModal ?? adapterStatus.activeModal),
|
|
683
|
+
activeInteractivePrompt: this.activeInteractivePrompt,
|
|
600
684
|
inputContent: '',
|
|
601
685
|
},
|
|
686
|
+
activeInteractivePrompt: this.activeInteractivePrompt,
|
|
602
687
|
workspace: this.workingDir,
|
|
603
688
|
instanceId: this.instanceId,
|
|
604
689
|
providerSessionId: this.providerSessionId,
|
|
@@ -651,13 +736,17 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
651
736
|
};
|
|
652
737
|
}
|
|
653
738
|
|
|
654
|
-
getSessionModalState(): SessionModalState {
|
|
655
|
-
const adapterStatus = this.adapter.getStatus({ allowParse:
|
|
739
|
+
getSessionModalState(sessionId?: string): SessionModalState {
|
|
740
|
+
const adapterStatus = this.adapter.getStatus({ allowParse: true });
|
|
656
741
|
const autoApproveActive = adapterStatus.status === 'waiting_approval' && this.shouldAutoApprove();
|
|
657
742
|
const visibleStatus = autoApproveActive ? 'generating' : adapterStatus.status;
|
|
658
743
|
const dirName = this.workingDir.split('/').filter(Boolean).pop() || 'session';
|
|
659
744
|
return {
|
|
660
|
-
|
|
745
|
+
// Honor the caller-supplied sessionId — InstanceMgr rejects the
|
|
746
|
+
// projection when projected.id !== requested sessionId, and
|
|
747
|
+
// this.instanceId is the manager's internal key, not the public
|
|
748
|
+
// sessionId the dashboard subscribes by.
|
|
749
|
+
id: sessionId ?? this.instanceId,
|
|
661
750
|
status: visibleStatus,
|
|
662
751
|
title: dirName,
|
|
663
752
|
activeModal: autoApproveActive ? null : adapterStatus.activeModal,
|
|
@@ -690,11 +779,65 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
690
779
|
void this.adapter.resolveAction(data).catch((e: any) => {
|
|
691
780
|
LOG.warn('CLI', `[${this.type}] resolve_action failed: ${e?.message || e}`);
|
|
692
781
|
});
|
|
782
|
+
} else if (event === 'interactive_prompt' && data) {
|
|
783
|
+
const prompt = normalizeInteractivePrompt(data);
|
|
784
|
+
if (prompt) {
|
|
785
|
+
this.activeInteractivePrompt = prompt;
|
|
786
|
+
this.events.push({
|
|
787
|
+
event: 'interactive_prompt',
|
|
788
|
+
timestamp: Date.now(),
|
|
789
|
+
promptId: prompt.promptId,
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
} else if (event === 'interactive_prompt_response' && data) {
|
|
793
|
+
try {
|
|
794
|
+
const response = normalizeInteractivePromptResponse(data);
|
|
795
|
+
if (this.activeInteractivePrompt?.promptId === response.promptId) {
|
|
796
|
+
this.activeInteractivePrompt = null;
|
|
797
|
+
}
|
|
798
|
+
if (typeof this.adapter.setInteractivePromptResponse !== 'function') {
|
|
799
|
+
LOG.warn('CLI', `[${this.type}] interactive_prompt_response ignored: adapter does not support interactive prompts`);
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
void this.adapter.setInteractivePromptResponse(response).catch((e: any) => {
|
|
803
|
+
LOG.warn('CLI', `[${this.type}] interactive_prompt_response failed: ${e?.message || e}`);
|
|
804
|
+
});
|
|
805
|
+
} catch (e: any) {
|
|
806
|
+
LOG.warn('CLI', `[${this.type}] invalid interactive_prompt_response: ${e?.message || e}`);
|
|
807
|
+
}
|
|
693
808
|
} else if (event === 'provider_state_patch' && data && typeof data === 'object') {
|
|
694
809
|
this.applyProviderResponse(data, { phase: 'immediate' });
|
|
695
810
|
}
|
|
696
811
|
}
|
|
697
812
|
|
|
813
|
+
recordAcknowledgedUserInput(input: InputEnvelope | string): void {
|
|
814
|
+
const content = typeof input === 'string'
|
|
815
|
+
? input.trim()
|
|
816
|
+
: buildCliStructuredInputPrompt(input).trim();
|
|
817
|
+
if (!content) return;
|
|
818
|
+
|
|
819
|
+
const receivedAt = Date.now();
|
|
820
|
+
const dedupKey = `user_input_ack:${crypto
|
|
821
|
+
.createHash('sha256')
|
|
822
|
+
.update(`${this.instanceId}:${content}:${receivedAt}`)
|
|
823
|
+
.digest('hex')
|
|
824
|
+
.slice(0, 24)}`;
|
|
825
|
+
this.appendRuntimeMessage(buildChatMessage({
|
|
826
|
+
role: 'user',
|
|
827
|
+
senderName: 'User',
|
|
828
|
+
kind: 'standard',
|
|
829
|
+
content,
|
|
830
|
+
receivedAt,
|
|
831
|
+
timestamp: receivedAt,
|
|
832
|
+
source: 'runtime_input_ack',
|
|
833
|
+
meta: {
|
|
834
|
+
runtimeInputAck: true,
|
|
835
|
+
provider: this.type,
|
|
836
|
+
workspace: this.workingDir,
|
|
837
|
+
},
|
|
838
|
+
} as ChatMessage), dedupKey);
|
|
839
|
+
}
|
|
840
|
+
|
|
698
841
|
dispose(): void {
|
|
699
842
|
this.adapter.shutdown();
|
|
700
843
|
this.monitor.reset();
|
|
@@ -740,7 +883,60 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
740
883
|
const lastVisible = visibleMessages[visibleMessages.length - 1] as ChatMessage | undefined;
|
|
741
884
|
const role = typeof lastVisible?.role === 'string' ? lastVisible.role.trim().toLowerCase() : '';
|
|
742
885
|
const content = lastVisible ? flattenContent(lastVisible.content).trim() : '';
|
|
743
|
-
|
|
886
|
+
if (role !== 'assistant' || !content) return false;
|
|
887
|
+
// Guard: if the last assistant message looks like an active approval/input prompt,
|
|
888
|
+
// it is not a real completion — the session is still awaiting user input.
|
|
889
|
+
if (looksLikeActiveApprovalPromptText(content)) return false;
|
|
890
|
+
return true;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
private buildCompletedFinalizationDiagnostic(args: {
|
|
894
|
+
blockReason: string;
|
|
895
|
+
latestStatus?: any;
|
|
896
|
+
latestVisibleStatus: string;
|
|
897
|
+
waitedMs: number;
|
|
898
|
+
pending: CompletedDebouncePending;
|
|
899
|
+
emittedAfterFinalizationTimeout: boolean;
|
|
900
|
+
}): Record<string, unknown> {
|
|
901
|
+
let parsed: any = null;
|
|
902
|
+
let parseError: string | undefined;
|
|
903
|
+
try {
|
|
904
|
+
parsed = this.adapter.getScriptParsedStatus();
|
|
905
|
+
} catch (error: any) {
|
|
906
|
+
parseError = error?.message || String(error);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
const visibleMessages = (Array.isArray(parsed?.messages) ? parsed.messages : [])
|
|
910
|
+
.filter((message: any) => isUserFacingChatMessage(message as ChatMessage));
|
|
911
|
+
const lastVisible = visibleMessages[visibleMessages.length - 1] as ChatMessage | undefined;
|
|
912
|
+
const lastVisibleRole = typeof lastVisible?.role === 'string' ? lastVisible.role.trim().toLowerCase() : null;
|
|
913
|
+
const lastVisibleKind = typeof (lastVisible as any)?.kind === 'string' ? (lastVisible as any).kind : null;
|
|
914
|
+
const lastVisibleContentLength = lastVisible ? flattenContent(lastVisible.content).trim().length : 0;
|
|
915
|
+
|
|
916
|
+
return {
|
|
917
|
+
providerType: this.type,
|
|
918
|
+
sessionId: this.instanceId,
|
|
919
|
+
providerSessionId: this.providerSessionId || null,
|
|
920
|
+
workspace: this.workingDir,
|
|
921
|
+
blockReason: args.blockReason,
|
|
922
|
+
emittedAfterFinalizationTimeout: args.emittedAfterFinalizationTimeout,
|
|
923
|
+
waitedMs: args.waitedMs,
|
|
924
|
+
maxWaitMs: COMPLETED_FINALIZATION_MAX_WAIT_MS,
|
|
925
|
+
adapterStatus: typeof args.latestStatus?.status === 'string' ? args.latestStatus.status : null,
|
|
926
|
+
latestVisibleStatus: args.latestVisibleStatus,
|
|
927
|
+
parsedStatus: typeof parsed?.status === 'string' ? parsed.status : (parseError ? 'parse_error' : 'unknown'),
|
|
928
|
+
parseError: parseError || undefined,
|
|
929
|
+
finalAssistantPresent: this.completionHasFinalAssistantMessage(parsed?.messages),
|
|
930
|
+
visibleMessageCount: visibleMessages.length,
|
|
931
|
+
lastVisibleRole,
|
|
932
|
+
lastVisibleKind,
|
|
933
|
+
lastVisibleContentLength,
|
|
934
|
+
pendingStartedAt: this.generatingStartedAt || null,
|
|
935
|
+
pendingFirstObservedAt: args.pending.firstObservedAt,
|
|
936
|
+
pendingTimestamp: args.pending.timestamp,
|
|
937
|
+
pendingDurationSec: args.pending.duration,
|
|
938
|
+
previousBlockReason: args.pending.loggedBlockReason || null,
|
|
939
|
+
};
|
|
744
940
|
}
|
|
745
941
|
|
|
746
942
|
private hasAdapterPendingResponse(): boolean {
|
|
@@ -765,32 +961,73 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
765
961
|
if (!isCliGeneratingLikeStatus(parsedRawStatus)) return false;
|
|
766
962
|
if (adapterRawStatus !== 'idle') return false;
|
|
767
963
|
if (hasNonEmptyCliModalButtons(parsedStatus?.activeModal ?? parsedStatus?.modal)) return false;
|
|
768
|
-
|
|
964
|
+
if (this.hasAdapterPendingResponse()) return false;
|
|
965
|
+
// Do not suppress when the adapter's raw response buffer is still non-empty.
|
|
966
|
+
// This catches the case where isWaitingForResponse has already flipped to false
|
|
967
|
+
// (so getPartialResponse() returns '') but the provider's native parser still
|
|
968
|
+
// reports generating because it's parsing buffered content. Suppressing the
|
|
969
|
+
// finalization block here would emit a false completion event while the provider
|
|
970
|
+
// session is still actively processing its response stream.
|
|
971
|
+
const adapterAny = this.adapter as any;
|
|
972
|
+
if (typeof adapterAny?.responseBuffer === 'string' && adapterAny.responseBuffer.trim()) return false;
|
|
973
|
+
return true;
|
|
769
974
|
}
|
|
770
975
|
|
|
771
|
-
private
|
|
772
|
-
if (latestVisibleStatus !== 'idle') return `status:${latestVisibleStatus}
|
|
976
|
+
private getCompletedFinalizationBlock(latestVisibleStatus: string, pending: CompletedDebouncePending): CompletedFinalizationBlock | null {
|
|
977
|
+
if (latestVisibleStatus !== 'idle') return { reason: `status:${latestVisibleStatus}`, terminal: true };
|
|
773
978
|
|
|
774
979
|
const adapterAny = this.adapter as any;
|
|
775
|
-
|
|
776
|
-
if (
|
|
980
|
+
const approvalResolvedIdle = pending.previousStatus === 'waiting_approval';
|
|
981
|
+
if (!approvalResolvedIdle) {
|
|
982
|
+
if (adapterAny?.isWaitingForResponse === true) return { reason: 'adapter_waiting_for_response', terminal: true };
|
|
983
|
+
if (adapterAny?.currentTurnScope) return { reason: 'adapter_turn_scope_active', terminal: true };
|
|
984
|
+
if (this.hasAdapterPendingResponse()) return { reason: 'adapter_pending_response', terminal: true };
|
|
985
|
+
}
|
|
777
986
|
|
|
778
987
|
const partial = typeof this.adapter.getPartialResponse === 'function'
|
|
779
988
|
? this.adapter.getPartialResponse()
|
|
780
989
|
: '';
|
|
781
|
-
if (typeof partial === 'string' && partial.trim()) return 'partial_response_pending';
|
|
990
|
+
if (typeof partial === 'string' && partial.trim()) return { reason: 'partial_response_pending', terminal: true };
|
|
782
991
|
|
|
783
992
|
let parsed: any;
|
|
784
993
|
try {
|
|
785
994
|
parsed = this.adapter.getScriptParsedStatus();
|
|
786
995
|
} catch (error: any) {
|
|
787
|
-
return `parse_error:${error?.message || String(error)}
|
|
996
|
+
return { reason: `parse_error:${error?.message || String(error)}` };
|
|
788
997
|
}
|
|
789
998
|
|
|
790
999
|
const parsedStatus = typeof parsed?.status === 'string' ? parsed.status : 'unknown';
|
|
791
|
-
if (parsedStatus !== 'idle')
|
|
792
|
-
|
|
793
|
-
|
|
1000
|
+
if (parsedStatus !== 'idle') {
|
|
1001
|
+
const adapterStatus = this.adapter.getStatus({ allowParse: false });
|
|
1002
|
+
if (this.shouldSuppressStaleParsedBusyStatus(parsed, adapterStatus)) return null;
|
|
1003
|
+
return { reason: `parsed_status:${parsedStatus}`, terminal: isCliGeneratingLikeStatus(parsedStatus) };
|
|
1004
|
+
}
|
|
1005
|
+
if (parsed?.activeModal || parsed?.modal) return { reason: 'parsed_modal_active', terminal: true };
|
|
1006
|
+
// SpecCliAdapter never populates parsed.messages — chat history flows
|
|
1007
|
+
// through the daemon's native-history pipeline, not the status hook.
|
|
1008
|
+
// Skipping the final-assistant gate avoids a 30s stall on every turn
|
|
1009
|
+
// for spec-routed providers (agy / codex / claude / hermes).
|
|
1010
|
+
const adapterOwnsMessagesElsewhere = (this.adapter as any)?.chatMessagesOwnedExternally === true;
|
|
1011
|
+
if (!adapterOwnsMessagesElsewhere
|
|
1012
|
+
&& !this.completionHasFinalAssistantMessage(parsed?.messages)) {
|
|
1013
|
+
return { reason: 'missing_final_assistant' };
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// Guard: if the screen still shows an approval/choice prompt as the last visible text,
|
|
1017
|
+
// the turn is not complete even if the parsed status says idle and there is an assistant
|
|
1018
|
+
// message. This catches the case where waiting_approval→idle transitions occur before
|
|
1019
|
+
// the modal has been resolved (e.g. the PTY rendered the prompt but no button press fired).
|
|
1020
|
+
try {
|
|
1021
|
+
const screenText = typeof (this.adapter as any).getScreenText === 'function'
|
|
1022
|
+
? String((this.adapter as any).getScreenText() || '')
|
|
1023
|
+
: '';
|
|
1024
|
+
if (screenText) {
|
|
1025
|
+
const tailLines = screenText.split(/\r?\n/).slice(-16).join('\n');
|
|
1026
|
+
if (looksLikeActiveApprovalPromptText(tailLines)) {
|
|
1027
|
+
return { reason: 'screen_shows_approval_prompt', terminal: approvalResolvedIdle };
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
} catch { /* defensive: screen text read is best-effort */ }
|
|
794
1031
|
|
|
795
1032
|
return null;
|
|
796
1033
|
}
|
|
@@ -817,10 +1054,11 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
817
1054
|
return;
|
|
818
1055
|
}
|
|
819
1056
|
|
|
820
|
-
const
|
|
821
|
-
if (
|
|
1057
|
+
const block = this.getCompletedFinalizationBlock(latestVisibleStatus, pending);
|
|
1058
|
+
if (block) {
|
|
1059
|
+
const blockReason = block.reason;
|
|
822
1060
|
const waitedMs = Date.now() - pending.firstObservedAt;
|
|
823
|
-
if (waitedMs < COMPLETED_FINALIZATION_MAX_WAIT_MS) {
|
|
1061
|
+
if (block.terminal || waitedMs < COMPLETED_FINALIZATION_MAX_WAIT_MS) {
|
|
824
1062
|
if (pending.loggedBlockReason !== blockReason) {
|
|
825
1063
|
LOG.info('CLI', `[${this.type}] waiting to emit completed until transcript finalizes (${blockReason})`);
|
|
826
1064
|
pending.loggedBlockReason = blockReason;
|
|
@@ -828,10 +1066,29 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
828
1066
|
this.scheduleCompletedDebounceFlush(COMPLETED_FINALIZATION_RETRY_MS);
|
|
829
1067
|
return;
|
|
830
1068
|
}
|
|
831
|
-
|
|
1069
|
+
const completionDiagnostic = this.buildCompletedFinalizationDiagnostic({
|
|
1070
|
+
blockReason,
|
|
1071
|
+
latestStatus,
|
|
1072
|
+
latestVisibleStatus,
|
|
1073
|
+
waitedMs,
|
|
1074
|
+
pending,
|
|
1075
|
+
emittedAfterFinalizationTimeout: true,
|
|
1076
|
+
});
|
|
1077
|
+
LOG.warn('CLI', `[${this.type}] emitting completed event after ${waitedMs}ms without finalized assistant turn (${blockReason})`);
|
|
1078
|
+
this.pushEvent({
|
|
1079
|
+
event: 'agent:generating_completed',
|
|
1080
|
+
chatTitle: pending.chatTitle,
|
|
1081
|
+
duration: pending.duration,
|
|
1082
|
+
timestamp: pending.timestamp,
|
|
1083
|
+
finalSummary: blockReason.startsWith('parsed_status:')
|
|
1084
|
+
? ''
|
|
1085
|
+
: extractFinalSummaryFromMessages(this.adapter?.getScriptParsedStatus()?.messages),
|
|
1086
|
+
completionDiagnostic,
|
|
1087
|
+
});
|
|
832
1088
|
this.completedDebouncePending = null;
|
|
833
1089
|
this.completedDebounceTimer = null;
|
|
834
1090
|
this.generatingStartedAt = 0;
|
|
1091
|
+
this.lastApprovalEventFingerprint = '';
|
|
835
1092
|
return;
|
|
836
1093
|
}
|
|
837
1094
|
|
|
@@ -846,6 +1103,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
846
1103
|
this.completedDebouncePending = null;
|
|
847
1104
|
this.completedDebounceTimer = null;
|
|
848
1105
|
this.generatingStartedAt = 0;
|
|
1106
|
+
this.lastApprovalEventFingerprint = '';
|
|
849
1107
|
}
|
|
850
1108
|
|
|
851
1109
|
private maybeAutoApproveStatus(adapterStatus: any, now = Date.now()): boolean {
|
|
@@ -853,15 +1111,46 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
853
1111
|
// Guard re-entry: onStatusChange/getState can observe the same modal multiple
|
|
854
1112
|
// times while the PTY absorbs the approval key. Without this flag, repeated
|
|
855
1113
|
// snapshots would write stray keys into the input once the modal dismisses.
|
|
856
|
-
|
|
1114
|
+
// However, Claude Code can present a second approval immediately after the
|
|
1115
|
+
// first. Resolve a changed modal signature even while the previous write is
|
|
1116
|
+
// still inside the short busy window.
|
|
1117
|
+
if (!autoApproveActive) {
|
|
1118
|
+
this.lastAutoApprovalSignature = '';
|
|
1119
|
+
return autoApproveActive;
|
|
1120
|
+
}
|
|
1121
|
+
const modal = adapterStatus.activeModal;
|
|
1122
|
+
// (fix) Do not auto-approve when no concrete modal/buttons are present.
|
|
1123
|
+
// Claude TUI flaps between paints; without this guard adapterStatus
|
|
1124
|
+
// could report status=waiting_approval with activeModal=null (or with
|
|
1125
|
+
// an empty buttons array briefly) and we'd still call
|
|
1126
|
+
// resolveModal(-1) — which used to type "1" into the prompt
|
|
1127
|
+
// repeatedly. Skip until a real modal is captured.
|
|
1128
|
+
const buttons = Array.isArray(modal?.buttons)
|
|
1129
|
+
? modal.buttons.map((b: any) => String(b || '').trim()).filter(Boolean)
|
|
1130
|
+
: [];
|
|
1131
|
+
if (!modal || buttons.length === 0) {
|
|
1132
|
+
return autoApproveActive;
|
|
1133
|
+
}
|
|
1134
|
+
const { index: buttonIndex, label: buttonLabel } = pickAutoApprovalButton(buttons);
|
|
1135
|
+
if (buttonIndex < 0) {
|
|
1136
|
+
// No concrete button matched — don't pick a random index, just
|
|
1137
|
+
// surface the modal so the user can decide.
|
|
1138
|
+
return autoApproveActive;
|
|
1139
|
+
}
|
|
1140
|
+
const signature = [
|
|
1141
|
+
typeof modal?.message === 'string' ? modal.message.trim() : '',
|
|
1142
|
+
buttons.join('|'),
|
|
1143
|
+
buttonIndex,
|
|
1144
|
+
].join('::');
|
|
1145
|
+
if (!this.autoApproveBusy || signature !== this.lastAutoApprovalSignature) {
|
|
857
1146
|
this.autoApproveBusy = true;
|
|
1147
|
+
this.lastAutoApprovalSignature = signature;
|
|
858
1148
|
if (this.autoApproveBusyTimer) clearTimeout(this.autoApproveBusyTimer);
|
|
859
1149
|
this.autoApproveBusyTimer = setTimeout(() => {
|
|
860
1150
|
this.autoApproveBusy = false;
|
|
861
1151
|
this.autoApproveBusyTimer = null;
|
|
1152
|
+
this.lastAutoApprovalSignature = '';
|
|
862
1153
|
}, 2000);
|
|
863
|
-
const modal = adapterStatus.activeModal;
|
|
864
|
-
const { index: buttonIndex, label: buttonLabel } = pickApprovalButton(modal?.buttons, this.provider);
|
|
865
1154
|
this.recordAutoApproval(modal?.message, buttonLabel, now);
|
|
866
1155
|
setTimeout(() => {
|
|
867
1156
|
this.adapter.resolveModal(buttonIndex);
|
|
@@ -876,6 +1165,13 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
876
1165
|
// during long-running CLI sessions. Keep this path on adapter-owned light
|
|
877
1166
|
// state only; rich provider parsing is reserved for getState/read_chat.
|
|
878
1167
|
const adapterStatus = this.adapter.getStatus({ allowParse: false });
|
|
1168
|
+
const adapterProviderSessionId = normalizeProviderSessionId(
|
|
1169
|
+
this.provider,
|
|
1170
|
+
typeof adapterStatus?.providerSessionId === 'string' ? adapterStatus.providerSessionId : '',
|
|
1171
|
+
);
|
|
1172
|
+
if (adapterProviderSessionId) {
|
|
1173
|
+
this.promoteProviderSessionId(adapterProviderSessionId);
|
|
1174
|
+
}
|
|
879
1175
|
const parsedStatus = null;
|
|
880
1176
|
const rawStatus = adapterStatus.status;
|
|
881
1177
|
const autoApproveActive = this.maybeAutoApproveStatus(adapterStatus, now);
|
|
@@ -926,10 +1222,14 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
926
1222
|
if (!this.generatingStartedAt) this.generatingStartedAt = now;
|
|
927
1223
|
const modal = adapterStatus.activeModal;
|
|
928
1224
|
LOG.info('CLI', `[${this.type}] approval modal: "${modal?.message?.slice(0, 80) ?? 'none'}"`);
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1225
|
+
const approvalFingerprint = JSON.stringify({
|
|
1226
|
+
message: typeof modal?.message === 'string' ? modal.message.trim() : '',
|
|
1227
|
+
buttons: Array.isArray(modal?.buttons) ? modal.buttons.map((button: unknown) => String(button).trim()) : [],
|
|
1228
|
+
});
|
|
1229
|
+
// PTY redraws can briefly leave waiting_approval and then re-enter it.
|
|
1230
|
+
// Keep one coordinator event per logical modal until the turn completes.
|
|
1231
|
+
if (this.lastStatus !== 'waiting_approval' && approvalFingerprint !== this.lastApprovalEventFingerprint) {
|
|
1232
|
+
this.lastApprovalEventFingerprint = approvalFingerprint;
|
|
933
1233
|
this.appendRuntimeSystemMessage(
|
|
934
1234
|
this.formatApprovalRequestMessage(modal?.message, modal?.buttons),
|
|
935
1235
|
`approval_request:${now}`,
|
|
@@ -943,20 +1243,65 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
943
1243
|
}
|
|
944
1244
|
} else if (newStatus === 'idle' && (this.lastStatus === 'generating' || this.lastStatus === 'waiting_approval')) {
|
|
945
1245
|
const duration = this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1000) : 0;
|
|
946
|
-
// If debounce still pending (generating lasted < 1s), cancel both events
|
|
1246
|
+
// If debounce still pending (generating lasted < 1s), cancel both UI events.
|
|
1247
|
+
// Still emit agent:generating_completed so mesh orchestration can record
|
|
1248
|
+
// task_completed for direct dispatches that complete faster than the debounce.
|
|
947
1249
|
if (this.generatingDebouncePending) {
|
|
948
|
-
|
|
1250
|
+
const shortDurationMs = this.generatingStartedAt ? now - this.generatingStartedAt : 0;
|
|
1251
|
+
LOG.info('CLI', `[${this.type}] suppressed short generating (${shortDurationMs}ms)`);
|
|
949
1252
|
if (this.generatingDebounceTimer) { clearTimeout(this.generatingDebounceTimer); this.generatingDebounceTimer = null; }
|
|
950
1253
|
this.generatingDebouncePending = null;
|
|
951
1254
|
this.generatingStartedAt = 0;
|
|
1255
|
+
// Emit completion for mesh task association even though the UI generating
|
|
1256
|
+
// started/completed pair is suppressed (too short for visible UI update).
|
|
1257
|
+
let shortFinalSummary: string | undefined;
|
|
1258
|
+
try { shortFinalSummary = extractFinalSummaryFromMessages(this.adapter?.getScriptParsedStatus()?.messages); } catch { /* best-effort */ }
|
|
1259
|
+
if ((this.provider as any).requiresFinalAssistantBeforeIdle === true && !shortFinalSummary) {
|
|
1260
|
+
LOG.info('CLI', `[${this.type}] suppressed short completion without final assistant evidence`);
|
|
1261
|
+
} else {
|
|
1262
|
+
this.pushEvent({
|
|
1263
|
+
event: 'agent:generating_completed',
|
|
1264
|
+
chatTitle,
|
|
1265
|
+
duration: 0,
|
|
1266
|
+
timestamp: now,
|
|
1267
|
+
finalSummary: shortFinalSummary,
|
|
1268
|
+
completionDiagnostic: {
|
|
1269
|
+
reason: 'short_generating_suppressed',
|
|
1270
|
+
shortDurationMs,
|
|
1271
|
+
},
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
952
1274
|
} else {
|
|
953
1275
|
// Debounce completed, then require the rich transcript path that read_chat
|
|
954
1276
|
// uses to show an idle turn whose last user-facing message is assistant.
|
|
955
|
-
this.completedDebouncePending = {
|
|
1277
|
+
this.completedDebouncePending = {
|
|
1278
|
+
chatTitle,
|
|
1279
|
+
duration,
|
|
1280
|
+
timestamp: now,
|
|
1281
|
+
firstObservedAt: now,
|
|
1282
|
+
previousStatus: this.lastStatus,
|
|
1283
|
+
};
|
|
956
1284
|
this.scheduleCompletedDebounceFlush(3000);
|
|
957
1285
|
}
|
|
958
1286
|
} else if (newStatus === 'idle' && this.lastStatus === 'starting') {
|
|
959
1287
|
this.pushEvent({ event: 'agent:ready', chatTitle, timestamp: now });
|
|
1288
|
+
} else if (newStatus === 'error') {
|
|
1289
|
+
if (this.generatingDebounceTimer) { clearTimeout(this.generatingDebounceTimer); this.generatingDebounceTimer = null; }
|
|
1290
|
+
this.generatingDebouncePending = null;
|
|
1291
|
+
if (this.completedDebounceTimer) { clearTimeout(this.completedDebounceTimer); this.completedDebounceTimer = null; }
|
|
1292
|
+
this.completedDebouncePending = null;
|
|
1293
|
+
this.errorMessage = adapterStatus.errorMessage || this.errorMessage;
|
|
1294
|
+
this.errorReason = (adapterStatus.errorReason as ProviderErrorReason) || this.errorReason;
|
|
1295
|
+
this.pushEvent({
|
|
1296
|
+
event: 'agent:stopped',
|
|
1297
|
+
chatTitle,
|
|
1298
|
+
timestamp: now,
|
|
1299
|
+
finalSummary: adapterStatus.errorMessage || adapterStatus.errorReason || 'Provider reported an error',
|
|
1300
|
+
completionDiagnostic: {
|
|
1301
|
+
reason: adapterStatus.errorReason || 'provider_error',
|
|
1302
|
+
errorMessage: adapterStatus.errorMessage || undefined,
|
|
1303
|
+
},
|
|
1304
|
+
});
|
|
960
1305
|
} else if (newStatus === 'stopped') {
|
|
961
1306
|
// Cancel any pending debounce
|
|
962
1307
|
if (this.generatingDebounceTimer) { clearTimeout(this.generatingDebounceTimer); this.generatingDebounceTimer = null; }
|
|
@@ -977,7 +1322,31 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
977
1322
|
// Monitor check (cooldown based notification, IDE/CLI common)
|
|
978
1323
|
const agentKey = `${this.type}:cli`;
|
|
979
1324
|
const monitorEvents = this.monitor.check(agentKey, newStatus, now, progressFingerprint);
|
|
1325
|
+
const monitorParsedStatus: any = parsedStatus;
|
|
980
1326
|
for (const me of monitorEvents) {
|
|
1327
|
+
if (
|
|
1328
|
+
me.type === 'monitor:long_generating'
|
|
1329
|
+
&& this.completionHasFinalAssistantMessage(monitorParsedStatus?.messages)
|
|
1330
|
+
&& !this.hasAdapterPendingResponse()
|
|
1331
|
+
&& !hasNonEmptyCliModalButtons(monitorParsedStatus?.activeModal ?? monitorParsedStatus?.modal)
|
|
1332
|
+
) {
|
|
1333
|
+
this.pushEvent({
|
|
1334
|
+
event: 'agent:generating_completed',
|
|
1335
|
+
chatTitle,
|
|
1336
|
+
duration: this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1000) : undefined,
|
|
1337
|
+
timestamp: me.timestamp,
|
|
1338
|
+
finalSummary: extractFinalSummaryFromMessages(monitorParsedStatus?.messages),
|
|
1339
|
+
completionDiagnostic: {
|
|
1340
|
+
providerType: this.type,
|
|
1341
|
+
sessionId: this.instanceId,
|
|
1342
|
+
providerSessionId: this.providerSessionId || null,
|
|
1343
|
+
reconciliationReason: 'long_generating_monitor_final_summary',
|
|
1344
|
+
finalAssistantPresent: true,
|
|
1345
|
+
},
|
|
1346
|
+
});
|
|
1347
|
+
this.generatingStartedAt = 0;
|
|
1348
|
+
continue;
|
|
1349
|
+
}
|
|
981
1350
|
this.pushEvent({ event: me.type, agentKey: me.agentKey, message: me.message, elapsedSec: me.elapsedSec, timestamp: me.timestamp });
|
|
982
1351
|
}
|
|
983
1352
|
}
|
|
@@ -1258,12 +1627,28 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1258
1627
|
index,
|
|
1259
1628
|
source: 'parsed',
|
|
1260
1629
|
}));
|
|
1630
|
+
const getRole = (message: ChatMessage): string => typeof message.role === 'string'
|
|
1631
|
+
? message.role.trim().toLowerCase()
|
|
1632
|
+
: '';
|
|
1261
1633
|
const runtimeEntries: MergeEntry[] = this.runtimeMessages.map((entry, index) => ({
|
|
1262
1634
|
message: entry.message,
|
|
1263
1635
|
index: parsedMessages.length + index,
|
|
1264
|
-
source: 'runtime',
|
|
1636
|
+
source: 'runtime' as const,
|
|
1265
1637
|
runtimeKey: entry.key,
|
|
1266
|
-
}))
|
|
1638
|
+
})).filter((entry) => {
|
|
1639
|
+
const meta = entry.message.meta && typeof entry.message.meta === 'object' && !Array.isArray(entry.message.meta)
|
|
1640
|
+
? entry.message.meta as Record<string, unknown>
|
|
1641
|
+
: {};
|
|
1642
|
+
if (meta.runtimeInputAck !== true) return true;
|
|
1643
|
+
const runtimeText = flattenContent(entry.message.content).replace(/\s+/g, ' ').trim();
|
|
1644
|
+
if (!runtimeText) return false;
|
|
1645
|
+
return !parsedEntries.some((parsedEntry) => {
|
|
1646
|
+
const parsedRole = getRole(parsedEntry.message);
|
|
1647
|
+
if (parsedRole !== 'user' && parsedRole !== 'human') return false;
|
|
1648
|
+
const parsedText = flattenContent(parsedEntry.message.content).replace(/\s+/g, ' ').trim();
|
|
1649
|
+
return parsedText === runtimeText;
|
|
1650
|
+
});
|
|
1651
|
+
});
|
|
1267
1652
|
const getTime = (message: ChatMessage): number => {
|
|
1268
1653
|
const value = typeof message.receivedAt === 'number'
|
|
1269
1654
|
? message.receivedAt
|
|
@@ -1273,9 +1658,6 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1273
1658
|
return Number.isFinite(value) && value > 0 ? value : 0;
|
|
1274
1659
|
};
|
|
1275
1660
|
|
|
1276
|
-
const getRole = (message: ChatMessage): string => typeof message.role === 'string'
|
|
1277
|
-
? message.role.trim().toLowerCase()
|
|
1278
|
-
: '';
|
|
1279
1661
|
const isRuntimeOverlay = (entry: MergeEntry): boolean => {
|
|
1280
1662
|
if (entry.source !== 'runtime') return false;
|
|
1281
1663
|
const key = typeof entry.runtimeKey === 'string' ? entry.runtimeKey.trim().toLowerCase() : '';
|
|
@@ -1341,7 +1723,9 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1341
1723
|
this.providerSessionId = nextSessionId;
|
|
1342
1724
|
this.historyWriter.promoteHistorySession(this.type, previousHistorySessionId, nextSessionId);
|
|
1343
1725
|
this.historyWriter.writeSessionStart(this.type, nextSessionId, this.workingDir, this.instanceId);
|
|
1344
|
-
this.
|
|
1726
|
+
if (this.shouldHydrateExistingProviderHistory()) {
|
|
1727
|
+
this.restorePersistedHistoryFromCurrentSession();
|
|
1728
|
+
}
|
|
1345
1729
|
this.adapter.updateRuntimeMeta({ providerSessionId: nextSessionId });
|
|
1346
1730
|
this.onProviderSessionResolved?.({
|
|
1347
1731
|
instanceId: this.instanceId,
|
|
@@ -1354,9 +1738,27 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1354
1738
|
LOG.info('CLI', `[${this.type}] discovered provider session id: ${nextSessionId}`);
|
|
1355
1739
|
}
|
|
1356
1740
|
|
|
1741
|
+
private shouldHydrateExistingProviderHistory(): boolean {
|
|
1742
|
+
return this.launchMode === 'resume' || this.launchMode === 'manual';
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
private shouldSuppressFreshLaunchStartupReplay(parsedMessages: unknown[], parsedStatus: any, adapterStatus: any, parsedProviderSessionId = ''): boolean {
|
|
1746
|
+
if (this.launchMode !== 'new') return false;
|
|
1747
|
+
if (this.providerSessionId) return false;
|
|
1748
|
+
if (!Array.isArray(parsedMessages) || parsedMessages.length === 0) return false;
|
|
1749
|
+
if (!isIdleStatus(adapterStatus?.status) || !isIdleStatus(parsedStatus?.status)) return false;
|
|
1750
|
+
if (parsedProviderSessionId) return true;
|
|
1751
|
+
|
|
1752
|
+
const newestMessageAt = parsedMessages.reduce<number>((newest, message) => Math.max(newest, getMessageTime(message)), 0);
|
|
1753
|
+
|
|
1754
|
+
// Untimestamped idle parser output during a fresh launch is usually the
|
|
1755
|
+
// provider's last workspace transcript before a new turn exists.
|
|
1756
|
+
return newestMessageAt === 0;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1357
1759
|
private syncCanonicalSavedHistoryIfNeeded(): boolean {
|
|
1358
1760
|
if (!this.providerSessionId) return false;
|
|
1359
|
-
const canonicalHistory = this.provider.
|
|
1761
|
+
const canonicalHistory = this.provider.nativeHistory;
|
|
1360
1762
|
if (!canonicalHistory) return false;
|
|
1361
1763
|
|
|
1362
1764
|
if (isNativeSourceCanonicalHistory(canonicalHistory)) {
|
|
@@ -1418,9 +1820,9 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1418
1820
|
private restorePersistedHistoryFromCurrentSession(): void {
|
|
1419
1821
|
if (!this.providerSessionId) return;
|
|
1420
1822
|
this.syncCanonicalSavedHistoryIfNeeded();
|
|
1421
|
-
const restoredHistory = isNativeSourceCanonicalHistory(this.provider.
|
|
1823
|
+
const restoredHistory = isNativeSourceCanonicalHistory(this.provider.nativeHistory)
|
|
1422
1824
|
? readProviderChatHistory(this.type, {
|
|
1423
|
-
canonicalHistory: this.provider.
|
|
1825
|
+
canonicalHistory: this.provider.nativeHistory,
|
|
1424
1826
|
historySessionId: this.providerSessionId,
|
|
1425
1827
|
workspace: this.workingDir,
|
|
1426
1828
|
offset: 0,
|