@aragon-agent/cli 0.6.12 → 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/README.md +81 -15
- package/dist/agent/controller.d.ts +29 -1
- package/dist/agent/controller.js +123 -22
- 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 +12 -0
- package/dist/agent/queued-messages.js +18 -0
- package/dist/agent/reducer.d.ts +13 -1
- package/dist/agent/reducer.js +51 -12
- package/dist/cli.js +15 -1
- package/dist/commands/builtins.js +66 -39
- 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/stdin-filter.js +3 -2
- 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/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 +113 -205
- package/dist/ui/BottomStatusRow.d.ts +6 -86
- package/dist/ui/BottomStatusRow.js +11 -26
- package/dist/ui/Composer.d.ts +16 -83
- package/dist/ui/Composer.js +9 -100
- package/dist/ui/PromptInput.d.ts +4 -1
- package/dist/ui/PromptInput.js +41 -34
- package/dist/ui/QueuePanel.d.ts +8 -0
- package/dist/ui/QueuePanel.js +9 -0
- package/dist/ui/QueueStatusRow.d.ts +2 -0
- package/dist/ui/QueueStatusRow.js +1 -1
- package/dist/ui/StatusBar.d.ts +13 -149
- package/dist/ui/StatusBar.js +27 -142
- 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-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 +4 -0
- package/dist/ui/editor-reducer.js +29 -15
- 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 +8 -8
- 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 +1 -0
- package/dist/ui/use-wheel-routing.js +2 -1
- package/package.json +2 -2
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* /resume /copy /terminal-setup /exit (/quit), plus the /team, /fast and
|
|
5
5
|
* /todo subsystems.
|
|
6
6
|
*/
|
|
7
|
+
import { modelSettingsResultMessage } from '../agent/model-profile-settings.js';
|
|
7
8
|
import { existsSync, statSync } from 'node:fs';
|
|
8
9
|
import { isAbsolute, resolve } from 'node:path';
|
|
9
10
|
import { resolveOutputTokens } from '@aragon-agent/core';
|
|
@@ -13,10 +14,9 @@ import { mergePendingEntries } from '../agent/queued-messages.js';
|
|
|
13
14
|
import { TODO_FOLLOW_LIMITS } from '../todo/limits.js';
|
|
14
15
|
import { buildContinuationMessage } from '../todo/follow-through.js';
|
|
15
16
|
import { MODE_LABEL, nextMode } from '../agent/agent-mode.js';
|
|
16
|
-
import { loadConfig } from '../config/load.js';
|
|
17
17
|
import { loadSession, resolveSessionPath, saveSession } from '../session/persist.js';
|
|
18
18
|
import { getLogger } from '../logging/logger.js';
|
|
19
|
-
import {
|
|
19
|
+
import { registerQueueCommand } from './queue-command.js';
|
|
20
20
|
import { perfCommand } from './perf.js';
|
|
21
21
|
import { runCompactCommand } from '../compaction/command.js';
|
|
22
22
|
import { runContextCommand } from '../compaction/context-command.js';
|
|
@@ -371,8 +371,16 @@ const COMMANDS = [
|
|
|
371
371
|
return;
|
|
372
372
|
}
|
|
373
373
|
const persist = (patch) => {
|
|
374
|
-
|
|
375
|
-
|
|
374
|
+
const connection = ['provider', 'model', 'baseUrl'].some((key) => key in patch);
|
|
375
|
+
if (!connection) {
|
|
376
|
+
controller.setFastConfig(patch);
|
|
377
|
+
ctx.persistConfig({ fast: patch });
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
const result = controller.saveModelSettingsPatch({ fast: patch });
|
|
381
|
+
if (!result.ok)
|
|
382
|
+
ctx.notify('warn', modelSettingsResultMessage(result));
|
|
383
|
+
return result.ok;
|
|
376
384
|
};
|
|
377
385
|
if (!verb || verb === 'status') {
|
|
378
386
|
ctx.notify('info', formatFastStatus(controller));
|
|
@@ -380,7 +388,8 @@ const COMMANDS = [
|
|
|
380
388
|
}
|
|
381
389
|
if (verb === 'on' || verb === 'off') {
|
|
382
390
|
const enabled = verb === 'on';
|
|
383
|
-
persist({ enabled })
|
|
391
|
+
if (!persist({ enabled }))
|
|
392
|
+
return;
|
|
384
393
|
if (!controller.isFastRegistered()) {
|
|
385
394
|
// The honest branch, and the whole point of splitting by what was
|
|
386
395
|
// decided at construction (§3.3). It is one sentence, and it keeps
|
|
@@ -414,11 +423,13 @@ const COMMANDS = [
|
|
|
414
423
|
ctx.notify('warn', `Unknown provider "${provider}". Choose: ${ADAPTER_PROVIDERS.join(', ')}.`);
|
|
415
424
|
return;
|
|
416
425
|
}
|
|
417
|
-
persist({ provider, model })
|
|
426
|
+
if (!persist({ provider, model }))
|
|
427
|
+
return;
|
|
418
428
|
ctx.toast('success', `Fast model set to ${provider}:${model}.`);
|
|
419
429
|
return;
|
|
420
430
|
}
|
|
421
|
-
persist({ model: raw })
|
|
431
|
+
if (!persist({ model: raw }))
|
|
432
|
+
return;
|
|
422
433
|
ctx.toast('success', `Fast model set to ${raw}.`);
|
|
423
434
|
return;
|
|
424
435
|
}
|
|
@@ -432,7 +443,8 @@ const COMMANDS = [
|
|
|
432
443
|
ctx.notify('warn', `Unknown provider "${raw}". Choose: ${ADAPTER_PROVIDERS.join(', ')}.`);
|
|
433
444
|
return;
|
|
434
445
|
}
|
|
435
|
-
persist({ provider: raw })
|
|
446
|
+
if (!persist({ provider: raw }))
|
|
447
|
+
return;
|
|
436
448
|
ctx.toast('success', `Fast provider set to ${raw}.`);
|
|
437
449
|
return;
|
|
438
450
|
}
|
|
@@ -441,18 +453,20 @@ const COMMANDS = [
|
|
|
441
453
|
// (R-13): the user is allowed to run both tiers on one model, and the
|
|
442
454
|
// review still costs what it costs.
|
|
443
455
|
const cfg = controller.getConfig();
|
|
444
|
-
persist({
|
|
456
|
+
if (!persist({
|
|
445
457
|
provider: cfg.provider,
|
|
446
458
|
model: cfg.model,
|
|
447
459
|
baseUrl: cfg.baseUrl ?? '',
|
|
448
|
-
})
|
|
460
|
+
}))
|
|
461
|
+
return;
|
|
449
462
|
ctx.toast('success', `Fast tier set to ${cfg.provider}:${cfg.model} (same as main).`);
|
|
450
463
|
return;
|
|
451
464
|
}
|
|
452
465
|
if (verb === 'review') {
|
|
453
466
|
const raw = (value ?? '').trim().toLowerCase();
|
|
454
467
|
if (raw === 'off') {
|
|
455
|
-
persist({ review: false })
|
|
468
|
+
if (!persist({ review: false }))
|
|
469
|
+
return;
|
|
456
470
|
ctx.toast('success', 'Fast review off.');
|
|
457
471
|
return;
|
|
458
472
|
}
|
|
@@ -508,7 +522,8 @@ const COMMANDS = [
|
|
|
508
522
|
ctx.notify('warn', 'Usage: /fast delegate <on|off>');
|
|
509
523
|
return;
|
|
510
524
|
}
|
|
511
|
-
persist({ delegate: raw === 'on' })
|
|
525
|
+
if (!persist({ delegate: raw === 'on' }))
|
|
526
|
+
return;
|
|
512
527
|
ctx.toast('success', `Fast delegation ${raw}.`);
|
|
513
528
|
return;
|
|
514
529
|
}
|
|
@@ -891,6 +906,8 @@ const COMMANDS = [
|
|
|
891
906
|
ctx.controller.clearAllQueues();
|
|
892
907
|
ctx.dispatch({ type: 'resetConversation' });
|
|
893
908
|
ctx.toast('info', 'Started a new conversation.');
|
|
909
|
+
if (ctx.state.pendingSteering.length)
|
|
910
|
+
ctx.notify('warn', `\u5df2\u53d6\u6d88 ${ctx.state.pendingSteering.length} \u6761\u672a\u63a5\u6536\u6d88\u606f`);
|
|
894
911
|
},
|
|
895
912
|
},
|
|
896
913
|
{
|
|
@@ -941,15 +958,15 @@ const COMMANDS = [
|
|
|
941
958
|
name: 'resume',
|
|
942
959
|
description: 'Load a saved session',
|
|
943
960
|
run: (ctx) => {
|
|
944
|
-
if (ctx.controller.isRunning()) {
|
|
945
|
-
ctx.notify('warn', 'Cannot resume while
|
|
961
|
+
if (ctx.controller.isRunning() || ctx.controller.isModelSettingsBusy?.()) {
|
|
962
|
+
ctx.notify('warn', 'Cannot resume while running work, team, review, or compaction is busy.');
|
|
946
963
|
return;
|
|
947
964
|
}
|
|
948
965
|
try {
|
|
949
966
|
const path = resolveSessionPath(ctx.args, ctx.controller.getCwd());
|
|
950
967
|
const session = loadSession(path);
|
|
951
|
-
if (ctx.controller.isRunning()) {
|
|
952
|
-
ctx.notify('warn', 'Cannot resume while
|
|
968
|
+
if (ctx.controller.isRunning() || ctx.controller.isModelSettingsBusy?.()) {
|
|
969
|
+
ctx.notify('warn', 'Cannot resume while running work, team, review, or compaction is busy.');
|
|
953
970
|
return;
|
|
954
971
|
}
|
|
955
972
|
// Loading has validated and normalized every external value. Commit
|
|
@@ -963,6 +980,8 @@ const COMMANDS = [
|
|
|
963
980
|
ctx.controller.setModel(session.model.providerId, session.model.modelId, session.model.baseUrl);
|
|
964
981
|
}
|
|
965
982
|
ctx.notify('info', `Resumed session from ${path}`);
|
|
983
|
+
if (ctx.state.pendingSteering.length)
|
|
984
|
+
ctx.notify('warn', `\u5df2\u53d6\u6d88 ${ctx.state.pendingSteering.length} \u6761\u672a\u63a5\u6536\u6d88\u606f`);
|
|
966
985
|
}
|
|
967
986
|
catch (err) {
|
|
968
987
|
ctx.notify('error', `Resume failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -972,7 +991,7 @@ const COMMANDS = [
|
|
|
972
991
|
{
|
|
973
992
|
name: 'copy',
|
|
974
993
|
description: 'Copy the last answer to the clipboard',
|
|
975
|
-
run: (ctx) => {
|
|
994
|
+
run: async (ctx) => {
|
|
976
995
|
const last = [...ctx.state.entries].reverse().find((e) => e.kind === 'assistant');
|
|
977
996
|
if (!last || last.kind !== 'assistant' || last.text.trim().length === 0) {
|
|
978
997
|
ctx.notify('warn', 'No assistant message to copy.');
|
|
@@ -984,15 +1003,11 @@ const COMMANDS = [
|
|
|
984
1003
|
// which the local `pbcopy` never could.
|
|
985
1004
|
//
|
|
986
1005
|
// `ctx.writeForeign` and not `stdout.write` — see the field's comment.
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
ctx.toast('warn', 'Clipboard not available.');
|
|
1006
|
+
if (!ctx.requestCopy) {
|
|
1007
|
+
ctx.notify('warn', 'Clipboard coordinator unavailable.');
|
|
990
1008
|
return;
|
|
991
1009
|
}
|
|
992
|
-
|
|
993
|
-
// detectable, and tmux without `set -g set-clipboard on` swallows OSC 52
|
|
994
|
-
// silently (R-7).
|
|
995
|
-
ctx.toast('success', via === 'osc52' ? 'Sent to the terminal clipboard.' : 'Sent to the system clipboard.');
|
|
1010
|
+
await ctx.requestCopy({ text: last.text, lines: last.text.split('\n').length });
|
|
996
1011
|
},
|
|
997
1012
|
},
|
|
998
1013
|
{
|
|
@@ -1053,20 +1068,24 @@ const COMMANDS = [
|
|
|
1053
1068
|
const lines = [
|
|
1054
1069
|
'Shift+Enter newline - terminal setup',
|
|
1055
1070
|
'',
|
|
1056
|
-
'Ctrl+J
|
|
1057
|
-
'
|
|
1071
|
+
'Ctrl+J inserts a newline when delivered; Alt+Enter may be intercepted.',
|
|
1072
|
+
'To get a dedicated Shift+Enter key, make the terminal send',
|
|
1058
1073
|
'the CSI-u sequence \\u001b[13;2u for it:',
|
|
1059
1074
|
'',
|
|
1060
|
-
' Windows Terminal
|
|
1061
|
-
'
|
|
1062
|
-
'
|
|
1075
|
+
' Windows Terminal settings.json, merge root-level arrays:',
|
|
1076
|
+
' "copyOnSelect": false,',
|
|
1077
|
+
' "actions": [{ "id": "User.AragonNewline", "command":',
|
|
1078
|
+
' { "action": "sendInput", "input": "\\u001b[13;2u" } }],',
|
|
1079
|
+
' "keybindings": [{ "keys": "shift+enter", "id": "User.AragonNewline" }]',
|
|
1063
1080
|
' PowerShell runs inside the terminal; configure this binding in',
|
|
1064
1081
|
' Windows Terminal, not in the PowerShell profile. If both keys',
|
|
1065
1082
|
' send CR, the application cannot distinguish Shift+Enter from Enter.',
|
|
1066
1083
|
' VS Code terminal keybindings.json:',
|
|
1067
1084
|
' { "key": "shift+enter",',
|
|
1068
1085
|
' "command": "workbench.action.terminal.sendSequence",',
|
|
1086
|
+
' "when": "terminalFocus",',
|
|
1069
1087
|
' "args": { "text": "\\u001b[13;2u" } }',
|
|
1088
|
+
' User settings: "terminal.integrated.copyOnSelection": false',
|
|
1070
1089
|
' iTerm2 Settings > Profiles > Keys > Key Mappings:',
|
|
1071
1090
|
' Shift+Enter -> Send Escape Sequence: [13;2u',
|
|
1072
1091
|
' kitty/WezTerm/foot native once keyboard enhancement is on',
|
|
@@ -1077,6 +1096,13 @@ const COMMANDS = [
|
|
|
1077
1096
|
' or Alt+Enter',
|
|
1078
1097
|
'',
|
|
1079
1098
|
'This command only prints advice; it changes no terminal modes.',
|
|
1099
|
+
'Host selections (Shift+drag or /mouse off) belong to the terminal.',
|
|
1100
|
+
'App selections wait for Ctrl+C; only a native tool exit confirms copying.',
|
|
1101
|
+
'SSH uses OSC 52: the terminal may deny it; sent does not mean confirmed.',
|
|
1102
|
+
'Without the stdin filter, Delete falls back to Backspace.',
|
|
1103
|
+
'Without paste support, multiline paste cannot be guaranteed safe.',
|
|
1104
|
+
'IME confirmation has no composition signal in PTY bytes. If it emits CR,',
|
|
1105
|
+
'edit externally and use bracketed paste; this host combination needs validation.',
|
|
1080
1106
|
];
|
|
1081
1107
|
ctx.notify('info', lines.join('\n'));
|
|
1082
1108
|
},
|
|
@@ -1104,12 +1130,12 @@ const COMMANDS = [
|
|
|
1104
1130
|
ctx.notify('warn', 'Cannot reload the config while a run is in progress.');
|
|
1105
1131
|
return;
|
|
1106
1132
|
}
|
|
1107
|
-
const
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1133
|
+
const result = ctx.controller.reloadModelSettings();
|
|
1134
|
+
if (!result.ok) {
|
|
1135
|
+
ctx.notify('warn', modelSettingsResultMessage(result));
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
const config = ctx.controller.getConfig();
|
|
1113
1139
|
getLogger().info('config', 'reload', { provider: config.provider, model: config.model });
|
|
1114
1140
|
ctx.toast('success', `Reloaded config: ${config.provider}:${config.model}.`);
|
|
1115
1141
|
},
|
|
@@ -1219,6 +1245,7 @@ const DEFAULT_BG_LOG_ROWS = 40;
|
|
|
1219
1245
|
export function registerBuiltinCommands(registry) {
|
|
1220
1246
|
for (const command of COMMANDS)
|
|
1221
1247
|
registry.register(command);
|
|
1248
|
+
registerQueueCommand(registry);
|
|
1222
1249
|
}
|
|
1223
1250
|
/**
|
|
1224
1251
|
* Every built-in command name INCLUDING aliases (`quit`), for documentation and
|
|
@@ -1228,10 +1255,10 @@ export function registerBuiltinCommands(registry) {
|
|
|
1228
1255
|
* `registry.get(name)` instead, because that is the only check that stays
|
|
1229
1256
|
* correct when commands are registered dynamically (§7.1 / P1-2).
|
|
1230
1257
|
*/
|
|
1231
|
-
export const BUILTIN_COMMAND_NAMES = COMMANDS.flatMap((c) => [
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
]).sort();
|
|
1258
|
+
export const BUILTIN_COMMAND_NAMES = ['queue', ...COMMANDS.flatMap((c) => [
|
|
1259
|
+
c.name,
|
|
1260
|
+
...(c.aliases ?? []),
|
|
1261
|
+
])].sort();
|
|
1235
1262
|
// The `copyToClipboard` helper that used to live here moved into
|
|
1236
1263
|
// `ui/clipboard.ts` (tui-selection-and-scroll-follow §4.4.5), unchanged, so that
|
|
1237
1264
|
// `/copy` and the drag-select release share ONE clipboard path instead of two
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Read-only access to the live receipt-backed queue through the standard overlay port. */
|
|
2
|
+
export function registerQueueCommand(registry) {
|
|
3
|
+
registry.register({ name: 'queue', description: '\u67e5\u770b\u5f85\u5904\u7406\u6d88\u606f\u5168\u6587',
|
|
4
|
+
run: (ctx) => { ctx.setOverlay('queue'); } });
|
|
5
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* input is routed here instead of to the agent; `//` is the literal-`/` escape.
|
|
4
4
|
*/
|
|
5
5
|
import type { SubmitMessageOptions } from '../agent/prompt-options.js';
|
|
6
|
+
import type { CopyRequest, CopyRequestResult } from '../ui/clipboard-task.js';
|
|
6
7
|
import type { AgentController } from '../agent/controller.js';
|
|
7
8
|
import type { AgentMode } from '../agent/agent-mode.js';
|
|
8
9
|
import type { NoticeLevel, Overlay, ToastLevel, ViewAction, ViewState } from '../agent/reducer.js';
|
|
@@ -10,6 +11,8 @@ import type { PersistedConfig } from '../config/schema.js';
|
|
|
10
11
|
import type { FollowThroughBudget } from '../todo/follow-through.js';
|
|
11
12
|
import type { UpdateCommandPort } from '../update/types.js';
|
|
12
13
|
export interface CommandContext {
|
|
14
|
+
/** App-owned shared lock and single feedback path for command and selection copies. */
|
|
15
|
+
requestCopy?: (request: CopyRequest) => Promise<CopyRequestResult>;
|
|
13
16
|
/** Raw argument string after the command word (may be empty). */
|
|
14
17
|
args: string;
|
|
15
18
|
controller: AgentController;
|
|
@@ -28,13 +28,14 @@
|
|
|
28
28
|
* lead's own history had been compacted. The team panel row and the dispatch
|
|
29
29
|
* report are where a child's internals belong.
|
|
30
30
|
*/
|
|
31
|
+
import type { ModelRole } from '../config/model-profiles.js';
|
|
31
32
|
import type { AssistantMessage, ContextManager, LLMRequest, Message, ModelInfo, ModelRef, TokenUsage } from '@aragon-agent/core';
|
|
32
33
|
import type { CliConfig } from '../config/schema.js';
|
|
33
34
|
export interface ChildCompactionDeps {
|
|
34
35
|
/** Read LIVE on every call - see `childConfig` below. */
|
|
35
36
|
getConfig: () => CliConfig;
|
|
36
|
-
hasKey: (providerId: string) => boolean;
|
|
37
|
-
getApiKey: (providerId: string) => string | undefined;
|
|
37
|
+
hasKey: (providerId: string, role?: ModelRole) => boolean;
|
|
38
|
+
getApiKey: (providerId: string, role?: ModelRole) => string | undefined;
|
|
38
39
|
getModelInfoFor: (ref: Pick<ModelRef, 'providerId' | 'modelId'>) => ModelInfo;
|
|
39
40
|
isPricedModel: (ref: Pick<ModelRef, 'providerId' | 'modelId'>) => boolean;
|
|
40
41
|
/** The lead wiring's own transport, reused (D-10 stays true). */
|
|
@@ -50,6 +51,7 @@ export interface ChildCompactionDeps {
|
|
|
50
51
|
* exactly on the cap.
|
|
51
52
|
*/
|
|
52
53
|
export interface ChildContextManagerRequest {
|
|
54
|
+
role?: ModelRole;
|
|
53
55
|
label: string;
|
|
54
56
|
/**
|
|
55
57
|
* The model the child actually runs on.
|
package/dist/compaction/child.js
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* lead's own history had been compacted. The team panel row and the dispatch
|
|
29
29
|
* report are where a child's internals belong.
|
|
30
30
|
*/
|
|
31
|
+
import { resolveFastTier } from '../fast/resolve.js';
|
|
31
32
|
import { getLogger } from '../logging/logger.js';
|
|
32
33
|
import { Compactor } from './compactor.js';
|
|
33
34
|
import { COMPACTION_LIMITS } from './limits.js';
|
|
@@ -56,7 +57,7 @@ export function createChildContextManager(req, deps) {
|
|
|
56
57
|
...base,
|
|
57
58
|
provider: req.model.providerId,
|
|
58
59
|
model: req.model.modelId,
|
|
59
|
-
|
|
60
|
+
baseUrl: req.model.baseUrl,
|
|
60
61
|
compaction: {
|
|
61
62
|
...base.compaction,
|
|
62
63
|
keepRecentTurns: COMPACTION_LIMITS.childKeepRecentTurns,
|
|
@@ -71,6 +72,8 @@ export function createChildContextManager(req, deps) {
|
|
|
71
72
|
};
|
|
72
73
|
const compactor = new Compactor({
|
|
73
74
|
getConfig: childConfig,
|
|
75
|
+
mainRole: req.role ?? 'main',
|
|
76
|
+
resolveFastCandidate: () => resolveFastTier(deps.getConfig(), deps.hasKey),
|
|
74
77
|
hasKey: deps.hasKey,
|
|
75
78
|
getApiKey: deps.getApiKey,
|
|
76
79
|
getModelInfoFor: deps.getModelInfoFor,
|
|
@@ -9,16 +9,20 @@
|
|
|
9
9
|
* keep, which model summarizes, what the prompt asks for, what happens when the
|
|
10
10
|
* call fails, and when to stop spending.
|
|
11
11
|
*/
|
|
12
|
+
import type { ModelRole } from '../config/model-profiles.js';
|
|
12
13
|
import { type Message, type ModelInfo, type ModelRef, type TokenUsage, type AssistantMessage, type CompactionContext, type CompactionOutcome, type CompactionProbe, type ContextManager, type LLMRequest } from '@aragon-agent/core';
|
|
13
14
|
import type { CliConfig } from '../config/schema.js';
|
|
14
15
|
import type { NoticeLevel } from '../agent/reducer.js';
|
|
16
|
+
import { resolveFastTier } from '../fast/resolve.js';
|
|
15
17
|
import { ContextMeter } from './meter.js';
|
|
16
18
|
import type { CompactionEvent, Pressure } from './types.js';
|
|
17
19
|
export interface CompactorDeps {
|
|
20
|
+
mainRole?: ModelRole;
|
|
21
|
+
resolveFastCandidate?: () => ReturnType<typeof resolveFastTier>;
|
|
18
22
|
/** Read LIVE on every call — the controller replaces the object on mutation. */
|
|
19
23
|
getConfig: () => CliConfig;
|
|
20
|
-
hasKey: (providerId: string) => boolean;
|
|
21
|
-
getApiKey: (providerId: string) => string | undefined;
|
|
24
|
+
hasKey: (providerId: string, role?: ModelRole) => boolean;
|
|
25
|
+
getApiKey: (providerId: string, role?: ModelRole) => string | undefined;
|
|
22
26
|
/**
|
|
23
27
|
* The engine's live history and system prompt.
|
|
24
28
|
*
|
|
@@ -87,6 +91,7 @@ export interface CompactorOptions {
|
|
|
87
91
|
}
|
|
88
92
|
/** The summarizer the next compaction would use. */
|
|
89
93
|
export interface SummarizerChoice {
|
|
94
|
+
role: ModelRole;
|
|
90
95
|
ref: ModelRef;
|
|
91
96
|
/** Whether the fast tier supplied it, for `/compact status` and the card. */
|
|
92
97
|
fromFastTier: boolean;
|
|
@@ -486,20 +486,22 @@ export class Compactor {
|
|
|
486
486
|
const { summarizer } = args;
|
|
487
487
|
if (!summarizer)
|
|
488
488
|
return { kind: 'failed', reason: 'no_summarizer_model' };
|
|
489
|
-
const first = await this.callOnce(summarizer
|
|
489
|
+
const first = await this.callOnce(summarizer, args);
|
|
490
490
|
if (first.kind === 'ok' || first.kind === 'aborted')
|
|
491
491
|
return first;
|
|
492
492
|
// Rung 2 — retry, on the MAIN model when rung 1 was the fast tier.
|
|
493
|
-
const
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
493
|
+
const retryChoice = summarizer.fromFastTier
|
|
494
|
+
? { ref: this.mainRef(), role: this.deps.mainRole ?? 'main', fromFastTier: false }
|
|
495
|
+
: summarizer;
|
|
496
|
+
this.log.warn('summarize_retry', { reason: first.reason, model: retryChoice.ref.modelId });
|
|
497
|
+
const second = await this.callOnce(retryChoice, args);
|
|
497
498
|
if (second.kind === 'ok' || second.kind === 'aborted')
|
|
498
499
|
return second;
|
|
499
500
|
return { kind: 'failed', reason: second.reason };
|
|
500
501
|
}
|
|
501
|
-
async callOnce(
|
|
502
|
-
const
|
|
502
|
+
async callOnce(choice, args) {
|
|
503
|
+
const { ref, role } = choice;
|
|
504
|
+
const apiKey = this.deps.getApiKey(ref.providerId, role);
|
|
503
505
|
if (!apiKey)
|
|
504
506
|
return { kind: 'failed', reason: 'no_api_key' };
|
|
505
507
|
if (args.ctx.signal.aborted)
|
|
@@ -764,14 +766,14 @@ export class Compactor {
|
|
|
764
766
|
resolveSummarizer() {
|
|
765
767
|
const config = this.deps.getConfig();
|
|
766
768
|
if (config.compaction.useFastTier) {
|
|
767
|
-
const tier = resolveFastTier(config, this.deps.hasKey);
|
|
769
|
+
const tier = this.deps.resolveFastCandidate?.() ?? resolveFastTier(config, this.deps.hasKey);
|
|
768
770
|
if (tier.ok)
|
|
769
|
-
return { ref: tier.ref, fromFastTier: true };
|
|
771
|
+
return { ref: tier.ref, role: 'fast', fromFastTier: true };
|
|
770
772
|
}
|
|
771
773
|
const ref = this.mainRef();
|
|
772
|
-
if (!this.deps.hasKey(ref.providerId))
|
|
774
|
+
if (!this.deps.hasKey(ref.providerId, this.deps.mainRole ?? 'main'))
|
|
773
775
|
return null;
|
|
774
|
-
return { ref, fromFastTier: false };
|
|
776
|
+
return { ref, role: this.deps.mainRole ?? 'main', fromFastTier: false };
|
|
775
777
|
}
|
|
776
778
|
/**
|
|
777
779
|
* The meter this compactor reads, injected or private (R-1).
|
|
@@ -45,6 +45,11 @@ export function formatContextReport(ctx) {
|
|
|
45
45
|
// one line and answers the same question.
|
|
46
46
|
lines.push(' includes subagent, fast-tier and compaction spend,');
|
|
47
47
|
lines.push(' not just this conversation');
|
|
48
|
+
lines.push(' Scope lead occupancy is used / limit in tokens; session input includes caches.');
|
|
49
|
+
lines.push(' Estimate USD session cost, including restored usage; missing prices count as 0.');
|
|
50
|
+
lines.push(' Zero estimated cost does not guarantee free usage or match the bill.');
|
|
51
|
+
lines.push(' Run average booked output-token delta / elapsed time, not instantaneous generation.');
|
|
52
|
+
lines.push(' May include team, fast-tier and compaction output booked during this run.');
|
|
48
53
|
return lines.join('\n');
|
|
49
54
|
}
|
|
50
55
|
/**
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
* guard is now unreachable for declines and stays for the abort race it was
|
|
36
36
|
* written for.
|
|
37
37
|
*/
|
|
38
|
+
import type { ModelRole } from '../config/model-profiles.js';
|
|
38
39
|
import { type AgentEvent, type ContextManager, type Message, type ModelInfo, type ModelRef, type ProviderRegistry, type ProviderRegistryOptions, type TokenUsage } from '@aragon-agent/core';
|
|
39
40
|
import type { CliConfig } from '../config/schema.js';
|
|
40
41
|
import type { NoticeLevel } from '../agent/reducer.js';
|
|
@@ -44,8 +45,8 @@ import type { CompactionEventListener, CompactionSnapshot } from './types.js';
|
|
|
44
45
|
export interface CompactionWiringDeps {
|
|
45
46
|
/** Read LIVE on every call - the controller replaces the object on mutation. */
|
|
46
47
|
getConfig: () => CliConfig;
|
|
47
|
-
hasKey: (providerId: string) => boolean;
|
|
48
|
-
getApiKey: (providerId: string) => string | undefined;
|
|
48
|
+
hasKey: (providerId: string, role?: ModelRole) => boolean;
|
|
49
|
+
getApiKey: (providerId: string, role?: ModelRole) => string | undefined;
|
|
49
50
|
getModelInfoFor: (ref: Pick<ModelRef, 'providerId' | 'modelId'>) => ModelInfo;
|
|
50
51
|
isPricedModel: (ref: Pick<ModelRef, 'providerId' | 'modelId'>) => boolean;
|
|
51
52
|
/**
|
|
@@ -158,7 +159,7 @@ export declare class CompactionWiring {
|
|
|
158
159
|
isEnabled(): boolean;
|
|
159
160
|
setEnabled(enabled: boolean): void;
|
|
160
161
|
/** Re-emit the snapshot after any config mutation, so the chip and the gauge move. */
|
|
161
|
-
onConfigChanged(): void;
|
|
162
|
+
onConfigChanged(enabled?: boolean): void;
|
|
162
163
|
abort(): void;
|
|
163
164
|
/**
|
|
164
165
|
* The host replaced or cleared the history (hardening §3.2.3, sites 3 and 4).
|
|
@@ -209,7 +209,12 @@ export class CompactionWiring {
|
|
|
209
209
|
this.emit({ type: 'snapshot', snapshot: this.snapshot() });
|
|
210
210
|
}
|
|
211
211
|
/** Re-emit the snapshot after any config mutation, so the chip and the gauge move. */
|
|
212
|
-
onConfigChanged() {
|
|
212
|
+
onConfigChanged(enabled) {
|
|
213
|
+
if (enabled !== undefined && enabled !== this.enabled) {
|
|
214
|
+
this.enabled = enabled;
|
|
215
|
+
if (enabled)
|
|
216
|
+
this.compactor.clearSelfDisable();
|
|
217
|
+
}
|
|
213
218
|
this.emit({ type: 'snapshot', snapshot: this.snapshot() });
|
|
214
219
|
}
|
|
215
220
|
abort() {
|
|
@@ -168,6 +168,8 @@ export declare function applyCompactionConfigSet(key: string, value: string): Pa
|
|
|
168
168
|
* later pastes a log into an issue.
|
|
169
169
|
*/
|
|
170
170
|
export declare function warnIfRedactionDisabled(key: string, value: string): void;
|
|
171
|
+
/** Every secret is masked wherever it is printed, `--json` included (R-12). */
|
|
172
|
+
export declare function maskConfig(config: PersistedConfig): Record<string, unknown>;
|
|
171
173
|
export declare function runConfigGet(key: string): void;
|
|
172
174
|
export declare function runConfigList(opts?: {
|
|
173
175
|
json?: boolean;
|
|
@@ -13,6 +13,7 @@ import process from 'node:process';
|
|
|
13
13
|
import { getConfigBackupPath, getConfigPath, getHomeRoot, getLogsDir, getPromptHistoryPath, getUiStatePath, isHomeOverridden, } from './app-paths.js';
|
|
14
14
|
import { getHomeMigrationBreadcrumbPath } from './migrate-home.js';
|
|
15
15
|
import { loadPersistedConfig, updatePersistedConfig } from './store.js';
|
|
16
|
+
import { normalizeModelProfiles, validateModelProfiles } from './model-profiles.js';
|
|
16
17
|
import { clampFastConfig, clampRetryConfig, clampCompactionConfig, parseThresholdInput, clampUpdateConfig, coercePositiveInt, maskSecret, DEFAULT_FAST_CONFIG, DEFAULT_RETRY_CONFIG, DEFAULT_TEAM_CONFIG, DEFAULT_COMPACTION_CONFIG, DEFAULT_UPDATE_CONFIG, } from './schema.js';
|
|
17
18
|
import { clampLogLevel } from '../logging/levels.js';
|
|
18
19
|
/** Config keys under `log.` that `aragon config set` accepts. */
|
|
@@ -386,12 +387,23 @@ export function warnIfRedactionDisabled(key, value) {
|
|
|
386
387
|
// get / list
|
|
387
388
|
// ---------------------------------------------------------------------------
|
|
388
389
|
/** Every secret is masked wherever it is printed, `--json` included (R-12). */
|
|
389
|
-
function maskConfig(config) {
|
|
390
|
+
export function maskConfig(config) {
|
|
390
391
|
const apiKeys = {};
|
|
391
392
|
for (const [provider, key] of Object.entries(config.apiKeys)) {
|
|
392
393
|
apiKeys[provider] = maskSecret(key);
|
|
393
394
|
}
|
|
394
|
-
|
|
395
|
+
const masked = { ...config, apiKeys };
|
|
396
|
+
if (config.modelProfiles !== undefined) {
|
|
397
|
+
if (validateModelProfiles(config.modelProfiles).length)
|
|
398
|
+
masked.modelProfiles = { invalid: true };
|
|
399
|
+
else {
|
|
400
|
+
const profiles = normalizeModelProfiles(config.modelProfiles);
|
|
401
|
+
masked.modelProfiles = { ...profiles, entries: profiles.entries.map((entry) => ({
|
|
402
|
+
...entry, apiKey: entry.apiKey ? 'Configured' : 'Missing',
|
|
403
|
+
})) };
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return masked;
|
|
395
407
|
}
|
|
396
408
|
/**
|
|
397
409
|
* The human-readable label for a value whose raw form would mislead, or
|
|
@@ -503,8 +515,8 @@ export async function runConfigEdit() {
|
|
|
503
515
|
JSON.parse(readFileSync(file, 'utf-8'));
|
|
504
516
|
process.stdout.write(`${file} saved.\n`);
|
|
505
517
|
}
|
|
506
|
-
catch
|
|
507
|
-
process.stderr.write(`${file} is not valid JSON
|
|
518
|
+
catch {
|
|
519
|
+
process.stderr.write(`${file} is not valid JSON.\n` +
|
|
508
520
|
`The previous version is still at ${backup}.\n`);
|
|
509
521
|
process.exitCode = 1;
|
|
510
522
|
}
|
package/dist/config/load.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* legitimately long single tool run can never be watchdog-killed.
|
|
7
7
|
*/
|
|
8
8
|
import { type CliConfig } from './schema.js';
|
|
9
|
+
import type { ModelRole } from './model-profiles.js';
|
|
9
10
|
export interface CliFlags {
|
|
10
11
|
provider?: string;
|
|
11
12
|
model?: string;
|
|
@@ -245,4 +246,4 @@ export declare function loadConfig(flags?: CliFlags): CliConfig;
|
|
|
245
246
|
* Build the `getApiKey(providerId)` resolver the core `Agent` consumes.
|
|
246
247
|
* The one-shot `--api-key` override wins, but only for the active provider.
|
|
247
248
|
*/
|
|
248
|
-
export declare function makeGetApiKey(config: CliConfig): (providerId: string) => string | undefined;
|
|
249
|
+
export declare function makeGetApiKey(config: CliConfig, role?: ModelRole): (providerId: string) => string | undefined;
|
package/dist/config/load.js
CHANGED
|
@@ -9,6 +9,7 @@ import process from 'node:process';
|
|
|
9
9
|
import { DEFAULT_CONFIG, IDLE_TIMEOUT_MARGIN_MS, clampAskRounds, clampDensity, clampHumanTimeout, clampCompactionConfig, clampLogConfig, clampFastConfig, clampRetryConfig, clampSkillsConfig, clampTeamConfig, clampTodoConfig, clampBashConfig, clampUpdateConfig, clampTheme, clampThinkingLevel, clampContextWindow, clampMaxTokens, clampMaxRenderInterval, clampScrollResumeMs, clampTranscriptRetain, clampTranscriptWindow, coercePositiveInt, isAutoToken, isSkillsToolPolicyMode, parseThresholdInput, } from './schema.js';
|
|
10
10
|
import { loadDotenv, readEnvConfig, readEnvDisabledSkills } from './env.js';
|
|
11
11
|
import { readConfigFile } from './store.js';
|
|
12
|
+
import { projectModelProfiles, resolveModelProfileState, resolveModelRoleKey } from './model-profile-resolution.js';
|
|
12
13
|
import { getConfigPath } from './app-paths.js';
|
|
13
14
|
import { setHistoryEnabled } from './prompt-history.js';
|
|
14
15
|
import { setEntryRetain } from '../agent/entry-limits.js';
|
|
@@ -440,11 +441,17 @@ export function loadConfig(flags = {}) {
|
|
|
440
441
|
// `.env` must be loaded before reading env overrides.
|
|
441
442
|
loadDotenv(cwd);
|
|
442
443
|
const read = readConfigFile();
|
|
443
|
-
const
|
|
444
|
+
const projection = projectModelProfiles(read.config ?? {});
|
|
445
|
+
const file = projection.file;
|
|
444
446
|
const env = readEnvConfig();
|
|
445
447
|
if (read.parseError) {
|
|
446
448
|
getLogger().error('config', 'config_parse_failed', { error: read.parseError });
|
|
447
449
|
}
|
|
450
|
+
if (projection.issues.length) {
|
|
451
|
+
getLogger().warn('config', 'model_profiles_invalid', {
|
|
452
|
+
error: 'Invalid model profiles; using custom connections. Repair with aragon config edit.',
|
|
453
|
+
});
|
|
454
|
+
}
|
|
448
455
|
const provider = pick(flags.provider, env.partial.provider, file.provider) ?? DEFAULT_CONFIG.provider;
|
|
449
456
|
const model = pick(flags.model, env.partial.model, file.model) ?? DEFAULT_CONFIG.model;
|
|
450
457
|
const baseUrl = pick(flags.baseUrl, env.partial.baseUrl ?? undefined, file.baseUrl ?? undefined);
|
|
@@ -543,6 +550,7 @@ export function loadConfig(flags = {}) {
|
|
|
543
550
|
// it through the same module-singleton channel `setHistoryEnabled` uses.
|
|
544
551
|
setEntryRetain(transcriptRetain);
|
|
545
552
|
const config = {
|
|
553
|
+
modelProfiles: projection.issues.length ? undefined : file.modelProfiles,
|
|
546
554
|
provider,
|
|
547
555
|
model,
|
|
548
556
|
baseUrl,
|
|
@@ -605,6 +613,10 @@ export function loadConfig(flags = {}) {
|
|
|
605
613
|
unicode: caps.unicode,
|
|
606
614
|
apiKeyOverride: flags.apiKey && flags.apiKey.trim().length > 0 ? flags.apiKey.trim() : undefined,
|
|
607
615
|
};
|
|
616
|
+
config.modelProfileState = resolveModelProfileState(config, config.modelProfiles, projection.issues.length > 0);
|
|
617
|
+
if (config.apiKeyOverride && (config.modelProfiles?.mainId || config.modelProfiles?.fastId)) {
|
|
618
|
+
config.apiKeyOverrideTarget = { provider, baseUrl: baseUrl?.trim() || null };
|
|
619
|
+
}
|
|
608
620
|
recordResolvedConfig(config);
|
|
609
621
|
return config;
|
|
610
622
|
}
|
|
@@ -646,11 +658,6 @@ function recordResolvedConfig(config) {
|
|
|
646
658
|
* Build the `getApiKey(providerId)` resolver the core `Agent` consumes.
|
|
647
659
|
* The one-shot `--api-key` override wins, but only for the active provider.
|
|
648
660
|
*/
|
|
649
|
-
export function makeGetApiKey(config) {
|
|
650
|
-
return (providerId) => {
|
|
651
|
-
if (config.apiKeyOverride && providerId === config.provider) {
|
|
652
|
-
return config.apiKeyOverride;
|
|
653
|
-
}
|
|
654
|
-
return config.apiKeys[providerId];
|
|
655
|
-
};
|
|
661
|
+
export function makeGetApiKey(config, role = 'main') {
|
|
662
|
+
return (providerId) => resolveModelRoleKey({ config, role, providerId });
|
|
656
663
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type CliConfig, type PersistedConfig } from './schema.js';
|
|
2
|
+
import { type ModelProfilesConfig, type ModelProfilesRuntime, type ModelRole, type ProfileValidationIssue } from './model-profiles.js';
|
|
3
|
+
export interface ProfileProjection {
|
|
4
|
+
file: Partial<PersistedConfig>;
|
|
5
|
+
state: ModelProfilesRuntime;
|
|
6
|
+
issues: ProfileValidationIssue[];
|
|
7
|
+
}
|
|
8
|
+
export interface RoleKeyInput {
|
|
9
|
+
config: CliConfig;
|
|
10
|
+
role: ModelRole;
|
|
11
|
+
providerId: string;
|
|
12
|
+
}
|
|
13
|
+
/** Compare configured endpoint spelling strictly, with null and undefined equivalent. */
|
|
14
|
+
export declare function normalizeProfileBaseUrl(value: string | null | undefined): string | null;
|
|
15
|
+
/** Project a valid library onto a copy without rewriting legacy preferences. */
|
|
16
|
+
export declare function projectModelProfiles(file: Partial<PersistedConfig>): ProfileProjection;
|
|
17
|
+
/** Derive binding status from current connections, including session/model overrides. */
|
|
18
|
+
export declare function resolveModelProfileState(config: CliConfig, profiles?: ModelProfilesConfig | undefined, invalid?: boolean): ModelProfilesRuntime;
|
|
19
|
+
/** Resolve only credentials scoped to the requested role and actual connection target. */
|
|
20
|
+
export declare function resolveModelRoleKey(input: RoleKeyInput): string | undefined;
|