@adhdev/daemon-core 0.9.82-rc.16 → 0.9.82-rc.161
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 +19 -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 +104 -63
- package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +27 -0
- package/dist/commands/handler.d.ts +66 -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 +31 -6
- package/dist/index.js +17210 -3575
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +17127 -3551
- 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 +77 -5
- package/dist/mesh/mesh-fast-forward.d.ts +39 -0
- package/dist/mesh/mesh-host-ownership.d.ts +9 -0
- package/dist/mesh/mesh-ledger.d.ts +58 -1
- package/dist/mesh/mesh-refine-status.d.ts +26 -0
- package/dist/mesh/mesh-work-queue.d.ts +44 -5
- package/dist/mesh/preview-freshness.d.ts +18 -0
- package/dist/mesh/refine-config.d.ts +193 -0
- package/dist/mesh/worktree-bootstrap-config.d.ts +113 -0
- package/dist/providers/approval-utils.d.ts +9 -0
- package/dist/providers/cli-provider-instance.d.ts +7 -2
- package/dist/providers/contracts.d.ts +98 -5
- 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-loader.d.ts +19 -1
- 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 +64 -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 +76 -0
- package/dist/providers/spec/driver.d.ts +148 -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 +507 -0
- package/dist/providers/spec/types.d.ts +211 -0
- package/dist/providers/transcript-v2.d.ts +176 -0
- package/dist/repo-mesh-types.d.ts +100 -1
- package/dist/sessions/registry.d.ts +3 -0
- package/dist/shared-types.d.ts +12 -0
- package/dist/status/reporter.d.ts +2 -0
- package/dist/status/snapshot.d.ts +1 -0
- package/dist/types.d.ts +5 -0
- package/package.json +7 -2
- 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 +18 -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 +664 -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 +57 -11
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +17 -1
- package/src/cli-adapters/terminal-backends/xterm-backend.ts +8 -1
- package/src/commands/chat-commands.ts +1597 -60
- package/src/commands/cli-manager.ts +195 -3
- package/src/commands/handler.ts +547 -2
- package/src/commands/mesh-coordinator.ts +231 -127
- package/src/commands/router.ts +3547 -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 +91 -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 +820 -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.ts +39 -5
- package/src/providers/chat-message-normalization.ts +4 -11
- package/src/providers/cli-provider-instance.ts +388 -47
- package/src/providers/contracts.ts +105 -5
- package/src/providers/ide-provider-instance.ts +17 -3
- 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-loader.ts +420 -31
- package/src/providers/provider-schema.ts +31 -13
- 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 +245 -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 +457 -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 +119 -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 +153 -0
- package/src/providers/spec/cli-adapter.ts +318 -0
- package/src/providers/spec/driver.ts +498 -0
- package/src/providers/spec/evaluator.ts +268 -0
- package/src/providers/spec/loader.ts +130 -0
- package/src/providers/spec/native-history-executor.ts +612 -0
- package/src/providers/spec/route.ts +51 -0
- package/src/providers/spec/schema.gen.ts +507 -0
- package/src/providers/spec/schema.json +210 -0
- package/src/providers/spec/types.ts +230 -0
- package/src/providers/transcript-v2.ts +567 -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 +9 -0
- package/src/status/builders.ts +23 -6
- package/src/status/reporter.ts +15 -0
- package/src/status/snapshot.ts +35 -11
- package/src/system/host-memory.ts +29 -12
- package/src/types.ts +5 -0
|
@@ -15,13 +15,14 @@ import { assertProviderSupportsDeclaredInput, getEffectiveMessageInputSupport }
|
|
|
15
15
|
import type { ProviderInstance, ProviderState, ProviderEvent, InstanceContext, ProviderErrorReason, HotChatSessionState, SessionModalState } from './provider-instance.js';
|
|
16
16
|
import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
|
|
17
17
|
import type { CliProviderModule } from '../cli-adapters/provider-cli-adapter.js';
|
|
18
|
+
import { createCliAdapter } from './spec/route.js';
|
|
18
19
|
import type { PtyRuntimeMetadata, PtyTransportFactory } from '../cli-adapters/pty-transport.js';
|
|
19
20
|
import { StatusMonitor } from './status-monitor.js';
|
|
20
21
|
import { ChatHistoryWriter, isNativeSourceCanonicalHistory, materializeProviderNativeHistory, readChatHistory, readProviderChatHistory } from '../config/chat-history.js';
|
|
21
22
|
import { LOG } from '../logging/logger.js';
|
|
22
23
|
import type { ChatMessage } from '../types.js';
|
|
23
24
|
import { buildPersistedProviderEffectMessage, normalizeProviderEffects } from './control-effects.js';
|
|
24
|
-
import { formatAutoApprovalMessage, pickApprovalButton } from './approval-utils.js';
|
|
25
|
+
import { formatAutoApprovalMessage, pickApprovalButton, looksLikeActiveApprovalPromptText } from './approval-utils.js';
|
|
25
26
|
import { getCliScriptCommand, parseCliScriptResult } from './cli-script-results.js';
|
|
26
27
|
import { mergeProviderPatchState, resolveProviderStateSurface } from './provider-patch-state.js';
|
|
27
28
|
import { normalizeProviderSessionId } from './provider-session-id.js';
|
|
@@ -43,6 +44,24 @@ type CompletedDebouncePending = {
|
|
|
43
44
|
loggedBlockReason?: string;
|
|
44
45
|
};
|
|
45
46
|
|
|
47
|
+
function isIdleStatus(value: unknown): boolean {
|
|
48
|
+
const status = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
49
|
+
return !status || status === 'idle' || status === 'ready';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
function getMessageTime(message: unknown): number {
|
|
54
|
+
if (!message || typeof message !== 'object') return 0;
|
|
55
|
+
const record = message as { receivedAt?: unknown; timestamp?: unknown };
|
|
56
|
+
const value = Number(record.receivedAt ?? record.timestamp ?? 0);
|
|
57
|
+
return Number.isFinite(value) ? value : 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
type CompletedFinalizationBlock = {
|
|
61
|
+
reason: string;
|
|
62
|
+
terminal?: boolean;
|
|
63
|
+
};
|
|
64
|
+
|
|
46
65
|
const COMPLETED_FINALIZATION_RETRY_MS = 1000;
|
|
47
66
|
const COMPLETED_FINALIZATION_MAX_WAIT_MS = 30_000;
|
|
48
67
|
|
|
@@ -312,6 +331,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
312
331
|
private lastApprovalEventAt = 0;
|
|
313
332
|
private autoApproveBusy = false;
|
|
314
333
|
private autoApproveBusyTimer: NodeJS.Timeout | null = null;
|
|
334
|
+
private lastAutoApprovalSignature = '';
|
|
315
335
|
private controlValues: Record<string, string | number | boolean> = {};
|
|
316
336
|
private summaryMetadata: unknown = undefined;
|
|
317
337
|
private appliedEffectKeys = new Set<string>();
|
|
@@ -370,7 +390,10 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
370
390
|
this.providerSessionId = options?.providerSessionId;
|
|
371
391
|
this.launchMode = options?.launchMode || 'new';
|
|
372
392
|
this.onProviderSessionResolved = options?.onProviderSessionResolved;
|
|
373
|
-
this.adapter =
|
|
393
|
+
this.adapter = createCliAdapter(provider as CliProviderModule, workingDir, cliArgs, options?.extraEnv || {}, transportFactory) as ProviderCliAdapter;
|
|
394
|
+
if (this.providerSessionId) {
|
|
395
|
+
this.adapter.updateRuntimeMeta({ providerSessionId: this.providerSessionId });
|
|
396
|
+
}
|
|
374
397
|
this.monitor = new StatusMonitor();
|
|
375
398
|
this.historyWriter = new ChatHistoryWriter();
|
|
376
399
|
}
|
|
@@ -412,7 +435,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
412
435
|
await this.adapter.spawn();
|
|
413
436
|
await this.enforceFreshSessionLaunchIfNeeded();
|
|
414
437
|
this.maybeAppendRuntimeRecoveryMessage(this.adapter.getRuntimeMetadata());
|
|
415
|
-
if (this.providerSessionId) {
|
|
438
|
+
if (this.providerSessionId && this.shouldHydrateExistingProviderHistory()) {
|
|
416
439
|
this.restorePersistedHistoryFromCurrentSession();
|
|
417
440
|
}
|
|
418
441
|
if (this.providerSessionId && this.launchMode === 'resume') {
|
|
@@ -486,14 +509,35 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
486
509
|
}
|
|
487
510
|
|
|
488
511
|
getState(): ProviderState {
|
|
512
|
+
// TODO(phase5-sandbox): JS override scripts (detectStatus, parseApproval,
|
|
513
|
+
// parseSession) are currently invoked by CliScriptRunner.invoke() via direct
|
|
514
|
+
// function calls — the scripts run in the daemon process with full Node.js
|
|
515
|
+
// access and no resource limits.
|
|
516
|
+
//
|
|
517
|
+
// When Phase 5 lands, CliScriptRunner should route these calls through a
|
|
518
|
+
// SandboxedScriptRunner (see providers/sdk/v1/sandbox/script-runner.ts) so
|
|
519
|
+
// that each call gets a fresh isolated-vm context with a 50 ms CPU limit and
|
|
520
|
+
// a 32 MB memory cap. The execution path to change is:
|
|
521
|
+
// CliScriptRunner.invoke() → SandboxedScriptRunner.run(scriptSource, context)
|
|
522
|
+
//
|
|
523
|
+
// This getState() call-site is NOT where the change goes — the wiring belongs
|
|
524
|
+
// in cli-script-runner.ts (CliScriptRunner.detectStatus / parseApproval /
|
|
525
|
+
// parseSession), with provider-loader.ts updated to store script source strings
|
|
526
|
+
// alongside the loaded function references for extended-legacy providers.
|
|
489
527
|
const adapterStatus = this.adapter.getStatus();
|
|
490
528
|
let parsedStatus: any = null;
|
|
491
529
|
let parseErrorMessage: string | undefined;
|
|
492
530
|
if (typeof this.adapter.getScriptParsedStatus === 'function') {
|
|
493
531
|
try {
|
|
494
532
|
parsedStatus = this.adapter.getScriptParsedStatus() || null;
|
|
495
|
-
|
|
496
|
-
|
|
533
|
+
const parsedErrorMessage = typeof parsedStatus?.errorMessage === 'string' && parsedStatus.errorMessage.trim()
|
|
534
|
+
? parsedStatus.errorMessage.trim()
|
|
535
|
+
: undefined;
|
|
536
|
+
const parsedErrorReason = typeof parsedStatus?.errorReason === 'string' && parsedStatus.errorReason.trim()
|
|
537
|
+
? parsedStatus.errorReason.trim() as ProviderErrorReason
|
|
538
|
+
: undefined;
|
|
539
|
+
this.errorMessage = parsedErrorMessage;
|
|
540
|
+
this.errorReason = parsedErrorReason;
|
|
497
541
|
} catch (error: any) {
|
|
498
542
|
parseErrorMessage = error?.message || String(error);
|
|
499
543
|
this.errorMessage = parseErrorMessage;
|
|
@@ -503,22 +547,39 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
503
547
|
this.errorMessage = undefined;
|
|
504
548
|
this.errorReason = undefined;
|
|
505
549
|
}
|
|
550
|
+
const adapterProviderSessionId = normalizeProviderSessionId(
|
|
551
|
+
this.provider,
|
|
552
|
+
typeof adapterStatus?.providerSessionId === 'string' ? adapterStatus.providerSessionId : '',
|
|
553
|
+
);
|
|
506
554
|
const autoApproveActive = this.maybeAutoApproveStatus(adapterStatus, Date.now());
|
|
507
|
-
const visibleStatus = parseErrorMessage
|
|
555
|
+
const visibleStatus = parseErrorMessage || parsedStatus?.status === 'error'
|
|
508
556
|
? 'error'
|
|
509
557
|
: (autoApproveActive ? 'generating' : adapterStatus.status);
|
|
558
|
+
const runtime = this.adapter.getRuntimeMetadata();
|
|
559
|
+
this.maybeAppendRuntimeRecoveryMessage(runtime);
|
|
560
|
+
let parsedMessages = Array.isArray(parsedStatus?.messages)
|
|
561
|
+
? parsedStatus.messages
|
|
562
|
+
: [];
|
|
510
563
|
const parsedProviderSessionId = normalizeProviderSessionId(
|
|
511
564
|
this.provider,
|
|
512
565
|
typeof parsedStatus?.providerSessionId === 'string' ? parsedStatus.providerSessionId : '',
|
|
513
566
|
);
|
|
514
|
-
|
|
567
|
+
const suppressFreshLaunchStartupReplay = this.shouldSuppressFreshLaunchStartupReplay(
|
|
568
|
+
parsedMessages,
|
|
569
|
+
parsedStatus,
|
|
570
|
+
adapterStatus,
|
|
571
|
+
parsedProviderSessionId,
|
|
572
|
+
);
|
|
573
|
+
if (adapterProviderSessionId && !suppressFreshLaunchStartupReplay) {
|
|
574
|
+
this.promoteProviderSessionId(adapterProviderSessionId);
|
|
575
|
+
}
|
|
576
|
+
if (parsedProviderSessionId && !suppressFreshLaunchStartupReplay) {
|
|
515
577
|
this.promoteProviderSessionId(parsedProviderSessionId);
|
|
516
578
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
: [];
|
|
579
|
+
if (suppressFreshLaunchStartupReplay) {
|
|
580
|
+
parsedMessages = [];
|
|
581
|
+
}
|
|
582
|
+
const activeChatId = this.providerSessionId || runtime?.runtimeId || this.instanceId;
|
|
522
583
|
const historyMessageCount = Number.isFinite(parsedStatus?.historyMessageCount)
|
|
523
584
|
? Math.max(0, Number(parsedStatus.historyMessageCount))
|
|
524
585
|
: null;
|
|
@@ -528,7 +589,18 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
528
589
|
: [];
|
|
529
590
|
}
|
|
530
591
|
const mergedMessages = this.mergeConversationMessages(parsedMessages);
|
|
531
|
-
const canonicalBackedHistory = this.
|
|
592
|
+
const canonicalBackedHistory = this.shouldHydrateExistingProviderHistory()
|
|
593
|
+
? this.syncCanonicalSavedHistoryIfNeeded()
|
|
594
|
+
: false;
|
|
595
|
+
const statusMessages = canonicalBackedHistory && this.lastPersistedHistoryMessages.length > 0
|
|
596
|
+
? this.lastPersistedHistoryMessages.map((message) => ({
|
|
597
|
+
role: message.role,
|
|
598
|
+
content: message.content,
|
|
599
|
+
kind: message.kind,
|
|
600
|
+
senderName: message.senderName,
|
|
601
|
+
receivedAt: message.receivedAt,
|
|
602
|
+
}))
|
|
603
|
+
: mergedMessages;
|
|
532
604
|
|
|
533
605
|
const dirName = this.workingDir.split('/').filter(Boolean).pop() || 'session';
|
|
534
606
|
const parsedChatStatus = typeof parsedStatus?.status === 'string' && parsedStatus.status.trim()
|
|
@@ -572,7 +644,12 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
572
644
|
}
|
|
573
645
|
}
|
|
574
646
|
|
|
575
|
-
this.applyProviderResponse(
|
|
647
|
+
this.applyProviderResponse(
|
|
648
|
+
suppressFreshLaunchStartupReplay && parsedStatus && typeof parsedStatus === 'object'
|
|
649
|
+
? { ...parsedStatus, providerSessionId: undefined }
|
|
650
|
+
: parsedStatus,
|
|
651
|
+
{ phase: 'immediate' },
|
|
652
|
+
);
|
|
576
653
|
const surface = resolveProviderStateSurface({
|
|
577
654
|
summaryMetadata: this.summaryMetadata as any,
|
|
578
655
|
controlValues: this.controlValues,
|
|
@@ -592,10 +669,10 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
592
669
|
status: visibleStatus,
|
|
593
670
|
mode: this.presentationMode,
|
|
594
671
|
activeChat: {
|
|
595
|
-
id:
|
|
672
|
+
id: activeChatId,
|
|
596
673
|
title: parsedStatus?.title || dirName,
|
|
597
674
|
status: activeChatStatus,
|
|
598
|
-
messages:
|
|
675
|
+
messages: statusMessages,
|
|
599
676
|
activeModal: autoApproveActive ? null : (parsedStatus?.activeModal ?? adapterStatus.activeModal),
|
|
600
677
|
inputContent: '',
|
|
601
678
|
},
|
|
@@ -651,13 +728,17 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
651
728
|
};
|
|
652
729
|
}
|
|
653
730
|
|
|
654
|
-
getSessionModalState(): SessionModalState {
|
|
731
|
+
getSessionModalState(sessionId?: string): SessionModalState {
|
|
655
732
|
const adapterStatus = this.adapter.getStatus({ allowParse: false });
|
|
656
733
|
const autoApproveActive = adapterStatus.status === 'waiting_approval' && this.shouldAutoApprove();
|
|
657
734
|
const visibleStatus = autoApproveActive ? 'generating' : adapterStatus.status;
|
|
658
735
|
const dirName = this.workingDir.split('/').filter(Boolean).pop() || 'session';
|
|
659
736
|
return {
|
|
660
|
-
|
|
737
|
+
// Honor the caller-supplied sessionId — InstanceMgr rejects the
|
|
738
|
+
// projection when projected.id !== requested sessionId, and
|
|
739
|
+
// this.instanceId is the manager's internal key, not the public
|
|
740
|
+
// sessionId the dashboard subscribes by.
|
|
741
|
+
id: sessionId ?? this.instanceId,
|
|
661
742
|
status: visibleStatus,
|
|
662
743
|
title: dirName,
|
|
663
744
|
activeModal: autoApproveActive ? null : adapterStatus.activeModal,
|
|
@@ -695,6 +776,34 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
695
776
|
}
|
|
696
777
|
}
|
|
697
778
|
|
|
779
|
+
recordAcknowledgedUserInput(input: InputEnvelope | string): void {
|
|
780
|
+
const content = typeof input === 'string'
|
|
781
|
+
? input.trim()
|
|
782
|
+
: buildCliStructuredInputPrompt(input).trim();
|
|
783
|
+
if (!content) return;
|
|
784
|
+
|
|
785
|
+
const receivedAt = Date.now();
|
|
786
|
+
const dedupKey = `user_input_ack:${crypto
|
|
787
|
+
.createHash('sha256')
|
|
788
|
+
.update(`${this.instanceId}:${content}:${receivedAt}`)
|
|
789
|
+
.digest('hex')
|
|
790
|
+
.slice(0, 24)}`;
|
|
791
|
+
this.appendRuntimeMessage(buildChatMessage({
|
|
792
|
+
role: 'user',
|
|
793
|
+
senderName: 'User',
|
|
794
|
+
kind: 'standard',
|
|
795
|
+
content,
|
|
796
|
+
receivedAt,
|
|
797
|
+
timestamp: receivedAt,
|
|
798
|
+
source: 'runtime_input_ack',
|
|
799
|
+
meta: {
|
|
800
|
+
runtimeInputAck: true,
|
|
801
|
+
provider: this.type,
|
|
802
|
+
workspace: this.workingDir,
|
|
803
|
+
},
|
|
804
|
+
} as ChatMessage), dedupKey);
|
|
805
|
+
}
|
|
806
|
+
|
|
698
807
|
dispose(): void {
|
|
699
808
|
this.adapter.shutdown();
|
|
700
809
|
this.monitor.reset();
|
|
@@ -740,7 +849,60 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
740
849
|
const lastVisible = visibleMessages[visibleMessages.length - 1] as ChatMessage | undefined;
|
|
741
850
|
const role = typeof lastVisible?.role === 'string' ? lastVisible.role.trim().toLowerCase() : '';
|
|
742
851
|
const content = lastVisible ? flattenContent(lastVisible.content).trim() : '';
|
|
743
|
-
|
|
852
|
+
if (role !== 'assistant' || !content) return false;
|
|
853
|
+
// Guard: if the last assistant message looks like an active approval/input prompt,
|
|
854
|
+
// it is not a real completion — the session is still awaiting user input.
|
|
855
|
+
if (looksLikeActiveApprovalPromptText(content)) return false;
|
|
856
|
+
return true;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
private buildCompletedFinalizationDiagnostic(args: {
|
|
860
|
+
blockReason: string;
|
|
861
|
+
latestStatus?: any;
|
|
862
|
+
latestVisibleStatus: string;
|
|
863
|
+
waitedMs: number;
|
|
864
|
+
pending: CompletedDebouncePending;
|
|
865
|
+
emittedAfterFinalizationTimeout: boolean;
|
|
866
|
+
}): Record<string, unknown> {
|
|
867
|
+
let parsed: any = null;
|
|
868
|
+
let parseError: string | undefined;
|
|
869
|
+
try {
|
|
870
|
+
parsed = this.adapter.getScriptParsedStatus();
|
|
871
|
+
} catch (error: any) {
|
|
872
|
+
parseError = error?.message || String(error);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
const visibleMessages = (Array.isArray(parsed?.messages) ? parsed.messages : [])
|
|
876
|
+
.filter((message: any) => isUserFacingChatMessage(message as ChatMessage));
|
|
877
|
+
const lastVisible = visibleMessages[visibleMessages.length - 1] as ChatMessage | undefined;
|
|
878
|
+
const lastVisibleRole = typeof lastVisible?.role === 'string' ? lastVisible.role.trim().toLowerCase() : null;
|
|
879
|
+
const lastVisibleKind = typeof (lastVisible as any)?.kind === 'string' ? (lastVisible as any).kind : null;
|
|
880
|
+
const lastVisibleContentLength = lastVisible ? flattenContent(lastVisible.content).trim().length : 0;
|
|
881
|
+
|
|
882
|
+
return {
|
|
883
|
+
providerType: this.type,
|
|
884
|
+
sessionId: this.instanceId,
|
|
885
|
+
providerSessionId: this.providerSessionId || null,
|
|
886
|
+
workspace: this.workingDir,
|
|
887
|
+
blockReason: args.blockReason,
|
|
888
|
+
emittedAfterFinalizationTimeout: args.emittedAfterFinalizationTimeout,
|
|
889
|
+
waitedMs: args.waitedMs,
|
|
890
|
+
maxWaitMs: COMPLETED_FINALIZATION_MAX_WAIT_MS,
|
|
891
|
+
adapterStatus: typeof args.latestStatus?.status === 'string' ? args.latestStatus.status : null,
|
|
892
|
+
latestVisibleStatus: args.latestVisibleStatus,
|
|
893
|
+
parsedStatus: typeof parsed?.status === 'string' ? parsed.status : (parseError ? 'parse_error' : 'unknown'),
|
|
894
|
+
parseError: parseError || undefined,
|
|
895
|
+
finalAssistantPresent: this.completionHasFinalAssistantMessage(parsed?.messages),
|
|
896
|
+
visibleMessageCount: visibleMessages.length,
|
|
897
|
+
lastVisibleRole,
|
|
898
|
+
lastVisibleKind,
|
|
899
|
+
lastVisibleContentLength,
|
|
900
|
+
pendingStartedAt: this.generatingStartedAt || null,
|
|
901
|
+
pendingFirstObservedAt: args.pending.firstObservedAt,
|
|
902
|
+
pendingTimestamp: args.pending.timestamp,
|
|
903
|
+
pendingDurationSec: args.pending.duration,
|
|
904
|
+
previousBlockReason: args.pending.loggedBlockReason || null,
|
|
905
|
+
};
|
|
744
906
|
}
|
|
745
907
|
|
|
746
908
|
private hasAdapterPendingResponse(): boolean {
|
|
@@ -765,32 +927,62 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
765
927
|
if (!isCliGeneratingLikeStatus(parsedRawStatus)) return false;
|
|
766
928
|
if (adapterRawStatus !== 'idle') return false;
|
|
767
929
|
if (hasNonEmptyCliModalButtons(parsedStatus?.activeModal ?? parsedStatus?.modal)) return false;
|
|
768
|
-
|
|
930
|
+
if (this.hasAdapterPendingResponse()) return false;
|
|
931
|
+
// Do not suppress when the adapter's raw response buffer is still non-empty.
|
|
932
|
+
// This catches the case where isWaitingForResponse has already flipped to false
|
|
933
|
+
// (so getPartialResponse() returns '') but the provider's native parser still
|
|
934
|
+
// reports generating because it's parsing buffered content. Suppressing the
|
|
935
|
+
// finalization block here would emit a false completion event while the provider
|
|
936
|
+
// session is still actively processing its response stream.
|
|
937
|
+
const adapterAny = this.adapter as any;
|
|
938
|
+
if (typeof adapterAny?.responseBuffer === 'string' && adapterAny.responseBuffer.trim()) return false;
|
|
939
|
+
return true;
|
|
769
940
|
}
|
|
770
941
|
|
|
771
|
-
private
|
|
772
|
-
if (latestVisibleStatus !== 'idle') return `status:${latestVisibleStatus}
|
|
942
|
+
private getCompletedFinalizationBlock(latestVisibleStatus: string): CompletedFinalizationBlock | null {
|
|
943
|
+
if (latestVisibleStatus !== 'idle') return { reason: `status:${latestVisibleStatus}`, terminal: true };
|
|
773
944
|
|
|
774
945
|
const adapterAny = this.adapter as any;
|
|
775
|
-
if (adapterAny?.isWaitingForResponse === true) return 'adapter_waiting_for_response';
|
|
776
|
-
if (adapterAny?.currentTurnScope) return 'adapter_turn_scope_active';
|
|
946
|
+
if (adapterAny?.isWaitingForResponse === true) return { reason: 'adapter_waiting_for_response', terminal: true };
|
|
947
|
+
if (adapterAny?.currentTurnScope) return { reason: 'adapter_turn_scope_active', terminal: true };
|
|
948
|
+
if (this.hasAdapterPendingResponse()) return { reason: 'adapter_pending_response', terminal: true };
|
|
777
949
|
|
|
778
950
|
const partial = typeof this.adapter.getPartialResponse === 'function'
|
|
779
951
|
? this.adapter.getPartialResponse()
|
|
780
952
|
: '';
|
|
781
|
-
if (typeof partial === 'string' && partial.trim()) return 'partial_response_pending';
|
|
953
|
+
if (typeof partial === 'string' && partial.trim()) return { reason: 'partial_response_pending', terminal: true };
|
|
782
954
|
|
|
783
955
|
let parsed: any;
|
|
784
956
|
try {
|
|
785
957
|
parsed = this.adapter.getScriptParsedStatus();
|
|
786
958
|
} catch (error: any) {
|
|
787
|
-
return `parse_error:${error?.message || String(error)}
|
|
959
|
+
return { reason: `parse_error:${error?.message || String(error)}` };
|
|
788
960
|
}
|
|
789
961
|
|
|
790
962
|
const parsedStatus = typeof parsed?.status === 'string' ? parsed.status : 'unknown';
|
|
791
|
-
if (parsedStatus !== 'idle')
|
|
792
|
-
|
|
793
|
-
|
|
963
|
+
if (parsedStatus !== 'idle') {
|
|
964
|
+
const adapterStatus = this.adapter.getStatus({ allowParse: false });
|
|
965
|
+
if (this.shouldSuppressStaleParsedBusyStatus(parsed, adapterStatus)) return null;
|
|
966
|
+
return { reason: `parsed_status:${parsedStatus}`, terminal: isCliGeneratingLikeStatus(parsedStatus) };
|
|
967
|
+
}
|
|
968
|
+
if (parsed?.activeModal || parsed?.modal) return { reason: 'parsed_modal_active', terminal: true };
|
|
969
|
+
if (!this.completionHasFinalAssistantMessage(parsed?.messages)) return { reason: 'missing_final_assistant' };
|
|
970
|
+
|
|
971
|
+
// Guard: if the screen still shows an approval/choice prompt as the last visible text,
|
|
972
|
+
// the turn is not complete even if the parsed status says idle and there is an assistant
|
|
973
|
+
// message. This catches the case where waiting_approval→idle transitions occur before
|
|
974
|
+
// the modal has been resolved (e.g. the PTY rendered the prompt but no button press fired).
|
|
975
|
+
try {
|
|
976
|
+
const screenText = typeof (this.adapter as any).getScreenText === 'function'
|
|
977
|
+
? String((this.adapter as any).getScreenText() || '')
|
|
978
|
+
: '';
|
|
979
|
+
if (screenText) {
|
|
980
|
+
const tailLines = screenText.split(/\r?\n/).slice(-16).join('\n');
|
|
981
|
+
if (looksLikeActiveApprovalPromptText(tailLines)) {
|
|
982
|
+
return { reason: 'screen_shows_approval_prompt', terminal: false };
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
} catch { /* defensive: screen text read is best-effort */ }
|
|
794
986
|
|
|
795
987
|
return null;
|
|
796
988
|
}
|
|
@@ -817,10 +1009,11 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
817
1009
|
return;
|
|
818
1010
|
}
|
|
819
1011
|
|
|
820
|
-
const
|
|
821
|
-
if (
|
|
1012
|
+
const block = this.getCompletedFinalizationBlock(latestVisibleStatus);
|
|
1013
|
+
if (block) {
|
|
1014
|
+
const blockReason = block.reason;
|
|
822
1015
|
const waitedMs = Date.now() - pending.firstObservedAt;
|
|
823
|
-
if (waitedMs < COMPLETED_FINALIZATION_MAX_WAIT_MS) {
|
|
1016
|
+
if (block.terminal || waitedMs < COMPLETED_FINALIZATION_MAX_WAIT_MS) {
|
|
824
1017
|
if (pending.loggedBlockReason !== blockReason) {
|
|
825
1018
|
LOG.info('CLI', `[${this.type}] waiting to emit completed until transcript finalizes (${blockReason})`);
|
|
826
1019
|
pending.loggedBlockReason = blockReason;
|
|
@@ -828,7 +1021,25 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
828
1021
|
this.scheduleCompletedDebounceFlush(COMPLETED_FINALIZATION_RETRY_MS);
|
|
829
1022
|
return;
|
|
830
1023
|
}
|
|
831
|
-
|
|
1024
|
+
const completionDiagnostic = this.buildCompletedFinalizationDiagnostic({
|
|
1025
|
+
blockReason,
|
|
1026
|
+
latestStatus,
|
|
1027
|
+
latestVisibleStatus,
|
|
1028
|
+
waitedMs,
|
|
1029
|
+
pending,
|
|
1030
|
+
emittedAfterFinalizationTimeout: true,
|
|
1031
|
+
});
|
|
1032
|
+
LOG.warn('CLI', `[${this.type}] emitting completed event after ${waitedMs}ms without finalized assistant turn (${blockReason})`);
|
|
1033
|
+
this.pushEvent({
|
|
1034
|
+
event: 'agent:generating_completed',
|
|
1035
|
+
chatTitle: pending.chatTitle,
|
|
1036
|
+
duration: pending.duration,
|
|
1037
|
+
timestamp: pending.timestamp,
|
|
1038
|
+
finalSummary: blockReason.startsWith('parsed_status:')
|
|
1039
|
+
? ''
|
|
1040
|
+
: extractFinalSummaryFromMessages(this.adapter?.getScriptParsedStatus()?.messages),
|
|
1041
|
+
completionDiagnostic,
|
|
1042
|
+
});
|
|
832
1043
|
this.completedDebouncePending = null;
|
|
833
1044
|
this.completedDebounceTimer = null;
|
|
834
1045
|
this.generatingStartedAt = 0;
|
|
@@ -853,15 +1064,46 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
853
1064
|
// Guard re-entry: onStatusChange/getState can observe the same modal multiple
|
|
854
1065
|
// times while the PTY absorbs the approval key. Without this flag, repeated
|
|
855
1066
|
// snapshots would write stray keys into the input once the modal dismisses.
|
|
856
|
-
|
|
1067
|
+
// However, Claude Code can present a second approval immediately after the
|
|
1068
|
+
// first. Resolve a changed modal signature even while the previous write is
|
|
1069
|
+
// still inside the short busy window.
|
|
1070
|
+
if (!autoApproveActive) {
|
|
1071
|
+
this.lastAutoApprovalSignature = '';
|
|
1072
|
+
return autoApproveActive;
|
|
1073
|
+
}
|
|
1074
|
+
const modal = adapterStatus.activeModal;
|
|
1075
|
+
// (fix) Do not auto-approve when no concrete modal/buttons are present.
|
|
1076
|
+
// Claude TUI flaps between paints; without this guard adapterStatus
|
|
1077
|
+
// could report status=waiting_approval with activeModal=null (or with
|
|
1078
|
+
// an empty buttons array briefly) and we'd still call
|
|
1079
|
+
// resolveModal(-1) — which used to type "1" into the prompt
|
|
1080
|
+
// repeatedly. Skip until a real modal is captured.
|
|
1081
|
+
const buttons = Array.isArray(modal?.buttons)
|
|
1082
|
+
? modal.buttons.map((b: any) => String(b || '').trim()).filter(Boolean)
|
|
1083
|
+
: [];
|
|
1084
|
+
if (!modal || buttons.length === 0) {
|
|
1085
|
+
return autoApproveActive;
|
|
1086
|
+
}
|
|
1087
|
+
const { index: buttonIndex, label: buttonLabel } = pickApprovalButton(buttons, this.provider);
|
|
1088
|
+
if (buttonIndex < 0) {
|
|
1089
|
+
// No positive button matched — don't pick a random index, just
|
|
1090
|
+
// surface the modal so the user can decide.
|
|
1091
|
+
return autoApproveActive;
|
|
1092
|
+
}
|
|
1093
|
+
const signature = [
|
|
1094
|
+
typeof modal?.message === 'string' ? modal.message.trim() : '',
|
|
1095
|
+
buttons.join('|'),
|
|
1096
|
+
buttonIndex,
|
|
1097
|
+
].join('::');
|
|
1098
|
+
if (!this.autoApproveBusy || signature !== this.lastAutoApprovalSignature) {
|
|
857
1099
|
this.autoApproveBusy = true;
|
|
1100
|
+
this.lastAutoApprovalSignature = signature;
|
|
858
1101
|
if (this.autoApproveBusyTimer) clearTimeout(this.autoApproveBusyTimer);
|
|
859
1102
|
this.autoApproveBusyTimer = setTimeout(() => {
|
|
860
1103
|
this.autoApproveBusy = false;
|
|
861
1104
|
this.autoApproveBusyTimer = null;
|
|
1105
|
+
this.lastAutoApprovalSignature = '';
|
|
862
1106
|
}, 2000);
|
|
863
|
-
const modal = adapterStatus.activeModal;
|
|
864
|
-
const { index: buttonIndex, label: buttonLabel } = pickApprovalButton(modal?.buttons, this.provider);
|
|
865
1107
|
this.recordAutoApproval(modal?.message, buttonLabel, now);
|
|
866
1108
|
setTimeout(() => {
|
|
867
1109
|
this.adapter.resolveModal(buttonIndex);
|
|
@@ -876,6 +1118,13 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
876
1118
|
// during long-running CLI sessions. Keep this path on adapter-owned light
|
|
877
1119
|
// state only; rich provider parsing is reserved for getState/read_chat.
|
|
878
1120
|
const adapterStatus = this.adapter.getStatus({ allowParse: false });
|
|
1121
|
+
const adapterProviderSessionId = normalizeProviderSessionId(
|
|
1122
|
+
this.provider,
|
|
1123
|
+
typeof adapterStatus?.providerSessionId === 'string' ? adapterStatus.providerSessionId : '',
|
|
1124
|
+
);
|
|
1125
|
+
if (adapterProviderSessionId) {
|
|
1126
|
+
this.promoteProviderSessionId(adapterProviderSessionId);
|
|
1127
|
+
}
|
|
879
1128
|
const parsedStatus = null;
|
|
880
1129
|
const rawStatus = adapterStatus.status;
|
|
881
1130
|
const autoApproveActive = this.maybeAutoApproveStatus(adapterStatus, now);
|
|
@@ -943,12 +1192,30 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
943
1192
|
}
|
|
944
1193
|
} else if (newStatus === 'idle' && (this.lastStatus === 'generating' || this.lastStatus === 'waiting_approval')) {
|
|
945
1194
|
const duration = this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1000) : 0;
|
|
946
|
-
// If debounce still pending (generating lasted < 1s), cancel both events
|
|
1195
|
+
// If debounce still pending (generating lasted < 1s), cancel both UI events.
|
|
1196
|
+
// Still emit agent:generating_completed so mesh orchestration can record
|
|
1197
|
+
// task_completed for direct dispatches that complete faster than the debounce.
|
|
947
1198
|
if (this.generatingDebouncePending) {
|
|
948
|
-
|
|
1199
|
+
const shortDurationMs = this.generatingStartedAt ? now - this.generatingStartedAt : 0;
|
|
1200
|
+
LOG.info('CLI', `[${this.type}] suppressed short generating (${shortDurationMs}ms)`);
|
|
949
1201
|
if (this.generatingDebounceTimer) { clearTimeout(this.generatingDebounceTimer); this.generatingDebounceTimer = null; }
|
|
950
1202
|
this.generatingDebouncePending = null;
|
|
951
1203
|
this.generatingStartedAt = 0;
|
|
1204
|
+
// Emit completion for mesh task association even though the UI generating
|
|
1205
|
+
// started/completed pair is suppressed (too short for visible UI update).
|
|
1206
|
+
let shortFinalSummary: string | undefined;
|
|
1207
|
+
try { shortFinalSummary = extractFinalSummaryFromMessages(this.adapter?.getScriptParsedStatus()?.messages); } catch { /* best-effort */ }
|
|
1208
|
+
this.pushEvent({
|
|
1209
|
+
event: 'agent:generating_completed',
|
|
1210
|
+
chatTitle,
|
|
1211
|
+
duration: 0,
|
|
1212
|
+
timestamp: now,
|
|
1213
|
+
finalSummary: shortFinalSummary,
|
|
1214
|
+
completionDiagnostic: {
|
|
1215
|
+
reason: 'short_generating_suppressed',
|
|
1216
|
+
shortDurationMs,
|
|
1217
|
+
},
|
|
1218
|
+
});
|
|
952
1219
|
} else {
|
|
953
1220
|
// Debounce completed, then require the rich transcript path that read_chat
|
|
954
1221
|
// uses to show an idle turn whose last user-facing message is assistant.
|
|
@@ -957,6 +1224,23 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
957
1224
|
}
|
|
958
1225
|
} else if (newStatus === 'idle' && this.lastStatus === 'starting') {
|
|
959
1226
|
this.pushEvent({ event: 'agent:ready', chatTitle, timestamp: now });
|
|
1227
|
+
} else if (newStatus === 'error') {
|
|
1228
|
+
if (this.generatingDebounceTimer) { clearTimeout(this.generatingDebounceTimer); this.generatingDebounceTimer = null; }
|
|
1229
|
+
this.generatingDebouncePending = null;
|
|
1230
|
+
if (this.completedDebounceTimer) { clearTimeout(this.completedDebounceTimer); this.completedDebounceTimer = null; }
|
|
1231
|
+
this.completedDebouncePending = null;
|
|
1232
|
+
this.errorMessage = adapterStatus.errorMessage || this.errorMessage;
|
|
1233
|
+
this.errorReason = (adapterStatus.errorReason as ProviderErrorReason) || this.errorReason;
|
|
1234
|
+
this.pushEvent({
|
|
1235
|
+
event: 'agent:stopped',
|
|
1236
|
+
chatTitle,
|
|
1237
|
+
timestamp: now,
|
|
1238
|
+
finalSummary: adapterStatus.errorMessage || adapterStatus.errorReason || 'Provider reported an error',
|
|
1239
|
+
completionDiagnostic: {
|
|
1240
|
+
reason: adapterStatus.errorReason || 'provider_error',
|
|
1241
|
+
errorMessage: adapterStatus.errorMessage || undefined,
|
|
1242
|
+
},
|
|
1243
|
+
});
|
|
960
1244
|
} else if (newStatus === 'stopped') {
|
|
961
1245
|
// Cancel any pending debounce
|
|
962
1246
|
if (this.generatingDebounceTimer) { clearTimeout(this.generatingDebounceTimer); this.generatingDebounceTimer = null; }
|
|
@@ -977,7 +1261,31 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
977
1261
|
// Monitor check (cooldown based notification, IDE/CLI common)
|
|
978
1262
|
const agentKey = `${this.type}:cli`;
|
|
979
1263
|
const monitorEvents = this.monitor.check(agentKey, newStatus, now, progressFingerprint);
|
|
1264
|
+
const monitorParsedStatus: any = parsedStatus;
|
|
980
1265
|
for (const me of monitorEvents) {
|
|
1266
|
+
if (
|
|
1267
|
+
me.type === 'monitor:long_generating'
|
|
1268
|
+
&& this.completionHasFinalAssistantMessage(monitorParsedStatus?.messages)
|
|
1269
|
+
&& !this.hasAdapterPendingResponse()
|
|
1270
|
+
&& !hasNonEmptyCliModalButtons(monitorParsedStatus?.activeModal ?? monitorParsedStatus?.modal)
|
|
1271
|
+
) {
|
|
1272
|
+
this.pushEvent({
|
|
1273
|
+
event: 'agent:generating_completed',
|
|
1274
|
+
chatTitle,
|
|
1275
|
+
duration: this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1000) : undefined,
|
|
1276
|
+
timestamp: me.timestamp,
|
|
1277
|
+
finalSummary: extractFinalSummaryFromMessages(monitorParsedStatus?.messages),
|
|
1278
|
+
completionDiagnostic: {
|
|
1279
|
+
providerType: this.type,
|
|
1280
|
+
sessionId: this.instanceId,
|
|
1281
|
+
providerSessionId: this.providerSessionId || null,
|
|
1282
|
+
reconciliationReason: 'long_generating_monitor_final_summary',
|
|
1283
|
+
finalAssistantPresent: true,
|
|
1284
|
+
},
|
|
1285
|
+
});
|
|
1286
|
+
this.generatingStartedAt = 0;
|
|
1287
|
+
continue;
|
|
1288
|
+
}
|
|
981
1289
|
this.pushEvent({ event: me.type, agentKey: me.agentKey, message: me.message, elapsedSec: me.elapsedSec, timestamp: me.timestamp });
|
|
982
1290
|
}
|
|
983
1291
|
}
|
|
@@ -1258,12 +1566,28 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1258
1566
|
index,
|
|
1259
1567
|
source: 'parsed',
|
|
1260
1568
|
}));
|
|
1569
|
+
const getRole = (message: ChatMessage): string => typeof message.role === 'string'
|
|
1570
|
+
? message.role.trim().toLowerCase()
|
|
1571
|
+
: '';
|
|
1261
1572
|
const runtimeEntries: MergeEntry[] = this.runtimeMessages.map((entry, index) => ({
|
|
1262
1573
|
message: entry.message,
|
|
1263
1574
|
index: parsedMessages.length + index,
|
|
1264
|
-
source: 'runtime',
|
|
1575
|
+
source: 'runtime' as const,
|
|
1265
1576
|
runtimeKey: entry.key,
|
|
1266
|
-
}))
|
|
1577
|
+
})).filter((entry) => {
|
|
1578
|
+
const meta = entry.message.meta && typeof entry.message.meta === 'object' && !Array.isArray(entry.message.meta)
|
|
1579
|
+
? entry.message.meta as Record<string, unknown>
|
|
1580
|
+
: {};
|
|
1581
|
+
if (meta.runtimeInputAck !== true) return true;
|
|
1582
|
+
const runtimeText = flattenContent(entry.message.content).replace(/\s+/g, ' ').trim();
|
|
1583
|
+
if (!runtimeText) return false;
|
|
1584
|
+
return !parsedEntries.some((parsedEntry) => {
|
|
1585
|
+
const parsedRole = getRole(parsedEntry.message);
|
|
1586
|
+
if (parsedRole !== 'user' && parsedRole !== 'human') return false;
|
|
1587
|
+
const parsedText = flattenContent(parsedEntry.message.content).replace(/\s+/g, ' ').trim();
|
|
1588
|
+
return parsedText === runtimeText;
|
|
1589
|
+
});
|
|
1590
|
+
});
|
|
1267
1591
|
const getTime = (message: ChatMessage): number => {
|
|
1268
1592
|
const value = typeof message.receivedAt === 'number'
|
|
1269
1593
|
? message.receivedAt
|
|
@@ -1273,9 +1597,6 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1273
1597
|
return Number.isFinite(value) && value > 0 ? value : 0;
|
|
1274
1598
|
};
|
|
1275
1599
|
|
|
1276
|
-
const getRole = (message: ChatMessage): string => typeof message.role === 'string'
|
|
1277
|
-
? message.role.trim().toLowerCase()
|
|
1278
|
-
: '';
|
|
1279
1600
|
const isRuntimeOverlay = (entry: MergeEntry): boolean => {
|
|
1280
1601
|
if (entry.source !== 'runtime') return false;
|
|
1281
1602
|
const key = typeof entry.runtimeKey === 'string' ? entry.runtimeKey.trim().toLowerCase() : '';
|
|
@@ -1341,7 +1662,9 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1341
1662
|
this.providerSessionId = nextSessionId;
|
|
1342
1663
|
this.historyWriter.promoteHistorySession(this.type, previousHistorySessionId, nextSessionId);
|
|
1343
1664
|
this.historyWriter.writeSessionStart(this.type, nextSessionId, this.workingDir, this.instanceId);
|
|
1344
|
-
this.
|
|
1665
|
+
if (this.shouldHydrateExistingProviderHistory()) {
|
|
1666
|
+
this.restorePersistedHistoryFromCurrentSession();
|
|
1667
|
+
}
|
|
1345
1668
|
this.adapter.updateRuntimeMeta({ providerSessionId: nextSessionId });
|
|
1346
1669
|
this.onProviderSessionResolved?.({
|
|
1347
1670
|
instanceId: this.instanceId,
|
|
@@ -1354,9 +1677,27 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1354
1677
|
LOG.info('CLI', `[${this.type}] discovered provider session id: ${nextSessionId}`);
|
|
1355
1678
|
}
|
|
1356
1679
|
|
|
1680
|
+
private shouldHydrateExistingProviderHistory(): boolean {
|
|
1681
|
+
return this.launchMode === 'resume' || this.launchMode === 'manual';
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
private shouldSuppressFreshLaunchStartupReplay(parsedMessages: unknown[], parsedStatus: any, adapterStatus: any, parsedProviderSessionId = ''): boolean {
|
|
1685
|
+
if (this.launchMode !== 'new') return false;
|
|
1686
|
+
if (this.providerSessionId) return false;
|
|
1687
|
+
if (!Array.isArray(parsedMessages) || parsedMessages.length === 0) return false;
|
|
1688
|
+
if (!isIdleStatus(adapterStatus?.status) || !isIdleStatus(parsedStatus?.status)) return false;
|
|
1689
|
+
if (parsedProviderSessionId) return true;
|
|
1690
|
+
|
|
1691
|
+
const newestMessageAt = parsedMessages.reduce<number>((newest, message) => Math.max(newest, getMessageTime(message)), 0);
|
|
1692
|
+
|
|
1693
|
+
// Untimestamped idle parser output during a fresh launch is usually the
|
|
1694
|
+
// provider's last workspace transcript before a new turn exists.
|
|
1695
|
+
return newestMessageAt === 0;
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1357
1698
|
private syncCanonicalSavedHistoryIfNeeded(): boolean {
|
|
1358
1699
|
if (!this.providerSessionId) return false;
|
|
1359
|
-
const canonicalHistory = this.provider.
|
|
1700
|
+
const canonicalHistory = this.provider.nativeHistory;
|
|
1360
1701
|
if (!canonicalHistory) return false;
|
|
1361
1702
|
|
|
1362
1703
|
if (isNativeSourceCanonicalHistory(canonicalHistory)) {
|
|
@@ -1418,9 +1759,9 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1418
1759
|
private restorePersistedHistoryFromCurrentSession(): void {
|
|
1419
1760
|
if (!this.providerSessionId) return;
|
|
1420
1761
|
this.syncCanonicalSavedHistoryIfNeeded();
|
|
1421
|
-
const restoredHistory = isNativeSourceCanonicalHistory(this.provider.
|
|
1762
|
+
const restoredHistory = isNativeSourceCanonicalHistory(this.provider.nativeHistory)
|
|
1422
1763
|
? readProviderChatHistory(this.type, {
|
|
1423
|
-
canonicalHistory: this.provider.
|
|
1764
|
+
canonicalHistory: this.provider.nativeHistory,
|
|
1424
1765
|
historySessionId: this.providerSessionId,
|
|
1425
1766
|
workspace: this.workingDir,
|
|
1426
1767
|
offset: 0,
|