@aragon-agent/cli 0.6.13 → 0.6.15
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/CHANGELOG.md +41 -0
- package/README.md +250 -222
- package/dist/agent/controller.d.ts +36 -2
- package/dist/agent/controller.js +244 -57
- package/dist/agent/model-profile-settings.js +11 -2
- package/dist/agent/plan-prompt.d.ts +1 -1
- package/dist/agent/plan-prompt.js +4 -3
- package/dist/agent/project-guidance-prompt.d.ts +4 -0
- package/dist/agent/project-guidance-prompt.js +104 -0
- package/dist/agent/reducer.d.ts +2 -0
- package/dist/agent/reducer.js +5 -0
- package/dist/agent/system-prompt.js +4 -0
- package/dist/boot/guard.d.ts +3 -3
- package/dist/boot/guard.js +3 -3
- package/dist/cli.js +33 -15
- package/dist/commands/builtins.js +2 -2
- package/dist/compaction/child.d.ts +9 -1
- package/dist/compaction/child.js +43 -13
- package/dist/compaction/command.js +20 -10
- package/dist/compaction/compactor.d.ts +33 -204
- package/dist/compaction/compactor.js +311 -723
- package/dist/compaction/context-command.js +5 -3
- package/dist/compaction/digest.d.ts +15 -51
- package/dist/compaction/digest.js +113 -148
- package/dist/compaction/limits.d.ts +19 -142
- package/dist/compaction/limits.js +20 -147
- package/dist/compaction/memory-identity.d.ts +16 -0
- package/dist/compaction/memory-identity.js +63 -0
- package/dist/compaction/memory-input.d.ts +25 -0
- package/dist/compaction/memory-input.js +106 -0
- package/dist/compaction/memory-render.d.ts +4 -0
- package/dist/compaction/memory-render.js +47 -0
- package/dist/compaction/memory-validation.d.ts +21 -0
- package/dist/compaction/memory-validation.js +268 -0
- package/dist/compaction/memory.d.ts +79 -0
- package/dist/compaction/memory.js +118 -0
- package/dist/compaction/meter.d.ts +14 -10
- package/dist/compaction/meter.js +55 -19
- package/dist/compaction/operation.d.ts +18 -0
- package/dist/compaction/operation.js +68 -0
- package/dist/compaction/pressure.d.ts +6 -20
- package/dist/compaction/pressure.js +14 -23
- package/dist/compaction/summarize-call.d.ts +3 -16
- package/dist/compaction/summarize-call.js +20 -20
- package/dist/compaction/summary-prompt.d.ts +29 -66
- package/dist/compaction/summary-prompt.js +132 -178
- package/dist/compaction/types.d.ts +23 -3
- package/dist/compaction/wiring.d.ts +10 -25
- package/dist/compaction/wiring.js +60 -67
- package/dist/config/load.d.ts +3 -1
- package/dist/config/load.js +8 -4
- package/dist/config/schema.d.ts +3 -3
- package/dist/config/schema.js +1 -1
- package/dist/exec/events.d.ts +8 -0
- package/dist/exec/index.js +13 -6
- package/dist/exec/runner.d.ts +8 -0
- package/dist/exec/runner.js +26 -2
- package/dist/session/persist.d.ts +3 -0
- package/dist/session/persist.js +9 -0
- package/dist/session/store.js +2 -0
- package/dist/session/validate-session.js +34 -0
- package/dist/team/subagent.js +8 -0
- package/dist/ui/App.js +221 -126
- package/dist/ui/BottomStatusRow.d.ts +5 -10
- package/dist/ui/BottomStatusRow.js +7 -11
- package/dist/ui/Composer.d.ts +4 -2
- package/dist/ui/Composer.js +2 -2
- package/dist/ui/Header.d.ts +6 -2
- package/dist/ui/Header.js +7 -6
- package/dist/ui/Logo.d.ts +3 -5
- package/dist/ui/Logo.js +4 -8
- package/dist/ui/PromptInput.d.ts +3 -1
- package/dist/ui/PromptInput.js +35 -42
- package/dist/ui/StatusBar.d.ts +6 -0
- package/dist/ui/StatusBar.js +8 -7
- package/dist/ui/Transcript.js +1 -1
- package/dist/ui/capabilities.d.ts +2 -0
- package/dist/ui/capabilities.js +6 -0
- package/dist/ui/color-runtime.d.ts +5 -0
- package/dist/ui/color-runtime.js +29 -0
- package/dist/ui/entries/CompactionCard.d.ts +3 -1
- package/dist/ui/entries/CompactionCard.js +7 -5
- package/dist/ui/frame-differ.js +27 -0
- package/dist/ui/frame-observer.js +18 -5
- package/dist/ui/glyphs.js +10 -8
- package/dist/ui/ink-runtime.d.ts +3 -0
- package/dist/ui/ink-runtime.js +30 -0
- package/dist/ui/interaction-copy.d.ts +73 -73
- package/dist/ui/interaction-copy.js +30 -38
- package/dist/ui/interaction-hints.d.ts +8 -1
- package/dist/ui/interaction-hints.js +56 -69
- package/dist/ui/layout/AppShell.d.ts +6 -2
- package/dist/ui/layout/AppShell.js +2 -2
- package/dist/ui/layout/OverlayFrame.d.ts +2 -0
- package/dist/ui/layout/OverlayFrame.js +23 -14
- package/dist/ui/layout/ScrollViewport.d.ts +3 -1
- package/dist/ui/layout/ScrollViewport.js +10 -2
- package/dist/ui/layout/budget.d.ts +25 -26
- package/dist/ui/layout/budget.js +29 -22
- package/dist/ui/layout/document-layout.d.ts +2 -2
- package/dist/ui/layout/document-layout.js +2 -3
- package/dist/ui/layout/queue-layout.js +4 -4
- package/dist/ui/layout/status-detail-layout.d.ts +9 -0
- package/dist/ui/layout/status-detail-layout.js +56 -0
- package/dist/ui/layout/status-layout.d.ts +19 -2
- package/dist/ui/layout/status-layout.js +95 -83
- package/dist/ui/layout/wrap-rows.d.ts +2 -0
- package/dist/ui/layout/wrap-rows.js +21 -7
- package/dist/ui/overlays/ConfirmDialog.d.ts +2 -1
- package/dist/ui/overlays/ConfirmDialog.js +22 -3
- package/dist/ui/overlays/HelpOverlay.js +9 -2
- package/dist/ui/overlays/ModelPicker.d.ts +5 -8
- package/dist/ui/overlays/ModelPicker.js +44 -10
- package/dist/ui/overlays/ModelProfileEditor.js +2 -0
- package/dist/ui/overlays/ModelProfilePicker.d.ts +1 -0
- package/dist/ui/overlays/ModelProfilePicker.js +4 -2
- package/dist/ui/overlays/PlanReviewOverlay.d.ts +2 -1
- package/dist/ui/overlays/PlanReviewOverlay.js +7 -4
- package/dist/ui/overlays/QuestionOverlay.d.ts +2 -1
- package/dist/ui/overlays/QuestionOverlay.js +45 -32
- package/dist/ui/overlays/QueueOverlay.js +2 -2
- package/dist/ui/overlays/SettingsScreen.d.ts +1 -0
- package/dist/ui/overlays/SettingsScreen.js +10 -3
- package/dist/ui/render-cache.d.ts +2 -2
- package/dist/ui/render-cache.js +4 -3
- package/dist/ui/run-metrics.d.ts +23 -0
- package/dist/ui/run-metrics.js +27 -0
- package/dist/ui/status-feedback.d.ts +33 -0
- package/dist/ui/status-feedback.js +36 -0
- package/dist/ui/stdout-frame-writer.js +28 -0
- package/package.json +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ModelSettingsSnapshot } from './model-profile-settings.js';
|
|
2
2
|
import type { ModelSettingsDraft, ModelSettingsPatch, ModelSettingsSaveResult } from '../config/model-profile-store.js';
|
|
3
|
+
import { type CompactionIdentity } from '../compaction/memory-identity.js';
|
|
3
4
|
/**
|
|
4
5
|
* AgentController — builds the core `Agent`, wires the built-in toolset, and
|
|
5
6
|
* exposes a small run/abort/steer surface plus the pre-flight validation that
|
|
@@ -123,6 +124,10 @@ export declare class AgentController {
|
|
|
123
124
|
private readonly agent;
|
|
124
125
|
private readonly providerRegistry;
|
|
125
126
|
private readonly modelRegistry;
|
|
127
|
+
private modelMetadataEnabled;
|
|
128
|
+
private modelMetadataGeneration;
|
|
129
|
+
private modelMetadataAbort;
|
|
130
|
+
private disposed;
|
|
126
131
|
private readonly tools;
|
|
127
132
|
private readonly skills;
|
|
128
133
|
private readonly skillsEnabled;
|
|
@@ -184,6 +189,10 @@ export declare class AgentController {
|
|
|
184
189
|
private readonly fast;
|
|
185
190
|
private readonly compaction;
|
|
186
191
|
private settingsRevision;
|
|
192
|
+
private requestVersion;
|
|
193
|
+
private idleCompaction;
|
|
194
|
+
private restoredCompactionIdentity;
|
|
195
|
+
private readonly compactionBusyListeners;
|
|
187
196
|
private modelSettingsBlocked;
|
|
188
197
|
private preparedSystemPrompt;
|
|
189
198
|
private readonly contextMeter;
|
|
@@ -287,6 +296,7 @@ export declare class AgentController {
|
|
|
287
296
|
private waitForEngineIdle;
|
|
288
297
|
/** Prepare before clearing the plan, then enter the engine without yielding. */
|
|
289
298
|
private startPrompt;
|
|
299
|
+
private prepareUserTurn;
|
|
290
300
|
/** Cancel pending startup as well as any engine run already in progress. */
|
|
291
301
|
abort(): void;
|
|
292
302
|
private stopEngine;
|
|
@@ -375,6 +385,11 @@ export declare class AgentController {
|
|
|
375
385
|
queueUserMessage(text: string): string;
|
|
376
386
|
private enqueueSteering;
|
|
377
387
|
followUp(text: string): void;
|
|
388
|
+
/** Resume queued input without appending a duplicate user message. */
|
|
389
|
+
continue(): Promise<PromptOutcome>;
|
|
390
|
+
/** Pending user receipts survive until Core accepts or the host clears them. */
|
|
391
|
+
hasPendingUserMessages(): boolean;
|
|
392
|
+
private compactionBusyOutcome;
|
|
378
393
|
isRunning(): boolean;
|
|
379
394
|
getAgentMode(): AgentMode;
|
|
380
395
|
/**
|
|
@@ -534,6 +549,7 @@ export declare class AgentController {
|
|
|
534
549
|
isModelSettingsBusy(): boolean;
|
|
535
550
|
/** Monotonic revision protects an editor from all intervening settings changes. */
|
|
536
551
|
getSettingsRevision(): number;
|
|
552
|
+
private advanceSettingsRevision;
|
|
537
553
|
/** Read-only draft creation; no startup migration or credential copying. */
|
|
538
554
|
createModelSettingsDraft(): ModelSettingsDraft;
|
|
539
555
|
/** Save an explicit draft as a single disk/live transaction. */
|
|
@@ -592,8 +608,15 @@ export declare class AgentController {
|
|
|
592
608
|
clearMessages(): void;
|
|
593
609
|
/** Read-only view of the live system prompt (regression tests, /debug). */
|
|
594
610
|
getSystemPrompt(): string;
|
|
595
|
-
replaceMessages(messages: Message[]): void;
|
|
611
|
+
replaceMessages(messages: Message[], identity?: CompactionIdentity): void;
|
|
596
612
|
getMessages(): Message[];
|
|
613
|
+
/** Atomically capture adopted history and its host credential for serialization. */
|
|
614
|
+
getSessionSnapshot(): {
|
|
615
|
+
messages: Message[];
|
|
616
|
+
compactionIdentity?: CompactionIdentity;
|
|
617
|
+
};
|
|
618
|
+
/** Compatibility name for the documented compaction session boundary. */
|
|
619
|
+
getCompactionSessionState(): ReturnType<AgentController['getSessionSnapshot']>;
|
|
597
620
|
clearAllQueues(): void;
|
|
598
621
|
getConfig(): CliConfig;
|
|
599
622
|
getProviderRegistry(): ProviderRegistry;
|
|
@@ -608,7 +631,11 @@ export declare class AgentController {
|
|
|
608
631
|
* cost table for the model that actually billed" cannot drift from "the cost
|
|
609
632
|
* table for the session's model" as two separate bodies.
|
|
610
633
|
*/
|
|
611
|
-
getModelInfoFor(ref: Pick<ModelRef, 'providerId' | 'modelId'>): ModelInfo;
|
|
634
|
+
getModelInfoFor(ref: Pick<ModelRef, 'providerId' | 'modelId' | 'baseUrl'>): ModelInfo;
|
|
635
|
+
/** Best-effort metadata lookup; never block typing or a model request. */
|
|
636
|
+
refreshModelMetadata(): Promise<void>;
|
|
637
|
+
/** Invalidate an old connection without restarting a lookup on every prompt. */
|
|
638
|
+
private resetModelMetadataRequest;
|
|
612
639
|
/**
|
|
613
640
|
* Whether the STATIC table knows this model's price (C-11 / RV-4).
|
|
614
641
|
*
|
|
@@ -751,6 +778,13 @@ export declare class AgentController {
|
|
|
751
778
|
ok: boolean;
|
|
752
779
|
reason?: string;
|
|
753
780
|
}>;
|
|
781
|
+
/** Local prepare/adopt ownership, available before any network-start event. */
|
|
782
|
+
isCompactionBusy(): boolean;
|
|
783
|
+
/** Cancel local compaction and await ownership release before a conversation switch. */
|
|
784
|
+
cancelCompaction(): Promise<void>;
|
|
785
|
+
/** Subscribe to synchronous ownership transitions; observers cannot break adoption. */
|
|
786
|
+
subscribeCompactionBusy(listener: (busy: boolean) => void): () => void;
|
|
787
|
+
private publishCompactionBusy;
|
|
754
788
|
/**
|
|
755
789
|
* Subscribe to the CLI-local compaction event stream.
|
|
756
790
|
*
|
package/dist/agent/controller.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { createModelSettingsDraft, saveModelSettings, reloadModelSettings, } from './model-profile-settings.js';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { verifyCompactionIdentity } from '../compaction/memory-identity.js';
|
|
4
|
+
import { COMPACTION_LIMITS } from '../compaction/limits.js';
|
|
3
5
|
import { Agent, initProviders, ModelRegistry, } from '@aragon-agent/core';
|
|
4
6
|
import { clampCompactionConfig, clampFastConfig, clampRetryConfig, clampTeamConfig, clampTodoConfig, isAdapterProvider, toRetryPolicy, } from '../config/schema.js';
|
|
5
7
|
import { resolveModelProfileState } from '../config/model-profile-resolution.js';
|
|
@@ -51,6 +53,10 @@ export class AgentController {
|
|
|
51
53
|
agent;
|
|
52
54
|
providerRegistry;
|
|
53
55
|
modelRegistry;
|
|
56
|
+
modelMetadataEnabled = false;
|
|
57
|
+
modelMetadataGeneration = 0;
|
|
58
|
+
modelMetadataAbort = new AbortController();
|
|
59
|
+
disposed = false;
|
|
54
60
|
tools;
|
|
55
61
|
skills;
|
|
56
62
|
skillsEnabled;
|
|
@@ -197,6 +203,10 @@ export class AgentController {
|
|
|
197
203
|
// and a `Set`.
|
|
198
204
|
// -----------------------------------------------------------------------
|
|
199
205
|
settingsRevision = 0;
|
|
206
|
+
requestVersion = 0;
|
|
207
|
+
idleCompaction;
|
|
208
|
+
restoredCompactionIdentity;
|
|
209
|
+
compactionBusyListeners = new Set();
|
|
200
210
|
modelSettingsBlocked = false;
|
|
201
211
|
preparedSystemPrompt;
|
|
202
212
|
contextMeter;
|
|
@@ -249,7 +259,7 @@ export class AgentController {
|
|
|
249
259
|
*/
|
|
250
260
|
abortRequested = false;
|
|
251
261
|
/** Only exact acceptance receipts release user messages from reviewer protection. */
|
|
252
|
-
pendingUserSteering = new
|
|
262
|
+
pendingUserSteering = new Map();
|
|
253
263
|
/** Session changes must not make a delayed receipt match a new message. */
|
|
254
264
|
steeringPrefix = randomUUID();
|
|
255
265
|
steeringSequence = 0;
|
|
@@ -526,18 +536,12 @@ export class AgentController {
|
|
|
526
536
|
// EVERY session, including one with compaction off, because that is the one
|
|
527
537
|
// whose gauge was broken (P1-4 / P0-2).
|
|
528
538
|
this.contextMeter = new ContextMeter({
|
|
539
|
+
getRequestVersion: () => this.requestVersion,
|
|
529
540
|
getMessages: () => this.agent.state.messages,
|
|
530
541
|
getSystemPrompt: () => this.agent.state.systemPrompt,
|
|
531
542
|
getModelInfo: () => this.getModelInfo(),
|
|
532
|
-
//
|
|
533
|
-
|
|
534
|
-
// back to `buildRuntimeModel` - a 128k placeholder AND a zero cost table -
|
|
535
|
-
// for exactly the models it answers `false` for. The two are the same
|
|
536
|
-
// question about the same static table, not a coincidence.
|
|
537
|
-
isWindowKnown: () => this.isPricedModel({
|
|
538
|
-
providerId: this.config.provider,
|
|
539
|
-
modelId: this.config.model,
|
|
540
|
-
}),
|
|
543
|
+
// Context discovery does not imply that the model's price is known.
|
|
544
|
+
isWindowKnown: () => this.getModelInfo().contextWindowSource !== 'fallback',
|
|
541
545
|
// LIVE, so a settings-screen edit moves the denominator without a relaunch.
|
|
542
546
|
getWindowOverride: () => this.config.contextWindow,
|
|
543
547
|
});
|
|
@@ -771,7 +775,10 @@ export class AgentController {
|
|
|
771
775
|
});
|
|
772
776
|
}
|
|
773
777
|
rebuildSystemPrompt() {
|
|
774
|
-
this.
|
|
778
|
+
const prompt = this.preparedSystemPrompt ?? this.composeSystemPrompt();
|
|
779
|
+
if (prompt !== this.agent.state.systemPrompt)
|
|
780
|
+
this.idleCompaction?.abort();
|
|
781
|
+
this.agent.setSystemPrompt(prompt);
|
|
775
782
|
}
|
|
776
783
|
/** Re-render the prompt from the current skill set and tell the UI. */
|
|
777
784
|
refreshSkills() {
|
|
@@ -802,7 +809,7 @@ export class AgentController {
|
|
|
802
809
|
* re-memoizes the palette (P1-1). Does not touch `@aragon-agent/core`.
|
|
803
810
|
*/
|
|
804
811
|
setTheme(name) {
|
|
805
|
-
this.
|
|
812
|
+
this.advanceSettingsRevision();
|
|
806
813
|
this.config = { ...this.config, theme: name };
|
|
807
814
|
}
|
|
808
815
|
/**
|
|
@@ -859,10 +866,14 @@ export class AgentController {
|
|
|
859
866
|
* never rejects, so callers must not treat resolution as success.
|
|
860
867
|
*/
|
|
861
868
|
async prompt(text, options = {}) {
|
|
869
|
+
if (this.isCompactionBusy())
|
|
870
|
+
return this.compactionBusyOutcome();
|
|
862
871
|
// Capture BEFORE stopping: synchronous abort listeners may cancel this request.
|
|
863
872
|
if (this.modelSettingsBlocked) {
|
|
864
873
|
return { status: 'not-started', reason: 'failed' };
|
|
865
874
|
}
|
|
875
|
+
if (this.modelMetadataEnabled)
|
|
876
|
+
void this.refreshModelMetadata();
|
|
866
877
|
const request = ++this.startupSequence;
|
|
867
878
|
try {
|
|
868
879
|
if (this.agent.state.isRunning) {
|
|
@@ -874,6 +885,8 @@ export class AgentController {
|
|
|
874
885
|
}
|
|
875
886
|
if (this.agent.state.isRunning)
|
|
876
887
|
throw new Error('The previous run is still stopping.');
|
|
888
|
+
if (this.isCompactionBusy())
|
|
889
|
+
return this.compactionBusyOutcome();
|
|
877
890
|
return await this.startPrompt(text, options, request);
|
|
878
891
|
}
|
|
879
892
|
catch (err) {
|
|
@@ -909,10 +922,7 @@ export class AgentController {
|
|
|
909
922
|
}
|
|
910
923
|
/** Prepare before clearing the plan, then enter the engine without yielding. */
|
|
911
924
|
async startPrompt(text, options, request) {
|
|
912
|
-
|
|
913
|
-
this.skills.beginUserTurn();
|
|
914
|
-
this.askRounds = 0;
|
|
915
|
-
this.fast?.setGoal(text);
|
|
925
|
+
this.prepareUserTurn(text);
|
|
916
926
|
if (request !== this.startupSequence) {
|
|
917
927
|
return { status: 'not-started', reason: 'cancelled' };
|
|
918
928
|
}
|
|
@@ -920,9 +930,16 @@ export class AgentController {
|
|
|
920
930
|
await this.agent.prompt(text);
|
|
921
931
|
return { status: 'finished' };
|
|
922
932
|
}
|
|
933
|
+
prepareUserTurn(text) {
|
|
934
|
+
if (this.skillsEnabled)
|
|
935
|
+
this.skills.beginUserTurn();
|
|
936
|
+
this.askRounds = 0;
|
|
937
|
+
this.fast?.setGoal(text);
|
|
938
|
+
}
|
|
923
939
|
/** Cancel pending startup as well as any engine run already in progress. */
|
|
924
940
|
abort() {
|
|
925
941
|
this.startupSequence += 1;
|
|
942
|
+
this.idleCompaction?.abort();
|
|
926
943
|
this.stopEngine();
|
|
927
944
|
}
|
|
928
945
|
stopEngine() {
|
|
@@ -1067,11 +1084,11 @@ export class AgentController {
|
|
|
1067
1084
|
}
|
|
1068
1085
|
enqueueSteering(text, user) {
|
|
1069
1086
|
// Both reviewer and user guidance can activate queued skill frames.
|
|
1070
|
-
if (this.skillsEnabled)
|
|
1087
|
+
if (this.skillsEnabled && this.isRunning())
|
|
1071
1088
|
this.skills.absorbPendingFrames();
|
|
1072
1089
|
const id = user ? `${this.steeringPrefix}:${++this.steeringSequence}` : undefined;
|
|
1073
1090
|
if (id !== undefined)
|
|
1074
|
-
this.pendingUserSteering.
|
|
1091
|
+
this.pendingUserSteering.set(id, text);
|
|
1075
1092
|
try {
|
|
1076
1093
|
this.agent.steer(text, id);
|
|
1077
1094
|
}
|
|
@@ -1085,6 +1102,30 @@ export class AgentController {
|
|
|
1085
1102
|
followUp(text) {
|
|
1086
1103
|
this.agent.followUp(text);
|
|
1087
1104
|
}
|
|
1105
|
+
/** Resume queued input without appending a duplicate user message. */
|
|
1106
|
+
async continue() {
|
|
1107
|
+
if (this.isCompactionBusy())
|
|
1108
|
+
return this.compactionBusyOutcome();
|
|
1109
|
+
if (this.modelSettingsBlocked || this.isRunning()) {
|
|
1110
|
+
return { status: 'not-started', reason: 'failed' };
|
|
1111
|
+
}
|
|
1112
|
+
if (this.pendingUserSteering.size > 0) {
|
|
1113
|
+
const request = ++this.startupSequence;
|
|
1114
|
+
this.prepareUserTurn([...this.pendingUserSteering.values()].join('\n\n'));
|
|
1115
|
+
if (request !== this.startupSequence) {
|
|
1116
|
+
return { status: 'not-started', reason: 'cancelled' };
|
|
1117
|
+
}
|
|
1118
|
+
this.todos?.beginUserTurn('new-task');
|
|
1119
|
+
}
|
|
1120
|
+
await this.agent.continue();
|
|
1121
|
+
return { status: 'finished' };
|
|
1122
|
+
}
|
|
1123
|
+
/** Pending user receipts survive until Core accepts or the host clears them. */
|
|
1124
|
+
hasPendingUserMessages() { return this.pendingUserSteering.size > 0; }
|
|
1125
|
+
compactionBusyOutcome() {
|
|
1126
|
+
this.notifyHostFn?.('warn', 'Context compaction is busy; your input was not submitted.');
|
|
1127
|
+
return { status: 'not-started', reason: 'failed' };
|
|
1128
|
+
}
|
|
1088
1129
|
isRunning() {
|
|
1089
1130
|
return this.agent.state.isRunning;
|
|
1090
1131
|
}
|
|
@@ -1217,7 +1258,7 @@ export class AgentController {
|
|
|
1217
1258
|
* honestly by checking `isTeamRegistered()` first.
|
|
1218
1259
|
*/
|
|
1219
1260
|
setTeamEnabled(enabled) {
|
|
1220
|
-
this.
|
|
1261
|
+
this.advanceSettingsRevision();
|
|
1221
1262
|
this.teamEnabled = enabled;
|
|
1222
1263
|
this.rebuildSystemPrompt();
|
|
1223
1264
|
}
|
|
@@ -1231,7 +1272,7 @@ export class AgentController {
|
|
|
1231
1272
|
*/
|
|
1232
1273
|
setTeamConfig(patch) {
|
|
1233
1274
|
const team = clampTeamConfig({ ...this.config.team, ...patch });
|
|
1234
|
-
this.
|
|
1275
|
+
this.advanceSettingsRevision();
|
|
1235
1276
|
this.config = { ...this.config, team };
|
|
1236
1277
|
this.rebuildSystemPrompt();
|
|
1237
1278
|
return team;
|
|
@@ -1257,6 +1298,10 @@ export class AgentController {
|
|
|
1257
1298
|
}
|
|
1258
1299
|
/** Abort every child and clear timers. Idempotent; safe to call twice. */
|
|
1259
1300
|
dispose() {
|
|
1301
|
+
this.idleCompaction?.abort();
|
|
1302
|
+
this.disposed = true;
|
|
1303
|
+
this.modelMetadataGeneration += 1;
|
|
1304
|
+
this.modelMetadataAbort.abort();
|
|
1260
1305
|
this.teamRuntime?.dispose();
|
|
1261
1306
|
this.fast?.dispose();
|
|
1262
1307
|
this.compaction?.dispose();
|
|
@@ -1293,7 +1338,7 @@ export class AgentController {
|
|
|
1293
1338
|
* reports that honestly by checking `isTodoRegistered()` first.
|
|
1294
1339
|
*/
|
|
1295
1340
|
setTodoEnabled(enabled) {
|
|
1296
|
-
this.
|
|
1341
|
+
this.advanceSettingsRevision();
|
|
1297
1342
|
this.todoEnabled = enabled;
|
|
1298
1343
|
this.rebuildSystemPrompt();
|
|
1299
1344
|
}
|
|
@@ -1316,7 +1361,7 @@ export class AgentController {
|
|
|
1316
1361
|
*/
|
|
1317
1362
|
setTodoConfig(patch) {
|
|
1318
1363
|
const todo = clampTodoConfig({ ...this.config.todo, ...patch });
|
|
1319
|
-
this.
|
|
1364
|
+
this.advanceSettingsRevision();
|
|
1320
1365
|
this.config = { ...this.config, todo };
|
|
1321
1366
|
this.rebuildSystemPrompt();
|
|
1322
1367
|
return todo;
|
|
@@ -1372,7 +1417,7 @@ export class AgentController {
|
|
|
1372
1417
|
*/
|
|
1373
1418
|
setRetryConfig(patch) {
|
|
1374
1419
|
const retry = clampRetryConfig({ ...this.config.retry, ...patch });
|
|
1375
|
-
this.
|
|
1420
|
+
this.advanceSettingsRevision();
|
|
1376
1421
|
this.config = { ...this.config, retry };
|
|
1377
1422
|
this.providerRegistry.setRetryPolicy(toRetryPolicy(retry));
|
|
1378
1423
|
return retry;
|
|
@@ -1382,11 +1427,16 @@ export class AgentController {
|
|
|
1382
1427
|
// -----------------------------------------------------------------------
|
|
1383
1428
|
/** True while any request can still use a model or credential snapshot. */
|
|
1384
1429
|
isModelSettingsBusy() {
|
|
1385
|
-
return this.isRunning() || this.isTeamBusy() || this.
|
|
1430
|
+
return this.isRunning() || this.isTeamBusy() || this.isCompactionBusy()
|
|
1431
|
+
|| this.getCompactionSnapshot().inFlight
|
|
1386
1432
|
|| this.getFastStatus().snapshot.inFlight;
|
|
1387
1433
|
}
|
|
1388
1434
|
/** Monotonic revision protects an editor from all intervening settings changes. */
|
|
1389
1435
|
getSettingsRevision() { return this.settingsRevision; }
|
|
1436
|
+
advanceSettingsRevision() {
|
|
1437
|
+
this.settingsRevision += 1;
|
|
1438
|
+
this.idleCompaction?.abort();
|
|
1439
|
+
}
|
|
1390
1440
|
/** Read-only draft creation; no startup migration or credential copying. */
|
|
1391
1441
|
createModelSettingsDraft() {
|
|
1392
1442
|
return createModelSettingsDraft(this.config, this.settingsRevision);
|
|
@@ -1412,21 +1462,37 @@ export class AgentController {
|
|
|
1412
1462
|
}
|
|
1413
1463
|
/** Apply prepared state synchronously, without rebuilding the controller or its history. */
|
|
1414
1464
|
applyModelSettingsSnapshot(snapshot) {
|
|
1465
|
+
const connectionChanged = this.config.provider !== snapshot.config.provider
|
|
1466
|
+
|| this.config.model !== snapshot.config.model || this.config.baseUrl !== snapshot.config.baseUrl;
|
|
1467
|
+
this.idleCompaction?.abort();
|
|
1415
1468
|
this.preparedSystemPrompt = snapshot.systemPrompt;
|
|
1416
1469
|
try {
|
|
1417
1470
|
this.config = snapshot.config;
|
|
1418
|
-
this.
|
|
1471
|
+
this.advanceSettingsRevision();
|
|
1419
1472
|
this.agent.setModel(snapshot.model);
|
|
1420
1473
|
this.agent.setThinkingLevel(snapshot.config.thinkingLevel);
|
|
1421
1474
|
this.agent.setMaxTokens(snapshot.config.maxTokens);
|
|
1475
|
+
// Credentials may change even when endpoint/model do not. Invalidate
|
|
1476
|
+
// before publishing a window, including when the new profile has no key.
|
|
1477
|
+
if (this.modelMetadataEnabled) {
|
|
1478
|
+
this.resetModelMetadataRequest();
|
|
1479
|
+
this.modelRegistry.clearCache();
|
|
1480
|
+
}
|
|
1422
1481
|
this.fast?.onConfigChanged(false, snapshot.config.fast.enabled);
|
|
1423
1482
|
this.compaction?.onConfigChanged(snapshot.config.compaction.enabled);
|
|
1424
|
-
|
|
1483
|
+
if (connectionChanged) {
|
|
1484
|
+
this.requestVersion += 1;
|
|
1485
|
+
this.contextMeter.onHistoryReplaced();
|
|
1486
|
+
}
|
|
1487
|
+
else
|
|
1488
|
+
this.contextMeter.onWindowChanged();
|
|
1425
1489
|
this.rebuildSystemPrompt();
|
|
1426
1490
|
}
|
|
1427
1491
|
finally {
|
|
1428
1492
|
this.preparedSystemPrompt = undefined;
|
|
1429
1493
|
}
|
|
1494
|
+
if (this.modelMetadataEnabled)
|
|
1495
|
+
void this.refreshModelMetadata();
|
|
1430
1496
|
}
|
|
1431
1497
|
/** Potentially failing ordinary-setting side effects are isolated from model application. */
|
|
1432
1498
|
applyModelSettingsEffects() { getLogger().reconfigure(this.config.log); }
|
|
@@ -1434,13 +1500,18 @@ export class AgentController {
|
|
|
1434
1500
|
blockModelSettingsRequests() {
|
|
1435
1501
|
this.modelSettingsBlocked = true;
|
|
1436
1502
|
this.config.modelSettingsRestartRequired = true;
|
|
1503
|
+
this.resetModelMetadataRequest();
|
|
1437
1504
|
}
|
|
1438
1505
|
/** The UI uses this before accepting or draining queued prompts. */
|
|
1439
1506
|
areModelSettingsBlocked() { return this.modelSettingsBlocked; }
|
|
1440
1507
|
setModel(provider, model, baseUrl) {
|
|
1441
1508
|
if (this.isModelSettingsBusy())
|
|
1442
1509
|
throw new Error('Model settings are busy.');
|
|
1443
|
-
this.
|
|
1510
|
+
const connectionChanged = this.config.provider !== provider || this.config.model !== model
|
|
1511
|
+
|| this.config.baseUrl !== baseUrl;
|
|
1512
|
+
if (this.modelMetadataEnabled)
|
|
1513
|
+
this.resetModelMetadataRequest();
|
|
1514
|
+
this.advanceSettingsRevision();
|
|
1444
1515
|
this.config = { ...this.config, provider, model, baseUrl };
|
|
1445
1516
|
this.config.modelProfileState = resolveModelProfileState(this.config);
|
|
1446
1517
|
this.agent.setModel({ providerId: provider, modelId: model, ...(baseUrl ? { baseUrl } : {}) });
|
|
@@ -1455,32 +1526,39 @@ export class AgentController {
|
|
|
1455
1526
|
// so the snapshot the chip and `/compact status` read has to be refreshed
|
|
1456
1527
|
// for the same reason and at the same moments (the RV-3 lesson).
|
|
1457
1528
|
this.compaction?.onConfigChanged();
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
this.
|
|
1529
|
+
if (connectionChanged) {
|
|
1530
|
+
this.requestVersion += 1;
|
|
1531
|
+
this.contextMeter.onHistoryReplaced();
|
|
1532
|
+
}
|
|
1533
|
+
else
|
|
1534
|
+
this.contextMeter.onWindowChanged();
|
|
1535
|
+
if (this.modelMetadataEnabled)
|
|
1536
|
+
void this.refreshModelMetadata();
|
|
1465
1537
|
}
|
|
1466
1538
|
setThinkingLevel(level) {
|
|
1467
|
-
this.
|
|
1539
|
+
this.advanceSettingsRevision();
|
|
1468
1540
|
this.config = { ...this.config, thinkingLevel: level };
|
|
1469
1541
|
this.agent.setThinkingLevel(level);
|
|
1470
1542
|
}
|
|
1471
1543
|
setMaxTokens(value) {
|
|
1472
|
-
this.
|
|
1544
|
+
this.advanceSettingsRevision();
|
|
1473
1545
|
this.config = { ...this.config, maxTokens: value };
|
|
1474
1546
|
this.agent.setMaxTokens(value);
|
|
1475
1547
|
}
|
|
1476
1548
|
setApiKey(provider, key) {
|
|
1477
1549
|
// The Agent resolves keys via `resolveKey(this.config)` at call time, so
|
|
1478
1550
|
// updating the config here is sufficient — no Agent rebuild needed.
|
|
1479
|
-
this.
|
|
1551
|
+
this.advanceSettingsRevision();
|
|
1480
1552
|
this.config = {
|
|
1481
1553
|
...this.config,
|
|
1482
1554
|
apiKeys: { ...this.config.apiKeys, [provider]: key },
|
|
1483
1555
|
};
|
|
1556
|
+
if (this.modelMetadataEnabled) {
|
|
1557
|
+
this.resetModelMetadataRequest();
|
|
1558
|
+
this.modelRegistry.clearCache(provider);
|
|
1559
|
+
this.contextMeter.onWindowChanged();
|
|
1560
|
+
void this.refreshModelMetadata();
|
|
1561
|
+
}
|
|
1484
1562
|
// Rule 5 of §3.2 reads `apiKeys`, so a key edit is a tier event: this is the
|
|
1485
1563
|
// path that turns `no_key` back into a working tier without a relaunch.
|
|
1486
1564
|
this.fast?.onConfigChanged();
|
|
@@ -1491,7 +1569,7 @@ export class AgentController {
|
|
|
1491
1569
|
}
|
|
1492
1570
|
setCwd(cwd) {
|
|
1493
1571
|
this.cwd = cwd;
|
|
1494
|
-
this.
|
|
1572
|
+
this.advanceSettingsRevision();
|
|
1495
1573
|
this.config = { ...this.config, cwd };
|
|
1496
1574
|
// The project scope moved with the cwd, so rescan BEFORE rebuilding — then
|
|
1497
1575
|
// let the single prompt entry point emit a prompt that reflects both the new
|
|
@@ -1560,6 +1638,8 @@ export class AgentController {
|
|
|
1560
1638
|
};
|
|
1561
1639
|
}
|
|
1562
1640
|
clearMessages() {
|
|
1641
|
+
this.idleCompaction?.abort();
|
|
1642
|
+
this.restoredCompactionIdentity = undefined;
|
|
1563
1643
|
this.agent.clearMessages();
|
|
1564
1644
|
this.startupSequence += 1;
|
|
1565
1645
|
// INVALIDATION SITE 3 (context-auto-compaction-hardening §3.2.3). The history
|
|
@@ -1587,7 +1667,9 @@ export class AgentController {
|
|
|
1587
1667
|
getSystemPrompt() {
|
|
1588
1668
|
return this.agent.state.systemPrompt;
|
|
1589
1669
|
}
|
|
1590
|
-
replaceMessages(messages) {
|
|
1670
|
+
replaceMessages(messages, identity) {
|
|
1671
|
+
this.idleCompaction?.abort();
|
|
1672
|
+
this.restoredCompactionIdentity = identity;
|
|
1591
1673
|
this.agent.replaceMessages(messages);
|
|
1592
1674
|
this.startupSequence += 1;
|
|
1593
1675
|
// INVALIDATION SITE 4 (§3.2.3) - `/resume`, and any future host-side rewrite.
|
|
@@ -1600,12 +1682,26 @@ export class AgentController {
|
|
|
1600
1682
|
// reading stood until the next completed turn. The DEEP reset also drops
|
|
1601
1683
|
// `estimateOffset`, because `/resume` can change the model in the same
|
|
1602
1684
|
// breath (I-8).
|
|
1603
|
-
this.compaction?.onHistoryReplaced();
|
|
1685
|
+
this.compaction?.onHistoryReplaced(identity);
|
|
1604
1686
|
this.contextMeter.onHistoryReplaced();
|
|
1605
1687
|
}
|
|
1606
1688
|
getMessages() {
|
|
1607
1689
|
return this.agent.state.messages;
|
|
1608
1690
|
}
|
|
1691
|
+
/** Atomically capture adopted history and its host credential for serialization. */
|
|
1692
|
+
getSessionSnapshot() {
|
|
1693
|
+
const messages = this.getMessages();
|
|
1694
|
+
const identity = this.compaction ? this.compaction.getIdentity() : this.restoredCompactionIdentity;
|
|
1695
|
+
if (identity && !verifyCompactionIdentity(messages, identity)) {
|
|
1696
|
+
getLogger().debug('agent', 'invalid_compaction_session_identity');
|
|
1697
|
+
}
|
|
1698
|
+
// Retain invalid evidence so damaged host memory cannot become ordinary text.
|
|
1699
|
+
return { messages, ...(identity ? { compactionIdentity: { ...identity } } : {}) };
|
|
1700
|
+
}
|
|
1701
|
+
/** Compatibility name for the documented compaction session boundary. */
|
|
1702
|
+
getCompactionSessionState() {
|
|
1703
|
+
return this.getSessionSnapshot();
|
|
1704
|
+
}
|
|
1609
1705
|
clearAllQueues() {
|
|
1610
1706
|
this.agent.clearAllQueues();
|
|
1611
1707
|
this.pendingUserSteering.clear();
|
|
@@ -1639,9 +1735,38 @@ export class AgentController {
|
|
|
1639
1735
|
*/
|
|
1640
1736
|
getModelInfoFor(ref) {
|
|
1641
1737
|
const found = this.modelRegistry.getModel(ref.providerId, ref.modelId);
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
return
|
|
1738
|
+
const baseUrl = ref.baseUrl ?? (ref.providerId === this.config.provider && ref.modelId === this.config.model
|
|
1739
|
+
? this.config.baseUrl : undefined);
|
|
1740
|
+
return {
|
|
1741
|
+
...(found ?? this.modelRegistry.buildRuntimeModel(ref.providerId, ref.modelId)),
|
|
1742
|
+
...this.modelRegistry.getContextWindow(ref.providerId, ref.modelId, baseUrl),
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
/** Best-effort metadata lookup; never block typing or a model request. */
|
|
1746
|
+
async refreshModelMetadata() {
|
|
1747
|
+
if (this.disposed || this.modelSettingsBlocked)
|
|
1748
|
+
return;
|
|
1749
|
+
this.modelMetadataEnabled = true;
|
|
1750
|
+
const generation = ++this.modelMetadataGeneration;
|
|
1751
|
+
const { provider, baseUrl } = this.config;
|
|
1752
|
+
try {
|
|
1753
|
+
const key = this.resolveKey(provider);
|
|
1754
|
+
if (!key)
|
|
1755
|
+
return;
|
|
1756
|
+
await this.modelRegistry.discoverModels(provider, key, baseUrl, this.modelMetadataAbort.signal);
|
|
1757
|
+
if (this.disposed || generation !== this.modelMetadataGeneration)
|
|
1758
|
+
return;
|
|
1759
|
+
this.contextMeter.onWindowChanged();
|
|
1760
|
+
}
|
|
1761
|
+
catch {
|
|
1762
|
+
// Offline or unsupported discovery leaves catalog/unknown metadata intact.
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
/** Invalidate an old connection without restarting a lookup on every prompt. */
|
|
1766
|
+
resetModelMetadataRequest() {
|
|
1767
|
+
this.modelMetadataGeneration += 1;
|
|
1768
|
+
this.modelMetadataAbort.abort();
|
|
1769
|
+
this.modelMetadataAbort = new AbortController();
|
|
1645
1770
|
}
|
|
1646
1771
|
/**
|
|
1647
1772
|
* Whether the STATIC table knows this model's price (C-11 / RV-4).
|
|
@@ -1676,7 +1801,7 @@ export class AgentController {
|
|
|
1676
1801
|
* `/fast` reports that honestly by checking `isFastRegistered()` first.
|
|
1677
1802
|
*/
|
|
1678
1803
|
setFastEnabled(enabled) {
|
|
1679
|
-
this.
|
|
1804
|
+
this.advanceSettingsRevision();
|
|
1680
1805
|
this.fast?.setEnabled(enabled);
|
|
1681
1806
|
}
|
|
1682
1807
|
getFastConfig() {
|
|
@@ -1692,7 +1817,7 @@ export class AgentController {
|
|
|
1692
1817
|
*/
|
|
1693
1818
|
setFastConfig(patch) {
|
|
1694
1819
|
const fast = clampFastConfig({ ...this.config.fast, ...patch });
|
|
1695
|
-
this.
|
|
1820
|
+
this.advanceSettingsRevision();
|
|
1696
1821
|
this.config = { ...this.config, fast };
|
|
1697
1822
|
// `onConfigChanged` rebuilds the prompt; when there is no wiring (the tier
|
|
1698
1823
|
// was never registered) nothing has to be rebuilt, because no block is
|
|
@@ -1792,7 +1917,7 @@ export class AgentController {
|
|
|
1792
1917
|
* on purpose (§3.2).
|
|
1793
1918
|
*/
|
|
1794
1919
|
setCompactionEnabled(enabled) {
|
|
1795
|
-
this.
|
|
1920
|
+
this.advanceSettingsRevision();
|
|
1796
1921
|
this.compaction?.setEnabled(enabled);
|
|
1797
1922
|
}
|
|
1798
1923
|
getCompactionConfig() {
|
|
@@ -1807,7 +1932,7 @@ export class AgentController {
|
|
|
1807
1932
|
*/
|
|
1808
1933
|
setCompactionConfig(patch) {
|
|
1809
1934
|
const compaction = clampCompactionConfig({ ...this.config.compaction, ...patch });
|
|
1810
|
-
this.
|
|
1935
|
+
this.advanceSettingsRevision();
|
|
1811
1936
|
this.config = { ...this.config, compaction };
|
|
1812
1937
|
this.compaction?.onConfigChanged();
|
|
1813
1938
|
return compaction;
|
|
@@ -1860,18 +1985,80 @@ export class AgentController {
|
|
|
1860
1985
|
return { ok: false, reason: 'settings_restart_required' };
|
|
1861
1986
|
if (!this.compaction)
|
|
1862
1987
|
return { ok: false, reason: 'not_registered' };
|
|
1988
|
+
if (this.isCompactionBusy() || this.isRunning())
|
|
1989
|
+
return { ok: false, reason: 'busy' };
|
|
1863
1990
|
const controller = new AbortController();
|
|
1864
|
-
const
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1991
|
+
const epoch = this.startupSequence;
|
|
1992
|
+
const settings = this.settingsRevision;
|
|
1993
|
+
let adopted = false;
|
|
1994
|
+
let timedOut = false;
|
|
1995
|
+
const isCurrent = () => this.idleCompaction === controller && !controller.signal.aborted
|
|
1996
|
+
&& epoch === this.startupSequence && settings === this.settingsRevision
|
|
1997
|
+
&& !this.isRunning() && !this.disposed;
|
|
1998
|
+
this.idleCompaction = controller;
|
|
1999
|
+
this.publishCompactionBusy(true);
|
|
2000
|
+
const deadline = setTimeout(() => {
|
|
2001
|
+
timedOut = true;
|
|
2002
|
+
controller.abort();
|
|
2003
|
+
}, COMPACTION_LIMITS.operationTimeoutMs);
|
|
2004
|
+
deadline.unref?.();
|
|
2005
|
+
try {
|
|
2006
|
+
// The wiring races cancellation locally and settles before releasing ownership.
|
|
2007
|
+
const outcome = await this.compaction.compactNow({
|
|
2008
|
+
messages: this.agent.state.messages,
|
|
2009
|
+
systemPrompt: this.agent.state.systemPrompt,
|
|
2010
|
+
model: this.agent.state.model,
|
|
2011
|
+
signal: controller.signal,
|
|
2012
|
+
...(instructions ? { instructions } : {}),
|
|
2013
|
+
isCurrent,
|
|
2014
|
+
adopt: (messages) => {
|
|
2015
|
+
if (!isCurrent())
|
|
2016
|
+
throw new Error('stale_history');
|
|
2017
|
+
this.agent.replaceMessages(messages);
|
|
2018
|
+
adopted = true;
|
|
2019
|
+
},
|
|
2020
|
+
});
|
|
2021
|
+
return outcome.ok || adopted ? { ok: true }
|
|
2022
|
+
: { ok: false, reason: timedOut ? 'timeout' : outcome.reason };
|
|
2023
|
+
}
|
|
2024
|
+
finally {
|
|
2025
|
+
clearTimeout(deadline);
|
|
2026
|
+
if (this.idleCompaction === controller) {
|
|
2027
|
+
this.idleCompaction = undefined;
|
|
2028
|
+
this.publishCompactionBusy(false);
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
/** Local prepare/adopt ownership, available before any network-start event. */
|
|
2033
|
+
isCompactionBusy() { return this.idleCompaction !== undefined; }
|
|
2034
|
+
/** Cancel local compaction and await ownership release before a conversation switch. */
|
|
2035
|
+
cancelCompaction() {
|
|
2036
|
+
if (!this.idleCompaction)
|
|
2037
|
+
return Promise.resolve();
|
|
2038
|
+
return new Promise((resolve) => {
|
|
2039
|
+
const unsubscribe = this.subscribeCompactionBusy((busy) => {
|
|
2040
|
+
if (busy)
|
|
2041
|
+
return;
|
|
2042
|
+
unsubscribe();
|
|
2043
|
+
resolve();
|
|
2044
|
+
});
|
|
2045
|
+
this.abort();
|
|
1870
2046
|
});
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
2047
|
+
}
|
|
2048
|
+
/** Subscribe to synchronous ownership transitions; observers cannot break adoption. */
|
|
2049
|
+
subscribeCompactionBusy(listener) {
|
|
2050
|
+
this.compactionBusyListeners.add(listener);
|
|
2051
|
+
return () => { this.compactionBusyListeners.delete(listener); };
|
|
2052
|
+
}
|
|
2053
|
+
publishCompactionBusy(busy) {
|
|
2054
|
+
for (const listener of this.compactionBusyListeners) {
|
|
2055
|
+
try {
|
|
2056
|
+
listener(busy);
|
|
2057
|
+
}
|
|
2058
|
+
catch {
|
|
2059
|
+
getLogger().debug('agent', 'compaction_busy_listener_threw');
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
1875
2062
|
}
|
|
1876
2063
|
/**
|
|
1877
2064
|
* Subscribe to the CLI-local compaction event stream.
|