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