@aragon-agent/cli 0.6.14 → 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 +14 -0
- package/README.md +250 -233
- package/dist/agent/controller.d.ts +27 -1
- package/dist/agent/controller.js +182 -45
- 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 +4 -0
- package/dist/agent/system-prompt.js +4 -0
- package/dist/commands/builtins.js +2 -2
- package/dist/compaction/child.d.ts +9 -1
- package/dist/compaction/child.js +39 -13
- package/dist/compaction/command.js +20 -10
- package/dist/compaction/compactor.d.ts +33 -204
- package/dist/compaction/compactor.js +311 -726
- 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 +53 -19
- package/dist/compaction/operation.d.ts +18 -0
- package/dist/compaction/operation.js +68 -0
- package/dist/compaction/pressure.d.ts +5 -20
- package/dist/compaction/pressure.js +13 -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 +21 -3
- package/dist/compaction/wiring.d.ts +10 -25
- package/dist/compaction/wiring.js +56 -66
- package/dist/config/schema.d.ts +1 -1
- 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 +218 -116
- 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/PromptInput.d.ts +3 -1
- package/dist/ui/PromptInput.js +32 -41
- 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/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/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 -84
- 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/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
|
|
@@ -188,6 +189,10 @@ export declare class AgentController {
|
|
|
188
189
|
private readonly fast;
|
|
189
190
|
private readonly compaction;
|
|
190
191
|
private settingsRevision;
|
|
192
|
+
private requestVersion;
|
|
193
|
+
private idleCompaction;
|
|
194
|
+
private restoredCompactionIdentity;
|
|
195
|
+
private readonly compactionBusyListeners;
|
|
191
196
|
private modelSettingsBlocked;
|
|
192
197
|
private preparedSystemPrompt;
|
|
193
198
|
private readonly contextMeter;
|
|
@@ -291,6 +296,7 @@ export declare class AgentController {
|
|
|
291
296
|
private waitForEngineIdle;
|
|
292
297
|
/** Prepare before clearing the plan, then enter the engine without yielding. */
|
|
293
298
|
private startPrompt;
|
|
299
|
+
private prepareUserTurn;
|
|
294
300
|
/** Cancel pending startup as well as any engine run already in progress. */
|
|
295
301
|
abort(): void;
|
|
296
302
|
private stopEngine;
|
|
@@ -379,6 +385,11 @@ export declare class AgentController {
|
|
|
379
385
|
queueUserMessage(text: string): string;
|
|
380
386
|
private enqueueSteering;
|
|
381
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;
|
|
382
393
|
isRunning(): boolean;
|
|
383
394
|
getAgentMode(): AgentMode;
|
|
384
395
|
/**
|
|
@@ -538,6 +549,7 @@ export declare class AgentController {
|
|
|
538
549
|
isModelSettingsBusy(): boolean;
|
|
539
550
|
/** Monotonic revision protects an editor from all intervening settings changes. */
|
|
540
551
|
getSettingsRevision(): number;
|
|
552
|
+
private advanceSettingsRevision;
|
|
541
553
|
/** Read-only draft creation; no startup migration or credential copying. */
|
|
542
554
|
createModelSettingsDraft(): ModelSettingsDraft;
|
|
543
555
|
/** Save an explicit draft as a single disk/live transaction. */
|
|
@@ -596,8 +608,15 @@ export declare class AgentController {
|
|
|
596
608
|
clearMessages(): void;
|
|
597
609
|
/** Read-only view of the live system prompt (regression tests, /debug). */
|
|
598
610
|
getSystemPrompt(): string;
|
|
599
|
-
replaceMessages(messages: Message[]): void;
|
|
611
|
+
replaceMessages(messages: Message[], identity?: CompactionIdentity): void;
|
|
600
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']>;
|
|
601
620
|
clearAllQueues(): void;
|
|
602
621
|
getConfig(): CliConfig;
|
|
603
622
|
getProviderRegistry(): ProviderRegistry;
|
|
@@ -759,6 +778,13 @@ export declare class AgentController {
|
|
|
759
778
|
ok: boolean;
|
|
760
779
|
reason?: string;
|
|
761
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;
|
|
762
788
|
/**
|
|
763
789
|
* Subscribe to the CLI-local compaction event stream.
|
|
764
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';
|
|
@@ -201,6 +203,10 @@ export class AgentController {
|
|
|
201
203
|
// and a `Set`.
|
|
202
204
|
// -----------------------------------------------------------------------
|
|
203
205
|
settingsRevision = 0;
|
|
206
|
+
requestVersion = 0;
|
|
207
|
+
idleCompaction;
|
|
208
|
+
restoredCompactionIdentity;
|
|
209
|
+
compactionBusyListeners = new Set();
|
|
204
210
|
modelSettingsBlocked = false;
|
|
205
211
|
preparedSystemPrompt;
|
|
206
212
|
contextMeter;
|
|
@@ -253,7 +259,7 @@ export class AgentController {
|
|
|
253
259
|
*/
|
|
254
260
|
abortRequested = false;
|
|
255
261
|
/** Only exact acceptance receipts release user messages from reviewer protection. */
|
|
256
|
-
pendingUserSteering = new
|
|
262
|
+
pendingUserSteering = new Map();
|
|
257
263
|
/** Session changes must not make a delayed receipt match a new message. */
|
|
258
264
|
steeringPrefix = randomUUID();
|
|
259
265
|
steeringSequence = 0;
|
|
@@ -530,6 +536,7 @@ export class AgentController {
|
|
|
530
536
|
// EVERY session, including one with compaction off, because that is the one
|
|
531
537
|
// whose gauge was broken (P1-4 / P0-2).
|
|
532
538
|
this.contextMeter = new ContextMeter({
|
|
539
|
+
getRequestVersion: () => this.requestVersion,
|
|
533
540
|
getMessages: () => this.agent.state.messages,
|
|
534
541
|
getSystemPrompt: () => this.agent.state.systemPrompt,
|
|
535
542
|
getModelInfo: () => this.getModelInfo(),
|
|
@@ -768,7 +775,10 @@ export class AgentController {
|
|
|
768
775
|
});
|
|
769
776
|
}
|
|
770
777
|
rebuildSystemPrompt() {
|
|
771
|
-
this.
|
|
778
|
+
const prompt = this.preparedSystemPrompt ?? this.composeSystemPrompt();
|
|
779
|
+
if (prompt !== this.agent.state.systemPrompt)
|
|
780
|
+
this.idleCompaction?.abort();
|
|
781
|
+
this.agent.setSystemPrompt(prompt);
|
|
772
782
|
}
|
|
773
783
|
/** Re-render the prompt from the current skill set and tell the UI. */
|
|
774
784
|
refreshSkills() {
|
|
@@ -799,7 +809,7 @@ export class AgentController {
|
|
|
799
809
|
* re-memoizes the palette (P1-1). Does not touch `@aragon-agent/core`.
|
|
800
810
|
*/
|
|
801
811
|
setTheme(name) {
|
|
802
|
-
this.
|
|
812
|
+
this.advanceSettingsRevision();
|
|
803
813
|
this.config = { ...this.config, theme: name };
|
|
804
814
|
}
|
|
805
815
|
/**
|
|
@@ -856,6 +866,8 @@ export class AgentController {
|
|
|
856
866
|
* never rejects, so callers must not treat resolution as success.
|
|
857
867
|
*/
|
|
858
868
|
async prompt(text, options = {}) {
|
|
869
|
+
if (this.isCompactionBusy())
|
|
870
|
+
return this.compactionBusyOutcome();
|
|
859
871
|
// Capture BEFORE stopping: synchronous abort listeners may cancel this request.
|
|
860
872
|
if (this.modelSettingsBlocked) {
|
|
861
873
|
return { status: 'not-started', reason: 'failed' };
|
|
@@ -873,6 +885,8 @@ export class AgentController {
|
|
|
873
885
|
}
|
|
874
886
|
if (this.agent.state.isRunning)
|
|
875
887
|
throw new Error('The previous run is still stopping.');
|
|
888
|
+
if (this.isCompactionBusy())
|
|
889
|
+
return this.compactionBusyOutcome();
|
|
876
890
|
return await this.startPrompt(text, options, request);
|
|
877
891
|
}
|
|
878
892
|
catch (err) {
|
|
@@ -908,10 +922,7 @@ export class AgentController {
|
|
|
908
922
|
}
|
|
909
923
|
/** Prepare before clearing the plan, then enter the engine without yielding. */
|
|
910
924
|
async startPrompt(text, options, request) {
|
|
911
|
-
|
|
912
|
-
this.skills.beginUserTurn();
|
|
913
|
-
this.askRounds = 0;
|
|
914
|
-
this.fast?.setGoal(text);
|
|
925
|
+
this.prepareUserTurn(text);
|
|
915
926
|
if (request !== this.startupSequence) {
|
|
916
927
|
return { status: 'not-started', reason: 'cancelled' };
|
|
917
928
|
}
|
|
@@ -919,9 +930,16 @@ export class AgentController {
|
|
|
919
930
|
await this.agent.prompt(text);
|
|
920
931
|
return { status: 'finished' };
|
|
921
932
|
}
|
|
933
|
+
prepareUserTurn(text) {
|
|
934
|
+
if (this.skillsEnabled)
|
|
935
|
+
this.skills.beginUserTurn();
|
|
936
|
+
this.askRounds = 0;
|
|
937
|
+
this.fast?.setGoal(text);
|
|
938
|
+
}
|
|
922
939
|
/** Cancel pending startup as well as any engine run already in progress. */
|
|
923
940
|
abort() {
|
|
924
941
|
this.startupSequence += 1;
|
|
942
|
+
this.idleCompaction?.abort();
|
|
925
943
|
this.stopEngine();
|
|
926
944
|
}
|
|
927
945
|
stopEngine() {
|
|
@@ -1066,11 +1084,11 @@ export class AgentController {
|
|
|
1066
1084
|
}
|
|
1067
1085
|
enqueueSteering(text, user) {
|
|
1068
1086
|
// Both reviewer and user guidance can activate queued skill frames.
|
|
1069
|
-
if (this.skillsEnabled)
|
|
1087
|
+
if (this.skillsEnabled && this.isRunning())
|
|
1070
1088
|
this.skills.absorbPendingFrames();
|
|
1071
1089
|
const id = user ? `${this.steeringPrefix}:${++this.steeringSequence}` : undefined;
|
|
1072
1090
|
if (id !== undefined)
|
|
1073
|
-
this.pendingUserSteering.
|
|
1091
|
+
this.pendingUserSteering.set(id, text);
|
|
1074
1092
|
try {
|
|
1075
1093
|
this.agent.steer(text, id);
|
|
1076
1094
|
}
|
|
@@ -1084,6 +1102,30 @@ export class AgentController {
|
|
|
1084
1102
|
followUp(text) {
|
|
1085
1103
|
this.agent.followUp(text);
|
|
1086
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
|
+
}
|
|
1087
1129
|
isRunning() {
|
|
1088
1130
|
return this.agent.state.isRunning;
|
|
1089
1131
|
}
|
|
@@ -1216,7 +1258,7 @@ export class AgentController {
|
|
|
1216
1258
|
* honestly by checking `isTeamRegistered()` first.
|
|
1217
1259
|
*/
|
|
1218
1260
|
setTeamEnabled(enabled) {
|
|
1219
|
-
this.
|
|
1261
|
+
this.advanceSettingsRevision();
|
|
1220
1262
|
this.teamEnabled = enabled;
|
|
1221
1263
|
this.rebuildSystemPrompt();
|
|
1222
1264
|
}
|
|
@@ -1230,7 +1272,7 @@ export class AgentController {
|
|
|
1230
1272
|
*/
|
|
1231
1273
|
setTeamConfig(patch) {
|
|
1232
1274
|
const team = clampTeamConfig({ ...this.config.team, ...patch });
|
|
1233
|
-
this.
|
|
1275
|
+
this.advanceSettingsRevision();
|
|
1234
1276
|
this.config = { ...this.config, team };
|
|
1235
1277
|
this.rebuildSystemPrompt();
|
|
1236
1278
|
return team;
|
|
@@ -1256,6 +1298,7 @@ export class AgentController {
|
|
|
1256
1298
|
}
|
|
1257
1299
|
/** Abort every child and clear timers. Idempotent; safe to call twice. */
|
|
1258
1300
|
dispose() {
|
|
1301
|
+
this.idleCompaction?.abort();
|
|
1259
1302
|
this.disposed = true;
|
|
1260
1303
|
this.modelMetadataGeneration += 1;
|
|
1261
1304
|
this.modelMetadataAbort.abort();
|
|
@@ -1295,7 +1338,7 @@ export class AgentController {
|
|
|
1295
1338
|
* reports that honestly by checking `isTodoRegistered()` first.
|
|
1296
1339
|
*/
|
|
1297
1340
|
setTodoEnabled(enabled) {
|
|
1298
|
-
this.
|
|
1341
|
+
this.advanceSettingsRevision();
|
|
1299
1342
|
this.todoEnabled = enabled;
|
|
1300
1343
|
this.rebuildSystemPrompt();
|
|
1301
1344
|
}
|
|
@@ -1318,7 +1361,7 @@ export class AgentController {
|
|
|
1318
1361
|
*/
|
|
1319
1362
|
setTodoConfig(patch) {
|
|
1320
1363
|
const todo = clampTodoConfig({ ...this.config.todo, ...patch });
|
|
1321
|
-
this.
|
|
1364
|
+
this.advanceSettingsRevision();
|
|
1322
1365
|
this.config = { ...this.config, todo };
|
|
1323
1366
|
this.rebuildSystemPrompt();
|
|
1324
1367
|
return todo;
|
|
@@ -1374,7 +1417,7 @@ export class AgentController {
|
|
|
1374
1417
|
*/
|
|
1375
1418
|
setRetryConfig(patch) {
|
|
1376
1419
|
const retry = clampRetryConfig({ ...this.config.retry, ...patch });
|
|
1377
|
-
this.
|
|
1420
|
+
this.advanceSettingsRevision();
|
|
1378
1421
|
this.config = { ...this.config, retry };
|
|
1379
1422
|
this.providerRegistry.setRetryPolicy(toRetryPolicy(retry));
|
|
1380
1423
|
return retry;
|
|
@@ -1384,11 +1427,16 @@ export class AgentController {
|
|
|
1384
1427
|
// -----------------------------------------------------------------------
|
|
1385
1428
|
/** True while any request can still use a model or credential snapshot. */
|
|
1386
1429
|
isModelSettingsBusy() {
|
|
1387
|
-
return this.isRunning() || this.isTeamBusy() || this.
|
|
1430
|
+
return this.isRunning() || this.isTeamBusy() || this.isCompactionBusy()
|
|
1431
|
+
|| this.getCompactionSnapshot().inFlight
|
|
1388
1432
|
|| this.getFastStatus().snapshot.inFlight;
|
|
1389
1433
|
}
|
|
1390
1434
|
/** Monotonic revision protects an editor from all intervening settings changes. */
|
|
1391
1435
|
getSettingsRevision() { return this.settingsRevision; }
|
|
1436
|
+
advanceSettingsRevision() {
|
|
1437
|
+
this.settingsRevision += 1;
|
|
1438
|
+
this.idleCompaction?.abort();
|
|
1439
|
+
}
|
|
1392
1440
|
/** Read-only draft creation; no startup migration or credential copying. */
|
|
1393
1441
|
createModelSettingsDraft() {
|
|
1394
1442
|
return createModelSettingsDraft(this.config, this.settingsRevision);
|
|
@@ -1414,10 +1462,13 @@ export class AgentController {
|
|
|
1414
1462
|
}
|
|
1415
1463
|
/** Apply prepared state synchronously, without rebuilding the controller or its history. */
|
|
1416
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();
|
|
1417
1468
|
this.preparedSystemPrompt = snapshot.systemPrompt;
|
|
1418
1469
|
try {
|
|
1419
1470
|
this.config = snapshot.config;
|
|
1420
|
-
this.
|
|
1471
|
+
this.advanceSettingsRevision();
|
|
1421
1472
|
this.agent.setModel(snapshot.model);
|
|
1422
1473
|
this.agent.setThinkingLevel(snapshot.config.thinkingLevel);
|
|
1423
1474
|
this.agent.setMaxTokens(snapshot.config.maxTokens);
|
|
@@ -1429,7 +1480,12 @@ export class AgentController {
|
|
|
1429
1480
|
}
|
|
1430
1481
|
this.fast?.onConfigChanged(false, snapshot.config.fast.enabled);
|
|
1431
1482
|
this.compaction?.onConfigChanged(snapshot.config.compaction.enabled);
|
|
1432
|
-
|
|
1483
|
+
if (connectionChanged) {
|
|
1484
|
+
this.requestVersion += 1;
|
|
1485
|
+
this.contextMeter.onHistoryReplaced();
|
|
1486
|
+
}
|
|
1487
|
+
else
|
|
1488
|
+
this.contextMeter.onWindowChanged();
|
|
1433
1489
|
this.rebuildSystemPrompt();
|
|
1434
1490
|
}
|
|
1435
1491
|
finally {
|
|
@@ -1451,9 +1507,11 @@ export class AgentController {
|
|
|
1451
1507
|
setModel(provider, model, baseUrl) {
|
|
1452
1508
|
if (this.isModelSettingsBusy())
|
|
1453
1509
|
throw new Error('Model settings are busy.');
|
|
1510
|
+
const connectionChanged = this.config.provider !== provider || this.config.model !== model
|
|
1511
|
+
|| this.config.baseUrl !== baseUrl;
|
|
1454
1512
|
if (this.modelMetadataEnabled)
|
|
1455
1513
|
this.resetModelMetadataRequest();
|
|
1456
|
-
this.
|
|
1514
|
+
this.advanceSettingsRevision();
|
|
1457
1515
|
this.config = { ...this.config, provider, model, baseUrl };
|
|
1458
1516
|
this.config.modelProfileState = resolveModelProfileState(this.config);
|
|
1459
1517
|
this.agent.setModel({ providerId: provider, modelId: model, ...(baseUrl ? { baseUrl } : {}) });
|
|
@@ -1468,30 +1526,29 @@ export class AgentController {
|
|
|
1468
1526
|
// so the snapshot the chip and `/compact status` read has to be refreshed
|
|
1469
1527
|
// for the same reason and at the same moments (the RV-3 lesson).
|
|
1470
1528
|
this.compaction?.onConfigChanged();
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
this.contextMeter.onWindowChanged();
|
|
1529
|
+
if (connectionChanged) {
|
|
1530
|
+
this.requestVersion += 1;
|
|
1531
|
+
this.contextMeter.onHistoryReplaced();
|
|
1532
|
+
}
|
|
1533
|
+
else
|
|
1534
|
+
this.contextMeter.onWindowChanged();
|
|
1478
1535
|
if (this.modelMetadataEnabled)
|
|
1479
1536
|
void this.refreshModelMetadata();
|
|
1480
1537
|
}
|
|
1481
1538
|
setThinkingLevel(level) {
|
|
1482
|
-
this.
|
|
1539
|
+
this.advanceSettingsRevision();
|
|
1483
1540
|
this.config = { ...this.config, thinkingLevel: level };
|
|
1484
1541
|
this.agent.setThinkingLevel(level);
|
|
1485
1542
|
}
|
|
1486
1543
|
setMaxTokens(value) {
|
|
1487
|
-
this.
|
|
1544
|
+
this.advanceSettingsRevision();
|
|
1488
1545
|
this.config = { ...this.config, maxTokens: value };
|
|
1489
1546
|
this.agent.setMaxTokens(value);
|
|
1490
1547
|
}
|
|
1491
1548
|
setApiKey(provider, key) {
|
|
1492
1549
|
// The Agent resolves keys via `resolveKey(this.config)` at call time, so
|
|
1493
1550
|
// updating the config here is sufficient — no Agent rebuild needed.
|
|
1494
|
-
this.
|
|
1551
|
+
this.advanceSettingsRevision();
|
|
1495
1552
|
this.config = {
|
|
1496
1553
|
...this.config,
|
|
1497
1554
|
apiKeys: { ...this.config.apiKeys, [provider]: key },
|
|
@@ -1512,7 +1569,7 @@ export class AgentController {
|
|
|
1512
1569
|
}
|
|
1513
1570
|
setCwd(cwd) {
|
|
1514
1571
|
this.cwd = cwd;
|
|
1515
|
-
this.
|
|
1572
|
+
this.advanceSettingsRevision();
|
|
1516
1573
|
this.config = { ...this.config, cwd };
|
|
1517
1574
|
// The project scope moved with the cwd, so rescan BEFORE rebuilding — then
|
|
1518
1575
|
// let the single prompt entry point emit a prompt that reflects both the new
|
|
@@ -1581,6 +1638,8 @@ export class AgentController {
|
|
|
1581
1638
|
};
|
|
1582
1639
|
}
|
|
1583
1640
|
clearMessages() {
|
|
1641
|
+
this.idleCompaction?.abort();
|
|
1642
|
+
this.restoredCompactionIdentity = undefined;
|
|
1584
1643
|
this.agent.clearMessages();
|
|
1585
1644
|
this.startupSequence += 1;
|
|
1586
1645
|
// INVALIDATION SITE 3 (context-auto-compaction-hardening §3.2.3). The history
|
|
@@ -1608,7 +1667,9 @@ export class AgentController {
|
|
|
1608
1667
|
getSystemPrompt() {
|
|
1609
1668
|
return this.agent.state.systemPrompt;
|
|
1610
1669
|
}
|
|
1611
|
-
replaceMessages(messages) {
|
|
1670
|
+
replaceMessages(messages, identity) {
|
|
1671
|
+
this.idleCompaction?.abort();
|
|
1672
|
+
this.restoredCompactionIdentity = identity;
|
|
1612
1673
|
this.agent.replaceMessages(messages);
|
|
1613
1674
|
this.startupSequence += 1;
|
|
1614
1675
|
// INVALIDATION SITE 4 (§3.2.3) - `/resume`, and any future host-side rewrite.
|
|
@@ -1621,12 +1682,26 @@ export class AgentController {
|
|
|
1621
1682
|
// reading stood until the next completed turn. The DEEP reset also drops
|
|
1622
1683
|
// `estimateOffset`, because `/resume` can change the model in the same
|
|
1623
1684
|
// breath (I-8).
|
|
1624
|
-
this.compaction?.onHistoryReplaced();
|
|
1685
|
+
this.compaction?.onHistoryReplaced(identity);
|
|
1625
1686
|
this.contextMeter.onHistoryReplaced();
|
|
1626
1687
|
}
|
|
1627
1688
|
getMessages() {
|
|
1628
1689
|
return this.agent.state.messages;
|
|
1629
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
|
+
}
|
|
1630
1705
|
clearAllQueues() {
|
|
1631
1706
|
this.agent.clearAllQueues();
|
|
1632
1707
|
this.pendingUserSteering.clear();
|
|
@@ -1726,7 +1801,7 @@ export class AgentController {
|
|
|
1726
1801
|
* `/fast` reports that honestly by checking `isFastRegistered()` first.
|
|
1727
1802
|
*/
|
|
1728
1803
|
setFastEnabled(enabled) {
|
|
1729
|
-
this.
|
|
1804
|
+
this.advanceSettingsRevision();
|
|
1730
1805
|
this.fast?.setEnabled(enabled);
|
|
1731
1806
|
}
|
|
1732
1807
|
getFastConfig() {
|
|
@@ -1742,7 +1817,7 @@ export class AgentController {
|
|
|
1742
1817
|
*/
|
|
1743
1818
|
setFastConfig(patch) {
|
|
1744
1819
|
const fast = clampFastConfig({ ...this.config.fast, ...patch });
|
|
1745
|
-
this.
|
|
1820
|
+
this.advanceSettingsRevision();
|
|
1746
1821
|
this.config = { ...this.config, fast };
|
|
1747
1822
|
// `onConfigChanged` rebuilds the prompt; when there is no wiring (the tier
|
|
1748
1823
|
// was never registered) nothing has to be rebuilt, because no block is
|
|
@@ -1842,7 +1917,7 @@ export class AgentController {
|
|
|
1842
1917
|
* on purpose (§3.2).
|
|
1843
1918
|
*/
|
|
1844
1919
|
setCompactionEnabled(enabled) {
|
|
1845
|
-
this.
|
|
1920
|
+
this.advanceSettingsRevision();
|
|
1846
1921
|
this.compaction?.setEnabled(enabled);
|
|
1847
1922
|
}
|
|
1848
1923
|
getCompactionConfig() {
|
|
@@ -1857,7 +1932,7 @@ export class AgentController {
|
|
|
1857
1932
|
*/
|
|
1858
1933
|
setCompactionConfig(patch) {
|
|
1859
1934
|
const compaction = clampCompactionConfig({ ...this.config.compaction, ...patch });
|
|
1860
|
-
this.
|
|
1935
|
+
this.advanceSettingsRevision();
|
|
1861
1936
|
this.config = { ...this.config, compaction };
|
|
1862
1937
|
this.compaction?.onConfigChanged();
|
|
1863
1938
|
return compaction;
|
|
@@ -1910,18 +1985,80 @@ export class AgentController {
|
|
|
1910
1985
|
return { ok: false, reason: 'settings_restart_required' };
|
|
1911
1986
|
if (!this.compaction)
|
|
1912
1987
|
return { ok: false, reason: 'not_registered' };
|
|
1988
|
+
if (this.isCompactionBusy() || this.isRunning())
|
|
1989
|
+
return { ok: false, reason: 'busy' };
|
|
1913
1990
|
const controller = new AbortController();
|
|
1914
|
-
const
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
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();
|
|
1920
2046
|
});
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
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
|
+
}
|
|
1925
2062
|
}
|
|
1926
2063
|
/**
|
|
1927
2064
|
* Subscribe to the CLI-local compaction event stream.
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* ASCII ONLY — `agent/` is inside the glyph scanner's scope.
|
|
9
9
|
*/
|
|
10
10
|
/** Bump when the wording changes, so a transcript can be dated. */
|
|
11
|
-
export declare const PLAN_MODE_BLOCK_VERSION = "
|
|
11
|
+
export declare const PLAN_MODE_BLOCK_VERSION = "v2-2026-10-08";
|
|
12
12
|
export interface PlanModeBlockOptions {
|
|
13
13
|
/** False under `-p` / piped stdin: no overlay can be rendered, so no tools. */
|
|
14
14
|
interactive: boolean;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* ASCII ONLY — `agent/` is inside the glyph scanner's scope.
|
|
9
9
|
*/
|
|
10
10
|
/** Bump when the wording changes, so a transcript can be dated. */
|
|
11
|
-
export const PLAN_MODE_BLOCK_VERSION = '
|
|
11
|
+
export const PLAN_MODE_BLOCK_VERSION = 'v2-2026-10-08';
|
|
12
12
|
/**
|
|
13
13
|
* The closing paragraph of the interactive block.
|
|
14
14
|
*
|
|
@@ -75,8 +75,9 @@ export function buildPlanModeBlock(options) {
|
|
|
75
75
|
'design mode: you must not change anything on disk and must not run commands.',
|
|
76
76
|
'',
|
|
77
77
|
'Work in this order:',
|
|
78
|
-
'1.
|
|
79
|
-
'
|
|
78
|
+
'1. Follow the project-guidance protocol: read applicable guidance and index navigation',
|
|
79
|
+
' before any source search or read. Then inspect the real code with read_file,',
|
|
80
|
+
' list_dir, glob, grep and relevant skills. Never plan against unread structure.',
|
|
80
81
|
...askStep,
|
|
81
82
|
...submitStep,
|
|
82
83
|
'',
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Shared workspace-orientation protocol; no local content is injected. */
|
|
2
|
+
export declare const PROJECT_GUIDANCE_BLOCK_VERSION = "v5-2026-10-08";
|
|
3
|
+
/** Return the versioned English protocol without performing discovery or IO. */
|
|
4
|
+
export declare function buildProjectGuidanceBlock(): string;
|