@aragon-agent/cli 0.6.11 → 0.6.13
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 +10 -1
- package/README.md +97 -20
- package/dist/agent/controller.d.ts +38 -3
- package/dist/agent/controller.js +151 -60
- package/dist/agent/model-profile-settings.d.ts +31 -0
- package/dist/agent/model-profile-settings.js +199 -0
- package/dist/agent/queued-messages.d.ts +20 -0
- package/dist/agent/queued-messages.js +46 -0
- package/dist/agent/reducer.d.ts +21 -2
- package/dist/agent/reducer.js +65 -37
- package/dist/cli.js +15 -1
- package/dist/commands/builtins.js +83 -42
- package/dist/commands/queue-command.d.ts +3 -0
- package/dist/commands/queue-command.js +5 -0
- package/dist/commands/registry.d.ts +3 -0
- package/dist/compaction/child.d.ts +4 -2
- package/dist/compaction/child.js +4 -1
- package/dist/compaction/compactor.d.ts +7 -2
- package/dist/compaction/compactor.js +13 -11
- package/dist/compaction/context-command.js +5 -0
- package/dist/compaction/wiring.d.ts +4 -3
- package/dist/compaction/wiring.js +6 -1
- package/dist/config/cli-commands.d.ts +2 -0
- package/dist/config/cli-commands.js +16 -4
- package/dist/config/load.d.ts +2 -1
- package/dist/config/load.js +15 -8
- package/dist/config/model-profile-resolution.d.ts +20 -0
- package/dist/config/model-profile-resolution.js +81 -0
- package/dist/config/model-profile-store.d.ts +67 -0
- package/dist/config/model-profile-store.js +153 -0
- package/dist/config/model-profiles.d.ts +44 -0
- package/dist/config/model-profiles.js +121 -0
- package/dist/config/schema.d.ts +9 -0
- package/dist/config/store.d.ts +11 -1
- package/dist/config/store.js +43 -27
- package/dist/fast/resolve.d.ts +2 -1
- package/dist/fast/resolve.js +6 -4
- package/dist/fast/wiring.d.ts +5 -3
- package/dist/fast/wiring.js +8 -4
- package/dist/input/enter-sequences.d.ts +11 -10
- package/dist/input/enter-sequences.js +29 -27
- package/dist/input/stdin-filter.js +93 -28
- package/dist/logging/install.d.ts +3 -0
- package/dist/logging/install.js +4 -2
- package/dist/logging/secret-registry.d.ts +2 -0
- package/dist/logging/secret-registry.js +18 -0
- package/dist/session/persist.d.ts +6 -7
- package/dist/session/persist.js +6 -9
- package/dist/session/store.d.ts +4 -1
- package/dist/session/validate-session.d.ts +3 -0
- package/dist/session/validate-session.js +207 -0
- package/dist/team/runtime.d.ts +2 -1
- package/dist/team/subagent.d.ts +3 -1
- package/dist/team/subagent.js +3 -1
- package/dist/ui/ActivityLine.d.ts +6 -70
- package/dist/ui/ActivityLine.js +12 -34
- package/dist/ui/App.d.ts +1 -0
- package/dist/ui/App.js +161 -226
- package/dist/ui/BottomStatusRow.d.ts +6 -86
- package/dist/ui/BottomStatusRow.js +11 -26
- package/dist/ui/Composer.d.ts +18 -84
- package/dist/ui/Composer.js +9 -100
- package/dist/ui/PromptInput.d.ts +6 -2
- package/dist/ui/PromptInput.js +91 -105
- package/dist/ui/QueuePanel.d.ts +8 -0
- package/dist/ui/QueuePanel.js +9 -0
- package/dist/ui/QueueStatusRow.d.ts +24 -0
- package/dist/ui/QueueStatusRow.js +51 -0
- package/dist/ui/StatusBar.d.ts +15 -149
- package/dist/ui/StatusBar.js +27 -112
- package/dist/ui/clipboard-task.d.ts +29 -0
- package/dist/ui/clipboard-task.js +66 -0
- package/dist/ui/clipboard.d.ts +29 -50
- package/dist/ui/clipboard.js +141 -75
- package/dist/ui/composer-input.d.ts +28 -0
- package/dist/ui/composer-input.js +46 -0
- package/dist/ui/composer-limits.d.ts +3 -4
- package/dist/ui/composer-limits.js +4 -9
- package/dist/ui/composer-rows.d.ts +1 -1
- package/dist/ui/composer-rows.js +24 -137
- package/dist/ui/editor-navigation.d.ts +45 -0
- package/dist/ui/editor-navigation.js +85 -0
- package/dist/ui/editor-reducer.d.ts +9 -0
- package/dist/ui/editor-reducer.js +31 -15
- package/dist/ui/enter-frames.d.ts +15 -31
- package/dist/ui/enter-frames.js +14 -33
- package/dist/ui/interaction-copy.d.ts +77 -0
- package/dist/ui/interaction-copy.js +40 -0
- package/dist/ui/interaction-hints.d.ts +18 -0
- package/dist/ui/interaction-hints.js +92 -0
- package/dist/ui/layout/budget.js +1 -1
- package/dist/ui/layout/queue-layout.d.ts +19 -0
- package/dist/ui/layout/queue-layout.js +26 -0
- package/dist/ui/layout/status-layout.d.ts +55 -0
- package/dist/ui/layout/status-layout.js +109 -0
- package/dist/ui/layout/todo-layout.d.ts +1 -0
- package/dist/ui/layout/todo-layout.js +1 -1
- package/dist/ui/overlays/HelpOverlay.js +11 -3
- package/dist/ui/overlays/ModelProfileEditor.d.ts +12 -0
- package/dist/ui/overlays/ModelProfileEditor.js +103 -0
- package/dist/ui/overlays/ModelProfilePicker.d.ts +35 -0
- package/dist/ui/overlays/ModelProfilePicker.js +167 -0
- package/dist/ui/overlays/QueueOverlay.d.ts +22 -0
- package/dist/ui/overlays/QueueOverlay.js +79 -0
- package/dist/ui/overlays/SettingsScreen.d.ts +9 -2
- package/dist/ui/overlays/SettingsScreen.js +347 -3
- package/dist/ui/overlays/model-profile-state.d.ts +74 -0
- package/dist/ui/overlays/model-profile-state.js +119 -0
- package/dist/ui/run-status-row.d.ts +7 -19
- package/dist/ui/run-status-row.js +2 -18
- package/dist/ui/selection/selection-controller.d.ts +2 -2
- package/dist/ui/selection/selection-controller.js +58 -20
- package/dist/ui/transcript-text.js +20 -12
- package/dist/ui/use-wheel-routing.js +2 -1
- package/package.json +2 -2
package/dist/agent/controller.js
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* exposes a small run/abort/steer surface plus the pre-flight validation that
|
|
4
|
-
* turns the most common failure (no API key) into a guided action instead of a
|
|
5
|
-
* blank screen (spec §3.3.1 / R2).
|
|
6
|
-
*
|
|
7
|
-
* The controller never inspects the resolved promise of `agent.prompt()` for
|
|
8
|
-
* success — that promise resolves even on failure. Success/failure is derived
|
|
9
|
-
* from the event stream by the reducer (TUI) and the headless writer.
|
|
10
|
-
*/
|
|
1
|
+
import { createModelSettingsDraft, saveModelSettings, reloadModelSettings, } from './model-profile-settings.js';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
11
3
|
import { Agent, initProviders, ModelRegistry, } from '@aragon-agent/core';
|
|
12
4
|
import { clampCompactionConfig, clampFastConfig, clampRetryConfig, clampTeamConfig, clampTodoConfig, isAdapterProvider, toRetryPolicy, } from '../config/schema.js';
|
|
5
|
+
import { resolveModelProfileState } from '../config/model-profile-resolution.js';
|
|
13
6
|
import { makeGetApiKey } from '../config/load.js';
|
|
14
7
|
import { updatePersistedConfig } from '../config/store.js';
|
|
15
8
|
import { createBuiltinTools } from '../tools/index.js';
|
|
@@ -203,6 +196,9 @@ export class AgentController {
|
|
|
203
196
|
// no timer while it has no subscribers, so the extra object is a few scalars
|
|
204
197
|
// and a `Set`.
|
|
205
198
|
// -----------------------------------------------------------------------
|
|
199
|
+
settingsRevision = 0;
|
|
200
|
+
modelSettingsBlocked = false;
|
|
201
|
+
preparedSystemPrompt;
|
|
206
202
|
contextMeter;
|
|
207
203
|
// -----------------------------------------------------------------------
|
|
208
204
|
// Background services (background-service-supervision §3.3 / §3.6)
|
|
@@ -252,8 +248,11 @@ export class AgentController {
|
|
|
252
248
|
* Cleared at the next `agent_start`.
|
|
253
249
|
*/
|
|
254
250
|
abortRequested = false;
|
|
255
|
-
/**
|
|
256
|
-
|
|
251
|
+
/** Only exact acceptance receipts release user messages from reviewer protection. */
|
|
252
|
+
pendingUserSteering = new Set();
|
|
253
|
+
/** Session changes must not make a delayed receipt match a new message. */
|
|
254
|
+
steeringPrefix = randomUUID();
|
|
255
|
+
steeringSequence = 0;
|
|
257
256
|
constructor(config, deps = {}) {
|
|
258
257
|
this.config = config;
|
|
259
258
|
this.cwd = config.cwd;
|
|
@@ -303,7 +302,7 @@ export class AgentController {
|
|
|
303
302
|
// ONCE (C-2). `FastWiring` re-runs the same pure function in its own
|
|
304
303
|
// constructor; the two agree by construction because they read the same
|
|
305
304
|
// config through the same resolver.
|
|
306
|
-
const fastTier = resolveFastTier(config, (id) => this.hasApiKey(id));
|
|
305
|
+
const fastTier = resolveFastTier(config, (id, role) => this.hasApiKey(id, role));
|
|
307
306
|
const fastRegistered = fastTier.ok;
|
|
308
307
|
// --- Team mode ---------------------------------------------------------
|
|
309
308
|
this.teamRegistered = config.team.enabled;
|
|
@@ -327,11 +326,12 @@ export class AgentController {
|
|
|
327
326
|
...(this.config.baseUrl ? { baseUrl: this.config.baseUrl } : {}),
|
|
328
327
|
},
|
|
329
328
|
thinkingLevel: this.config.thinkingLevel,
|
|
329
|
+
role: 'main',
|
|
330
330
|
},
|
|
331
331
|
providerRegistry: this.providerRegistry,
|
|
332
332
|
getCwd: () => this.cwd,
|
|
333
333
|
getMode: () => this.effectiveMode,
|
|
334
|
-
getApiKey: (id) => this.resolveKey(id),
|
|
334
|
+
getApiKey: (id, role) => this.resolveKey(id, role),
|
|
335
335
|
// `skill_find` only, and the lead's always-on bodies. See the long
|
|
336
336
|
// note in `subagent.ts` for why `skill` itself is withheld (D-16).
|
|
337
337
|
...(this.skillsEnabled
|
|
@@ -551,8 +551,8 @@ export class AgentController {
|
|
|
551
551
|
this.compaction = config.compaction.enabled
|
|
552
552
|
? new CompactionWiring({
|
|
553
553
|
getConfig: () => this.config,
|
|
554
|
-
hasKey: (id) => this.hasApiKey(id),
|
|
555
|
-
getApiKey: (id) => this.resolveKey(id),
|
|
554
|
+
hasKey: (id, role) => this.hasApiKey(id, role),
|
|
555
|
+
getApiKey: (id, role) => this.resolveKey(id, role),
|
|
556
556
|
getModelInfoFor: (ref) => this.getModelInfoFor(ref),
|
|
557
557
|
isPricedModel: (ref) => this.isPricedModel(ref),
|
|
558
558
|
getMessages: () => this.agent.state.messages,
|
|
@@ -629,38 +629,31 @@ export class AgentController {
|
|
|
629
629
|
this.fast = fastRegistered
|
|
630
630
|
? new FastWiring({
|
|
631
631
|
getConfig: () => this.config,
|
|
632
|
-
hasKey: (id) => this.hasApiKey(id),
|
|
633
|
-
getApiKey: (id) => this.resolveKey(id),
|
|
632
|
+
hasKey: (id, role) => this.hasApiKey(id, role),
|
|
633
|
+
getApiKey: (id, role) => this.resolveKey(id, role),
|
|
634
634
|
isPricedModel: (ref) => this.isPricedModel(ref),
|
|
635
635
|
subscribe: (listener) => this.agent.subscribe(listener),
|
|
636
636
|
complete: (providerId, request) => this.providerRegistry.complete(providerId, request),
|
|
637
|
-
//
|
|
638
|
-
|
|
639
|
-
// `userSteerCount` would otherwise mis-report and what would make
|
|
640
|
-
// guard 2 clear a queue it does not own (D-21).
|
|
641
|
-
//
|
|
642
|
-
// It is decremented back out below: the counter must answer "did the
|
|
643
|
-
// USER queue anything", and the reviewer's own message is not that.
|
|
644
|
-
steer: (text) => {
|
|
645
|
-
this.steer(text);
|
|
646
|
-
this.userSteerCount -= 1;
|
|
647
|
-
},
|
|
637
|
+
// Reviewer advice shares skill activation, but cannot own a user ID.
|
|
638
|
+
steer: (text) => this.enqueueSteering(text, false),
|
|
648
639
|
isRunning: () => this.isRunning(),
|
|
649
640
|
isAbortRequested: () => this.abortRequested,
|
|
650
|
-
userSteerCount: () => this.
|
|
641
|
+
userSteerCount: () => this.pendingUserSteering.size,
|
|
651
642
|
clearAllQueues: () => this.clearAllQueues(),
|
|
652
643
|
notify: (level, text) => deps.notify?.(level, text),
|
|
653
644
|
onPromptChanged: () => this.rebuildSystemPrompt(),
|
|
654
645
|
})
|
|
655
646
|
: null;
|
|
656
647
|
// The two guards' bookkeeping, and the only reason this class subscribes to
|
|
657
|
-
// its own agent.
|
|
648
|
+
// its own agent. A receipt is where a user steer stops being pending;
|
|
658
649
|
// `agent_start` is where a requested abort stops being in force.
|
|
659
650
|
this.agent.subscribe((event) => {
|
|
660
651
|
if (event.type === 'agent_start')
|
|
661
652
|
this.abortRequested = false;
|
|
662
|
-
if (event.type === '
|
|
663
|
-
|
|
653
|
+
if (event.type === 'steering_accepted') {
|
|
654
|
+
for (const id of event.ids)
|
|
655
|
+
this.pendingUserSteering.delete(id);
|
|
656
|
+
}
|
|
664
657
|
// The authoritative result arrives with this event, so from this instant
|
|
665
658
|
// the tail is superseded and its slot must be released (§3.1.1). Clearing
|
|
666
659
|
// HERE rather than on read is what keeps the store's size a function of
|
|
@@ -686,8 +679,8 @@ export class AgentController {
|
|
|
686
679
|
if (problem)
|
|
687
680
|
deps.notify?.('warn', problem);
|
|
688
681
|
}
|
|
689
|
-
resolveKey(providerId) {
|
|
690
|
-
return makeGetApiKey(this.config)(providerId);
|
|
682
|
+
resolveKey(providerId, role = 'main') {
|
|
683
|
+
return makeGetApiKey(this.config, role)(providerId);
|
|
691
684
|
}
|
|
692
685
|
/**
|
|
693
686
|
* The tool names registered right now — LAZY, and the laziness is required.
|
|
@@ -718,27 +711,30 @@ export class AgentController {
|
|
|
718
711
|
// suddenly denies having any skills). Structure, not call ordering, is what
|
|
719
712
|
// rules that out — so if you add a third caller, route it through here.
|
|
720
713
|
// -----------------------------------------------------------------------
|
|
721
|
-
composeSystemPrompt() {
|
|
714
|
+
composeSystemPrompt(config = this.config) {
|
|
715
|
+
const tier = resolveFastTier(config, (id, role) => !!makeGetApiKey(config, role)(id));
|
|
716
|
+
const fastAvailable = config === this.config ? this.fast?.available() === true
|
|
717
|
+
: this.fast?.isRegistered() && config.fast.enabled && tier.ok;
|
|
722
718
|
return buildSystemPrompt({
|
|
723
719
|
cwd: this.cwd,
|
|
724
720
|
tools: this.tools,
|
|
725
721
|
skillsBlock: this.skillsEnabled ? this.skills.catalogBlock() + this.skills.alwaysBlock() : '',
|
|
726
722
|
agentMode: this.effectiveMode,
|
|
727
723
|
planInteractive: this.planInteractive,
|
|
728
|
-
planMaxAskRounds:
|
|
724
|
+
planMaxAskRounds: config.planModeMaxAskRounds,
|
|
729
725
|
// BOTH flags, and both are required (§4.5). `teamRegistered` false means
|
|
730
726
|
// there is no `task` tool to advertise; `teamEnabled` false means the user
|
|
731
727
|
// turned it off for this session. Advertising in either case would
|
|
732
728
|
// describe a capability the model does not have.
|
|
733
729
|
teamBlock: this.teamRegistered && this.teamEnabled
|
|
734
730
|
? buildTeamBlock({
|
|
735
|
-
maxSubagents:
|
|
736
|
-
maxConcurrent:
|
|
731
|
+
maxSubagents: config.team.maxSubagents,
|
|
732
|
+
maxConcurrent: config.team.maxConcurrent,
|
|
737
733
|
// One cross-reference sentence, present only when the capability
|
|
738
734
|
// is (§3.8). `this.fast` is null during the FIRST compose (the
|
|
739
735
|
// wiring needs the Agent), which is why the constructor composes
|
|
740
736
|
// again once it exists.
|
|
741
|
-
fastDelegation:
|
|
737
|
+
fastDelegation: !!fastAvailable && config.fast.delegate,
|
|
742
738
|
})
|
|
743
739
|
: '',
|
|
744
740
|
// BOTH FLAGS, exactly as team mode uses both, and for the identical
|
|
@@ -748,18 +744,18 @@ export class AgentController {
|
|
|
748
744
|
// model does not have.
|
|
749
745
|
todoBlock: this.todoRegistered && this.todoEnabled
|
|
750
746
|
? buildTodoBlock({
|
|
751
|
-
panelVisible: this.todoPanelCapable &&
|
|
747
|
+
panelVisible: this.todoPanelCapable && config.todo.panel,
|
|
752
748
|
})
|
|
753
749
|
: '',
|
|
754
750
|
// The SAME two flags one more time, plus the live resolution — all three
|
|
755
751
|
// live inside `FastWiring.available()` (§3.3). `buildFastBlock` itself
|
|
756
752
|
// returns `''` when neither capability is on, so a user who turned both
|
|
757
753
|
// off pays nothing for the tier being technically live.
|
|
758
|
-
fastBlock:
|
|
754
|
+
fastBlock: fastAvailable
|
|
759
755
|
? buildFastBlock({
|
|
760
|
-
model:
|
|
761
|
-
delegate:
|
|
762
|
-
review:
|
|
756
|
+
model: tier.ok ? tier.ref.modelId : '',
|
|
757
|
+
delegate: config.fast.delegate,
|
|
758
|
+
review: config.fast.review,
|
|
763
759
|
})
|
|
764
760
|
: '',
|
|
765
761
|
// ONE FLAG, unlike team/todo's two: there is no live on/off switch for
|
|
@@ -775,7 +771,7 @@ export class AgentController {
|
|
|
775
771
|
});
|
|
776
772
|
}
|
|
777
773
|
rebuildSystemPrompt() {
|
|
778
|
-
this.agent.setSystemPrompt(this.composeSystemPrompt());
|
|
774
|
+
this.agent.setSystemPrompt(this.preparedSystemPrompt ?? this.composeSystemPrompt());
|
|
779
775
|
}
|
|
780
776
|
/** Re-render the prompt from the current skill set and tell the UI. */
|
|
781
777
|
refreshSkills() {
|
|
@@ -794,8 +790,10 @@ export class AgentController {
|
|
|
794
790
|
* Read-only CLI-layer helper for the Header/Welcome key-status dot (P1-1);
|
|
795
791
|
* does not touch `@aragon-agent/core`.
|
|
796
792
|
*/
|
|
797
|
-
hasApiKey(provider) {
|
|
798
|
-
const
|
|
793
|
+
hasApiKey(provider, role = 'main') {
|
|
794
|
+
const activeProvider = role === 'fast'
|
|
795
|
+
? this.config.fast.provider || this.config.provider : this.config.provider;
|
|
796
|
+
const key = this.resolveKey(provider ?? activeProvider, role);
|
|
799
797
|
return !!key && key.trim().length > 0;
|
|
800
798
|
}
|
|
801
799
|
/**
|
|
@@ -804,6 +802,7 @@ export class AgentController {
|
|
|
804
802
|
* re-memoizes the palette (P1-1). Does not touch `@aragon-agent/core`.
|
|
805
803
|
*/
|
|
806
804
|
setTheme(name) {
|
|
805
|
+
this.settingsRevision += 1;
|
|
807
806
|
this.config = { ...this.config, theme: name };
|
|
808
807
|
}
|
|
809
808
|
/**
|
|
@@ -824,6 +823,9 @@ export class AgentController {
|
|
|
824
823
|
// Pre-flight (R2): validate BEFORE entering the loop.
|
|
825
824
|
// -----------------------------------------------------------------------
|
|
826
825
|
preflight() {
|
|
826
|
+
if (this.modelSettingsBlocked)
|
|
827
|
+
return { ok: false, kind: 'config',
|
|
828
|
+
message: 'Settings were saved but not fully applied. Restart before sending requests.' };
|
|
827
829
|
const provider = this.config.provider;
|
|
828
830
|
if (!isAdapterProvider(provider)) {
|
|
829
831
|
return {
|
|
@@ -858,6 +860,9 @@ export class AgentController {
|
|
|
858
860
|
*/
|
|
859
861
|
async prompt(text, options = {}) {
|
|
860
862
|
// Capture BEFORE stopping: synchronous abort listeners may cancel this request.
|
|
863
|
+
if (this.modelSettingsBlocked) {
|
|
864
|
+
return { status: 'not-started', reason: 'failed' };
|
|
865
|
+
}
|
|
861
866
|
const request = ++this.startupSequence;
|
|
862
867
|
try {
|
|
863
868
|
if (this.agent.state.isRunning) {
|
|
@@ -1052,21 +1057,30 @@ export class AgentController {
|
|
|
1052
1057
|
reapServicesSync() {
|
|
1053
1058
|
this.procs.reapSync();
|
|
1054
1059
|
}
|
|
1060
|
+
/** Queue automation/user guidance with the same protection as visible input. */
|
|
1055
1061
|
steer(text) {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1062
|
+
this.enqueueSteering(text, true);
|
|
1063
|
+
}
|
|
1064
|
+
/** Return the opaque ID used by the view to match Core's acceptance receipt. */
|
|
1065
|
+
queueUserMessage(text) {
|
|
1066
|
+
return this.enqueueSteering(text, true);
|
|
1067
|
+
}
|
|
1068
|
+
enqueueSteering(text, user) {
|
|
1069
|
+
// Both reviewer and user guidance can activate queued skill frames.
|
|
1060
1070
|
if (this.skillsEnabled)
|
|
1061
1071
|
this.skills.absorbPendingFrames();
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1072
|
+
const id = user ? `${this.steeringPrefix}:${++this.steeringSequence}` : undefined;
|
|
1073
|
+
if (id !== undefined)
|
|
1074
|
+
this.pendingUserSteering.add(id);
|
|
1075
|
+
try {
|
|
1076
|
+
this.agent.steer(text, id);
|
|
1077
|
+
}
|
|
1078
|
+
catch (error) {
|
|
1079
|
+
if (id !== undefined)
|
|
1080
|
+
this.pendingUserSteering.delete(id);
|
|
1081
|
+
throw error;
|
|
1082
|
+
}
|
|
1083
|
+
return id;
|
|
1070
1084
|
}
|
|
1071
1085
|
followUp(text) {
|
|
1072
1086
|
this.agent.followUp(text);
|
|
@@ -1203,6 +1217,7 @@ export class AgentController {
|
|
|
1203
1217
|
* honestly by checking `isTeamRegistered()` first.
|
|
1204
1218
|
*/
|
|
1205
1219
|
setTeamEnabled(enabled) {
|
|
1220
|
+
this.settingsRevision += 1;
|
|
1206
1221
|
this.teamEnabled = enabled;
|
|
1207
1222
|
this.rebuildSystemPrompt();
|
|
1208
1223
|
}
|
|
@@ -1216,6 +1231,7 @@ export class AgentController {
|
|
|
1216
1231
|
*/
|
|
1217
1232
|
setTeamConfig(patch) {
|
|
1218
1233
|
const team = clampTeamConfig({ ...this.config.team, ...patch });
|
|
1234
|
+
this.settingsRevision += 1;
|
|
1219
1235
|
this.config = { ...this.config, team };
|
|
1220
1236
|
this.rebuildSystemPrompt();
|
|
1221
1237
|
return team;
|
|
@@ -1277,6 +1293,7 @@ export class AgentController {
|
|
|
1277
1293
|
* reports that honestly by checking `isTodoRegistered()` first.
|
|
1278
1294
|
*/
|
|
1279
1295
|
setTodoEnabled(enabled) {
|
|
1296
|
+
this.settingsRevision += 1;
|
|
1280
1297
|
this.todoEnabled = enabled;
|
|
1281
1298
|
this.rebuildSystemPrompt();
|
|
1282
1299
|
}
|
|
@@ -1299,6 +1316,7 @@ export class AgentController {
|
|
|
1299
1316
|
*/
|
|
1300
1317
|
setTodoConfig(patch) {
|
|
1301
1318
|
const todo = clampTodoConfig({ ...this.config.todo, ...patch });
|
|
1319
|
+
this.settingsRevision += 1;
|
|
1302
1320
|
this.config = { ...this.config, todo };
|
|
1303
1321
|
this.rebuildSystemPrompt();
|
|
1304
1322
|
return todo;
|
|
@@ -1354,6 +1372,7 @@ export class AgentController {
|
|
|
1354
1372
|
*/
|
|
1355
1373
|
setRetryConfig(patch) {
|
|
1356
1374
|
const retry = clampRetryConfig({ ...this.config.retry, ...patch });
|
|
1375
|
+
this.settingsRevision += 1;
|
|
1357
1376
|
this.config = { ...this.config, retry };
|
|
1358
1377
|
this.providerRegistry.setRetryPolicy(toRetryPolicy(retry));
|
|
1359
1378
|
return retry;
|
|
@@ -1361,8 +1380,69 @@ export class AgentController {
|
|
|
1361
1380
|
// -----------------------------------------------------------------------
|
|
1362
1381
|
// Mutators
|
|
1363
1382
|
// -----------------------------------------------------------------------
|
|
1383
|
+
/** True while any request can still use a model or credential snapshot. */
|
|
1384
|
+
isModelSettingsBusy() {
|
|
1385
|
+
return this.isRunning() || this.isTeamBusy() || this.getCompactionSnapshot().inFlight
|
|
1386
|
+
|| this.getFastStatus().snapshot.inFlight;
|
|
1387
|
+
}
|
|
1388
|
+
/** Monotonic revision protects an editor from all intervening settings changes. */
|
|
1389
|
+
getSettingsRevision() { return this.settingsRevision; }
|
|
1390
|
+
/** Read-only draft creation; no startup migration or credential copying. */
|
|
1391
|
+
createModelSettingsDraft() {
|
|
1392
|
+
return createModelSettingsDraft(this.config, this.settingsRevision);
|
|
1393
|
+
}
|
|
1394
|
+
/** Save an explicit draft as a single disk/live transaction. */
|
|
1395
|
+
saveModelSettings(draft) {
|
|
1396
|
+
return saveModelSettings({ draft, controller: this });
|
|
1397
|
+
}
|
|
1398
|
+
/** Legacy commands share the same lossless conversion and atomic save path. */
|
|
1399
|
+
saveModelSettingsPatch(patch) {
|
|
1400
|
+
const draft = this.createModelSettingsDraft();
|
|
1401
|
+
draft.profiles = undefined;
|
|
1402
|
+
draft.patch = patch;
|
|
1403
|
+
return this.saveModelSettings(draft);
|
|
1404
|
+
}
|
|
1405
|
+
/** Refresh both role connections and credentials while retaining conversation state. */
|
|
1406
|
+
reloadModelSettings() { return reloadModelSettings(this); }
|
|
1407
|
+
/** Prebuild all potentially failing model/prompt work before committing the file. */
|
|
1408
|
+
prepareModelSettingsSnapshot(config) {
|
|
1409
|
+
return { config, model: { providerId: config.provider, modelId: config.model,
|
|
1410
|
+
...(config.baseUrl ? { baseUrl: config.baseUrl } : {}) },
|
|
1411
|
+
systemPrompt: this.composeSystemPrompt(config) };
|
|
1412
|
+
}
|
|
1413
|
+
/** Apply prepared state synchronously, without rebuilding the controller or its history. */
|
|
1414
|
+
applyModelSettingsSnapshot(snapshot) {
|
|
1415
|
+
this.preparedSystemPrompt = snapshot.systemPrompt;
|
|
1416
|
+
try {
|
|
1417
|
+
this.config = snapshot.config;
|
|
1418
|
+
this.settingsRevision += 1;
|
|
1419
|
+
this.agent.setModel(snapshot.model);
|
|
1420
|
+
this.agent.setThinkingLevel(snapshot.config.thinkingLevel);
|
|
1421
|
+
this.agent.setMaxTokens(snapshot.config.maxTokens);
|
|
1422
|
+
this.fast?.onConfigChanged(false, snapshot.config.fast.enabled);
|
|
1423
|
+
this.compaction?.onConfigChanged(snapshot.config.compaction.enabled);
|
|
1424
|
+
this.contextMeter.onWindowChanged();
|
|
1425
|
+
this.rebuildSystemPrompt();
|
|
1426
|
+
}
|
|
1427
|
+
finally {
|
|
1428
|
+
this.preparedSystemPrompt = undefined;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
/** Potentially failing ordinary-setting side effects are isolated from model application. */
|
|
1432
|
+
applyModelSettingsEffects() { getLogger().reconfigure(this.config.log); }
|
|
1433
|
+
/** A partially applied model snapshot must never reach another provider request. */
|
|
1434
|
+
blockModelSettingsRequests() {
|
|
1435
|
+
this.modelSettingsBlocked = true;
|
|
1436
|
+
this.config.modelSettingsRestartRequired = true;
|
|
1437
|
+
}
|
|
1438
|
+
/** The UI uses this before accepting or draining queued prompts. */
|
|
1439
|
+
areModelSettingsBlocked() { return this.modelSettingsBlocked; }
|
|
1364
1440
|
setModel(provider, model, baseUrl) {
|
|
1441
|
+
if (this.isModelSettingsBusy())
|
|
1442
|
+
throw new Error('Model settings are busy.');
|
|
1443
|
+
this.settingsRevision += 1;
|
|
1365
1444
|
this.config = { ...this.config, provider, model, baseUrl };
|
|
1445
|
+
this.config.modelProfileState = resolveModelProfileState(this.config);
|
|
1366
1446
|
this.agent.setModel({ providerId: provider, modelId: model, ...(baseUrl ? { baseUrl } : {}) });
|
|
1367
1447
|
// NOT NEUTRAL TO THE FAST TIER even when the user never touched a `fast.*`
|
|
1368
1448
|
// key (§3.9 / RV-3): `fast.provider: ''` and `fast.baseUrl: ''` INHERIT from
|
|
@@ -1384,16 +1464,19 @@ export class AgentController {
|
|
|
1384
1464
|
this.contextMeter.onWindowChanged();
|
|
1385
1465
|
}
|
|
1386
1466
|
setThinkingLevel(level) {
|
|
1467
|
+
this.settingsRevision += 1;
|
|
1387
1468
|
this.config = { ...this.config, thinkingLevel: level };
|
|
1388
1469
|
this.agent.setThinkingLevel(level);
|
|
1389
1470
|
}
|
|
1390
1471
|
setMaxTokens(value) {
|
|
1472
|
+
this.settingsRevision += 1;
|
|
1391
1473
|
this.config = { ...this.config, maxTokens: value };
|
|
1392
1474
|
this.agent.setMaxTokens(value);
|
|
1393
1475
|
}
|
|
1394
1476
|
setApiKey(provider, key) {
|
|
1395
1477
|
// The Agent resolves keys via `resolveKey(this.config)` at call time, so
|
|
1396
1478
|
// updating the config here is sufficient — no Agent rebuild needed.
|
|
1479
|
+
this.settingsRevision += 1;
|
|
1397
1480
|
this.config = {
|
|
1398
1481
|
...this.config,
|
|
1399
1482
|
apiKeys: { ...this.config.apiKeys, [provider]: key },
|
|
@@ -1408,6 +1491,7 @@ export class AgentController {
|
|
|
1408
1491
|
}
|
|
1409
1492
|
setCwd(cwd) {
|
|
1410
1493
|
this.cwd = cwd;
|
|
1494
|
+
this.settingsRevision += 1;
|
|
1411
1495
|
this.config = { ...this.config, cwd };
|
|
1412
1496
|
// The project scope moved with the cwd, so rescan BEFORE rebuilding — then
|
|
1413
1497
|
// let the single prompt entry point emit a prompt that reflects both the new
|
|
@@ -1524,6 +1608,7 @@ export class AgentController {
|
|
|
1524
1608
|
}
|
|
1525
1609
|
clearAllQueues() {
|
|
1526
1610
|
this.agent.clearAllQueues();
|
|
1611
|
+
this.pendingUserSteering.clear();
|
|
1527
1612
|
}
|
|
1528
1613
|
// -----------------------------------------------------------------------
|
|
1529
1614
|
// Model / provider info
|
|
@@ -1591,6 +1676,7 @@ export class AgentController {
|
|
|
1591
1676
|
* `/fast` reports that honestly by checking `isFastRegistered()` first.
|
|
1592
1677
|
*/
|
|
1593
1678
|
setFastEnabled(enabled) {
|
|
1679
|
+
this.settingsRevision += 1;
|
|
1594
1680
|
this.fast?.setEnabled(enabled);
|
|
1595
1681
|
}
|
|
1596
1682
|
getFastConfig() {
|
|
@@ -1606,6 +1692,7 @@ export class AgentController {
|
|
|
1606
1692
|
*/
|
|
1607
1693
|
setFastConfig(patch) {
|
|
1608
1694
|
const fast = clampFastConfig({ ...this.config.fast, ...patch });
|
|
1695
|
+
this.settingsRevision += 1;
|
|
1609
1696
|
this.config = { ...this.config, fast };
|
|
1610
1697
|
// `onConfigChanged` rebuilds the prompt; when there is no wiring (the tier
|
|
1611
1698
|
// was never registered) nothing has to be rebuilt, because no block is
|
|
@@ -1705,6 +1792,7 @@ export class AgentController {
|
|
|
1705
1792
|
* on purpose (§3.2).
|
|
1706
1793
|
*/
|
|
1707
1794
|
setCompactionEnabled(enabled) {
|
|
1795
|
+
this.settingsRevision += 1;
|
|
1708
1796
|
this.compaction?.setEnabled(enabled);
|
|
1709
1797
|
}
|
|
1710
1798
|
getCompactionConfig() {
|
|
@@ -1719,6 +1807,7 @@ export class AgentController {
|
|
|
1719
1807
|
*/
|
|
1720
1808
|
setCompactionConfig(patch) {
|
|
1721
1809
|
const compaction = clampCompactionConfig({ ...this.config.compaction, ...patch });
|
|
1810
|
+
this.settingsRevision += 1;
|
|
1722
1811
|
this.config = { ...this.config, compaction };
|
|
1723
1812
|
this.compaction?.onConfigChanged();
|
|
1724
1813
|
return compaction;
|
|
@@ -1767,6 +1856,8 @@ export class AgentController {
|
|
|
1767
1856
|
* unbypassable. `replaceMessages` validates NOTHING.
|
|
1768
1857
|
*/
|
|
1769
1858
|
async compactNow(instructions) {
|
|
1859
|
+
if (this.modelSettingsBlocked)
|
|
1860
|
+
return { ok: false, reason: 'settings_restart_required' };
|
|
1770
1861
|
if (!this.compaction)
|
|
1771
1862
|
return { ok: false, reason: 'not_registered' };
|
|
1772
1863
|
const controller = new AbortController();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ModelRef } from '@aragon-agent/core';
|
|
2
|
+
import { type CliConfig } from '../config/schema.js';
|
|
3
|
+
import { type ModelSettingsDraft, type ModelSettingsSaveResult } from '../config/model-profile-store.js';
|
|
4
|
+
/** Fully prepared live state; applying it performs no input parsing or disk access. */
|
|
5
|
+
export interface ModelSettingsSnapshot {
|
|
6
|
+
config: CliConfig;
|
|
7
|
+
model: ModelRef;
|
|
8
|
+
systemPrompt: string;
|
|
9
|
+
}
|
|
10
|
+
/** Narrow controller port keeps persistence independent from the agent implementation. */
|
|
11
|
+
export interface ModelSettingsController {
|
|
12
|
+
getConfig(): CliConfig;
|
|
13
|
+
getSettingsRevision(): number;
|
|
14
|
+
isModelSettingsBusy(): boolean;
|
|
15
|
+
isFastRegistered(): boolean;
|
|
16
|
+
prepareModelSettingsSnapshot(config: CliConfig): ModelSettingsSnapshot;
|
|
17
|
+
applyModelSettingsSnapshot(snapshot: ModelSettingsSnapshot): void;
|
|
18
|
+
applyModelSettingsEffects(): void;
|
|
19
|
+
blockModelSettingsRequests(): void;
|
|
20
|
+
}
|
|
21
|
+
/** Capture both baselines when the editor opens, without migrating or writing anything. */
|
|
22
|
+
export declare function createModelSettingsDraft(live: CliConfig, liveRevision: number): ModelSettingsDraft;
|
|
23
|
+
/** Map stable failure codes to safe UI text; never include parser or SDK error messages. */
|
|
24
|
+
export declare function modelSettingsResultMessage(result: ModelSettingsSaveResult): string;
|
|
25
|
+
/** Validate and prepare everything before the one synchronous, atomic disk commit. */
|
|
26
|
+
export declare function saveModelSettings(input: {
|
|
27
|
+
draft: ModelSettingsDraft;
|
|
28
|
+
controller: ModelSettingsController;
|
|
29
|
+
}): ModelSettingsSaveResult;
|
|
30
|
+
/** Reload the complete model snapshot; invalid disk content never replaces a live session. */
|
|
31
|
+
export declare function reloadModelSettings(controller: ModelSettingsController): ModelSettingsSaveResult;
|