@astralyn/pi 0.84.5 → 0.84.7
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 +35 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +4 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +1 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +6 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +11 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/tools/bash.d.ts +5 -0
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +1 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/extensions/background/index.d.ts +95 -20
- package/dist/extensions/background/index.d.ts.map +1 -1
- package/dist/extensions/background/index.js +470 -136
- package/dist/extensions/background/index.js.map +1 -1
- package/dist/extensions/background/manager.d.ts +14 -0
- package/dist/extensions/background/manager.d.ts.map +1 -1
- package/dist/extensions/background/manager.js +125 -24
- package/dist/extensions/background/manager.js.map +1 -1
- package/dist/extensions/background/registry.d.ts +82 -0
- package/dist/extensions/background/registry.d.ts.map +1 -1
- package/dist/extensions/background/registry.js +237 -6
- package/dist/extensions/background/registry.js.map +1 -1
- package/dist/extensions/background/render.d.ts +35 -11
- package/dist/extensions/background/render.d.ts.map +1 -1
- package/dist/extensions/background/render.js +180 -30
- package/dist/extensions/background/render.js.map +1 -1
- package/dist/extensions/router/dialog-host.d.ts +60 -0
- package/dist/extensions/router/dialog-host.d.ts.map +1 -0
- package/dist/extensions/router/dialog-host.js +196 -0
- package/dist/extensions/router/dialog-host.js.map +1 -0
- package/dist/extensions/router/probe.d.ts.map +1 -1
- package/dist/extensions/router/probe.js +2 -0
- package/dist/extensions/router/probe.js.map +1 -1
- package/dist/extensions/router/ui.d.ts.map +1 -1
- package/dist/extensions/router/ui.js +117 -67
- package/dist/extensions/router/ui.js.map +1 -1
- package/dist/extensions/subagent/activity.d.ts +3 -5
- package/dist/extensions/subagent/activity.d.ts.map +1 -1
- package/dist/extensions/subagent/activity.js +8 -89
- package/dist/extensions/subagent/activity.js.map +1 -1
- package/dist/extensions/subagent/agents-command.d.ts.map +1 -1
- package/dist/extensions/subagent/agents-command.js +391 -161
- package/dist/extensions/subagent/agents-command.js.map +1 -1
- package/dist/extensions/subagent/agents.d.ts +2 -0
- package/dist/extensions/subagent/agents.d.ts.map +1 -1
- package/dist/extensions/subagent/agents.js +7 -1
- package/dist/extensions/subagent/agents.js.map +1 -1
- package/dist/extensions/subagent/budget.d.ts +15 -0
- package/dist/extensions/subagent/budget.d.ts.map +1 -0
- package/dist/extensions/subagent/budget.js +96 -0
- package/dist/extensions/subagent/budget.js.map +1 -0
- package/dist/extensions/subagent/cancellation.d.ts +29 -0
- package/dist/extensions/subagent/cancellation.d.ts.map +1 -0
- package/dist/extensions/subagent/cancellation.js +73 -0
- package/dist/extensions/subagent/cancellation.js.map +1 -0
- package/dist/extensions/subagent/index.d.ts.map +1 -1
- package/dist/extensions/subagent/index.js +5 -22
- package/dist/extensions/subagent/index.js.map +1 -1
- package/dist/extensions/subagent/model-selection.d.ts +16 -0
- package/dist/extensions/subagent/model-selection.d.ts.map +1 -0
- package/dist/extensions/subagent/model-selection.js +51 -0
- package/dist/extensions/subagent/model-selection.js.map +1 -0
- package/dist/extensions/subagent/render.d.ts +6 -1
- package/dist/extensions/subagent/render.d.ts.map +1 -1
- package/dist/extensions/subagent/render.js +23 -12
- package/dist/extensions/subagent/render.js.map +1 -1
- package/dist/extensions/subagent/resolve.d.ts.map +1 -1
- package/dist/extensions/subagent/resolve.js +2 -21
- package/dist/extensions/subagent/resolve.js.map +1 -1
- package/dist/extensions/subagent/runner.d.ts +4 -5
- package/dist/extensions/subagent/runner.d.ts.map +1 -1
- package/dist/extensions/subagent/runner.js +71 -264
- package/dist/extensions/subagent/runner.js.map +1 -1
- package/dist/extensions/subagent/sdk-runner.d.ts +13 -11
- package/dist/extensions/subagent/sdk-runner.d.ts.map +1 -1
- package/dist/extensions/subagent/sdk-runner.js +167 -148
- package/dist/extensions/subagent/sdk-runner.js.map +1 -1
- package/dist/extensions/subagent/settings.d.ts.map +1 -1
- package/dist/extensions/subagent/settings.js +11 -4
- package/dist/extensions/subagent/settings.js.map +1 -1
- package/dist/extensions/subagent/state.d.ts +89 -0
- package/dist/extensions/subagent/state.d.ts.map +1 -0
- package/dist/extensions/subagent/state.js +294 -0
- package/dist/extensions/subagent/state.js.map +1 -0
- package/dist/extensions/subagent/text.d.ts +4 -0
- package/dist/extensions/subagent/text.d.ts.map +1 -1
- package/dist/extensions/subagent/text.js +75 -0
- package/dist/extensions/subagent/text.js.map +1 -1
- package/dist/extensions/subagent/types.d.ts +3 -8
- package/dist/extensions/subagent/types.d.ts.map +1 -1
- package/dist/extensions/subagent/types.js.map +1 -1
- package/dist/extensions/subagent/ui/choices.d.ts +36 -0
- package/dist/extensions/subagent/ui/choices.d.ts.map +1 -0
- package/dist/extensions/subagent/ui/choices.js +54 -0
- package/dist/extensions/subagent/ui/choices.js.map +1 -0
- package/dist/extensions/subagent/ui/model-list.d.ts +1 -0
- package/dist/extensions/subagent/ui/model-list.d.ts.map +1 -1
- package/dist/extensions/subagent/ui/model-list.js +15 -18
- package/dist/extensions/subagent/ui/model-list.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/docs/bundled/README.md +1 -1
- package/docs/bundled/extensions/background.md +55 -20
- package/docs/extensions.md +13 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/dist/extensions/subagent/retry.d.ts +0 -17
- package/dist/extensions/subagent/retry.d.ts.map +0 -1
- package/dist/extensions/subagent/retry.js +0 -41
- package/dist/extensions/subagent/retry.js.map +0 -1
|
@@ -1,45 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Container, Spacer, Text, TruncatedText, } from "@earendil-works/pi-tui";
|
|
1
|
+
import { Container, isFocusable, Spacer, Text, TruncatedText, } from "@earendil-works/pi-tui";
|
|
3
2
|
import { getAgentDir } from "../../config.js";
|
|
4
3
|
import { DynamicBorder } from "../../modes/interactive/components/dynamic-border.js";
|
|
5
4
|
import { keyLabel } from "../../modes/interactive/components/keybinding-hints.js";
|
|
6
|
-
import { AGENT_PROFILES } from "./agents.js";
|
|
7
|
-
import { THINKING_LEVELS } from "./constants.js";
|
|
5
|
+
import { AGENT_PROFILE_LABELS, AGENT_PROFILES } from "./agents.js";
|
|
8
6
|
import { loadSubagentConfig, updateProfileOverride } from "./settings.js";
|
|
7
|
+
import { truncate } from "./text.js";
|
|
8
|
+
import { buildModelChoices, buildSettingsRows, buildThinkingChoices, compareModels, } from "./ui/choices.js";
|
|
9
9
|
import { ProfileModelListComponent } from "./ui/model-list.js";
|
|
10
10
|
const MODEL_REFRESH_TIMEOUT_MS = 15_000;
|
|
11
11
|
const MAX_REFRESH_ERROR_LENGTH = 240;
|
|
12
|
-
// Static display titles for the two built-in profiles; agents.ts owns the
|
|
13
|
-
// model-facing copy.
|
|
14
|
-
const PROFILE_LABELS = {
|
|
15
|
-
explorer: "Explorer",
|
|
16
|
-
general: "General",
|
|
17
|
-
};
|
|
18
|
-
function modelId(model) {
|
|
19
|
-
return model ? `${model.provider}/${model.id}` : "none";
|
|
20
|
-
}
|
|
21
|
-
function findModel(models, value) {
|
|
22
|
-
if (!value)
|
|
23
|
-
return undefined;
|
|
24
|
-
return models.find((model) => `${model.provider}/${model.id}` === value);
|
|
25
|
-
}
|
|
26
|
-
function boundText(value, limit) {
|
|
27
|
-
const characters = [...value];
|
|
28
|
-
return characters.length <= limit ? value : `${characters.slice(0, Math.max(0, limit - 1)).join("")}…`;
|
|
29
|
-
}
|
|
30
|
-
// Capability checks use the saved model when it is still in the catalog,
|
|
31
|
-
// falling back to the parent session model otherwise.
|
|
32
|
-
function effectiveModel(currentSessionModel, models, override) {
|
|
33
|
-
if (override?.model) {
|
|
34
|
-
const saved = findModel(models, override.model);
|
|
35
|
-
if (saved)
|
|
36
|
-
return saved;
|
|
37
|
-
}
|
|
38
|
-
return currentSessionModel;
|
|
39
|
-
}
|
|
40
|
-
function inheritedThinking(effective, currentThinking) {
|
|
41
|
-
return effective ? clampThinkingLevel(effective, currentThinking) : currentThinking;
|
|
42
|
-
}
|
|
43
12
|
function refreshFailureMessage(providerIds) {
|
|
44
13
|
if (providerIds.length === 1)
|
|
45
14
|
return `Could not refresh ${providerIds[0]}; showing cached models.`;
|
|
@@ -58,6 +27,85 @@ function navigationHint(theme, keybindings) {
|
|
|
58
27
|
.join("/");
|
|
59
28
|
return keys ? `${theme.fg("dim", keys)}${theme.fg("muted", " navigate")}` : "";
|
|
60
29
|
}
|
|
30
|
+
/** Small Pi-native list page used inside the single /agents custom UI lifecycle. */
|
|
31
|
+
class ChoiceMenuComponent extends Container {
|
|
32
|
+
get focused() {
|
|
33
|
+
return this._focused;
|
|
34
|
+
}
|
|
35
|
+
set focused(value) {
|
|
36
|
+
this._focused = value;
|
|
37
|
+
}
|
|
38
|
+
constructor(options) {
|
|
39
|
+
super();
|
|
40
|
+
this._focused = false;
|
|
41
|
+
this.theme = options.theme;
|
|
42
|
+
this.keybindings = options.keybindings;
|
|
43
|
+
this.choices = options.choices;
|
|
44
|
+
this.onDone = options.onDone;
|
|
45
|
+
this.title = options.title;
|
|
46
|
+
this.cancelDescription = options.cancelDescription;
|
|
47
|
+
this.selectedIndex = Math.min(Math.max(options.initialIndex ?? 0, 0), Math.max(0, options.choices.length - 1));
|
|
48
|
+
this.addChild(new DynamicBorder());
|
|
49
|
+
this.addChild(new Spacer(1));
|
|
50
|
+
this.titleText = new Text("", 1, 0);
|
|
51
|
+
this.addChild(this.titleText);
|
|
52
|
+
this.addChild(new Spacer(1));
|
|
53
|
+
this.listContainer = new Container();
|
|
54
|
+
this.addChild(this.listContainer);
|
|
55
|
+
this.addChild(new Spacer(1));
|
|
56
|
+
this.hintText = new Text("", 1, 0);
|
|
57
|
+
this.addChild(this.hintText);
|
|
58
|
+
this.addChild(new Spacer(1));
|
|
59
|
+
this.addChild(new DynamicBorder());
|
|
60
|
+
this.updateStaticText();
|
|
61
|
+
this.updateList();
|
|
62
|
+
}
|
|
63
|
+
updateStaticText() {
|
|
64
|
+
this.titleText.setText(this.theme.fg("accent", this.theme.bold(this.title)));
|
|
65
|
+
const hints = [
|
|
66
|
+
navigationHint(this.theme, this.keybindings),
|
|
67
|
+
hint(this.theme, this.keybindings, "tui.select.confirm", "select"),
|
|
68
|
+
hint(this.theme, this.keybindings, "tui.select.cancel", this.cancelDescription),
|
|
69
|
+
].filter(Boolean);
|
|
70
|
+
this.hintText.setText(hints.join(this.theme.fg("muted", " • ")));
|
|
71
|
+
}
|
|
72
|
+
updateList() {
|
|
73
|
+
this.listContainer.clear();
|
|
74
|
+
for (const [index, choice] of this.choices.entries()) {
|
|
75
|
+
const selected = index === this.selectedIndex;
|
|
76
|
+
const prefix = selected ? this.theme.fg("accent", "→ ") : " ";
|
|
77
|
+
const label = this.theme.fg(selected ? "accent" : "text", choice.label);
|
|
78
|
+
this.listContainer.addChild(new TruncatedText(`${prefix}${label}`, 1, 0));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
moveSelection(offset) {
|
|
82
|
+
if (this.choices.length === 0)
|
|
83
|
+
return;
|
|
84
|
+
this.selectedIndex = (this.selectedIndex + offset + this.choices.length) % this.choices.length;
|
|
85
|
+
this.updateList();
|
|
86
|
+
}
|
|
87
|
+
invalidate() {
|
|
88
|
+
this.updateStaticText();
|
|
89
|
+
this.updateList();
|
|
90
|
+
super.invalidate();
|
|
91
|
+
}
|
|
92
|
+
handleInput(data) {
|
|
93
|
+
if (this.keybindings.matches(data, "tui.select.up")) {
|
|
94
|
+
this.moveSelection(-1);
|
|
95
|
+
}
|
|
96
|
+
else if (this.keybindings.matches(data, "tui.select.down")) {
|
|
97
|
+
this.moveSelection(1);
|
|
98
|
+
}
|
|
99
|
+
else if (this.keybindings.matches(data, "tui.select.confirm")) {
|
|
100
|
+
const choice = this.choices[this.selectedIndex];
|
|
101
|
+
if (choice)
|
|
102
|
+
this.onDone(choice);
|
|
103
|
+
}
|
|
104
|
+
else if (this.keybindings.matches(data, "tui.select.cancel")) {
|
|
105
|
+
this.onDone(undefined);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
61
109
|
/** Compact Model/Thinking settings menu for one profile; every confirm persists immediately. */
|
|
62
110
|
class ProfileSettingsMenuComponent extends Container {
|
|
63
111
|
get focused() {
|
|
@@ -73,42 +121,48 @@ class ProfileSettingsMenuComponent extends Container {
|
|
|
73
121
|
this.theme = options.theme;
|
|
74
122
|
this.keybindings = options.keybindings;
|
|
75
123
|
this.onDone = options.onDone;
|
|
76
|
-
|
|
77
|
-
this.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
action: "thinking",
|
|
88
|
-
label: "Thinking",
|
|
89
|
-
value: options.override?.thinking
|
|
90
|
-
? `override — ${options.override.thinking}`
|
|
91
|
-
: `inherit — ${inheritedThinking(effective, options.currentThinking)}`,
|
|
92
|
-
override: options.override?.thinking !== undefined,
|
|
93
|
-
},
|
|
94
|
-
];
|
|
124
|
+
this.title = AGENT_PROFILE_LABELS[options.profile.name];
|
|
125
|
+
this.status = options.status;
|
|
126
|
+
this.rows = buildSettingsRows({
|
|
127
|
+
override: options.override,
|
|
128
|
+
models: options.models,
|
|
129
|
+
currentSessionModel: options.currentSessionModel,
|
|
130
|
+
currentThinking: options.currentThinking,
|
|
131
|
+
});
|
|
132
|
+
this.selectedIndex = Math.max(0, this.rows.findIndex((row) => row.action === options.selectedAction));
|
|
95
133
|
this.addChild(new DynamicBorder());
|
|
96
134
|
this.addChild(new Spacer(1));
|
|
97
|
-
this.
|
|
135
|
+
this.titleText = new Text("", 1, 0);
|
|
136
|
+
this.addChild(this.titleText);
|
|
98
137
|
this.addChild(new Spacer(1));
|
|
99
138
|
this.listContainer = new Container();
|
|
100
139
|
this.addChild(this.listContainer);
|
|
101
140
|
this.addChild(new Spacer(1));
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
141
|
+
if (this.status) {
|
|
142
|
+
this.statusText = new Text("", 1, 0);
|
|
143
|
+
this.addChild(this.statusText);
|
|
144
|
+
this.addChild(new Spacer(1));
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
this.statusText = undefined;
|
|
148
|
+
}
|
|
149
|
+
this.hintText = new Text("", 1, 0);
|
|
150
|
+
this.addChild(this.hintText);
|
|
108
151
|
this.addChild(new Spacer(1));
|
|
109
152
|
this.addChild(new DynamicBorder());
|
|
153
|
+
this.updateStaticText();
|
|
110
154
|
this.updateList();
|
|
111
155
|
}
|
|
156
|
+
updateStaticText() {
|
|
157
|
+
this.titleText.setText(this.theme.fg("accent", this.theme.bold(this.title)));
|
|
158
|
+
this.statusText?.setText(this.theme.fg("muted", this.status ?? ""));
|
|
159
|
+
const hints = [
|
|
160
|
+
navigationHint(this.theme, this.keybindings),
|
|
161
|
+
hint(this.theme, this.keybindings, "tui.select.confirm", "select"),
|
|
162
|
+
hint(this.theme, this.keybindings, "tui.select.cancel", "back"),
|
|
163
|
+
].filter(Boolean);
|
|
164
|
+
this.hintText.setText(hints.join(this.theme.fg("muted", " • ")));
|
|
165
|
+
}
|
|
112
166
|
updateList() {
|
|
113
167
|
this.listContainer.clear();
|
|
114
168
|
for (const [index, row] of this.rows.entries()) {
|
|
@@ -124,6 +178,11 @@ class ProfileSettingsMenuComponent extends Container {
|
|
|
124
178
|
this.selectedIndex = (this.selectedIndex + offset + this.rows.length) % this.rows.length;
|
|
125
179
|
this.updateList();
|
|
126
180
|
}
|
|
181
|
+
invalidate() {
|
|
182
|
+
this.updateStaticText();
|
|
183
|
+
this.updateList();
|
|
184
|
+
super.invalidate();
|
|
185
|
+
}
|
|
127
186
|
handleInput(data) {
|
|
128
187
|
if (this.keybindings.matches(data, "tui.select.up")) {
|
|
129
188
|
this.moveSelection(-1);
|
|
@@ -139,7 +198,7 @@ class ProfileSettingsMenuComponent extends Container {
|
|
|
139
198
|
}
|
|
140
199
|
}
|
|
141
200
|
}
|
|
142
|
-
/**
|
|
201
|
+
/** Searchable model page for one profile; confirm saves immediately. */
|
|
143
202
|
class ModelPickerComponent extends Container {
|
|
144
203
|
get focused() {
|
|
145
204
|
return this._focused;
|
|
@@ -156,9 +215,12 @@ class ModelPickerComponent extends Container {
|
|
|
156
215
|
this.tui = options.tui;
|
|
157
216
|
this.theme = options.theme;
|
|
158
217
|
this.keybindings = options.keybindings;
|
|
218
|
+
this.title = `Model — ${AGENT_PROFILE_LABELS[options.profile.name]}`;
|
|
219
|
+
this.hasScopedModels = options.scopedModels.length > 0;
|
|
159
220
|
this.addChild(new DynamicBorder());
|
|
160
221
|
this.addChild(new Spacer(1));
|
|
161
|
-
this.
|
|
222
|
+
this.titleText = new Text("", 1, 0);
|
|
223
|
+
this.addChild(this.titleText);
|
|
162
224
|
this.addChild(new Spacer(1));
|
|
163
225
|
this.modelList = new ProfileModelListComponent({
|
|
164
226
|
theme: options.theme,
|
|
@@ -171,15 +233,21 @@ class ModelPickerComponent extends Container {
|
|
|
171
233
|
});
|
|
172
234
|
this.addChild(this.modelList);
|
|
173
235
|
this.addChild(new Spacer(1));
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
options.scopedModels.length > 0 ? hint(this.theme, options.keybindings, "tui.input.tab", "scope") : "",
|
|
177
|
-
hint(this.theme, options.keybindings, "tui.select.confirm", "select"),
|
|
178
|
-
hint(this.theme, options.keybindings, "tui.select.cancel", "back"),
|
|
179
|
-
].filter(Boolean);
|
|
180
|
-
this.addChild(new Text(hints.join(this.theme.fg("muted", " • ")), 1, 0));
|
|
236
|
+
this.hintText = new Text("", 1, 0);
|
|
237
|
+
this.addChild(this.hintText);
|
|
181
238
|
this.addChild(new Spacer(1));
|
|
182
239
|
this.addChild(new DynamicBorder());
|
|
240
|
+
this.updateStaticText();
|
|
241
|
+
}
|
|
242
|
+
updateStaticText() {
|
|
243
|
+
this.titleText.setText(this.theme.fg("accent", this.theme.bold(this.title)));
|
|
244
|
+
const hints = [
|
|
245
|
+
navigationHint(this.theme, this.keybindings),
|
|
246
|
+
this.hasScopedModels ? hint(this.theme, this.keybindings, "tui.input.tab", "scope") : "",
|
|
247
|
+
hint(this.theme, this.keybindings, "tui.select.confirm", "select"),
|
|
248
|
+
hint(this.theme, this.keybindings, "tui.select.cancel", "back"),
|
|
249
|
+
].filter(Boolean);
|
|
250
|
+
this.hintText.setText(hints.join(this.theme.fg("muted", " • ")));
|
|
183
251
|
}
|
|
184
252
|
get refreshSignal() {
|
|
185
253
|
return this.refreshAbortController.signal;
|
|
@@ -206,20 +274,22 @@ class ModelPickerComponent extends Container {
|
|
|
206
274
|
this.modelList.setStatus(message, tone);
|
|
207
275
|
this.tui.requestRender();
|
|
208
276
|
}
|
|
277
|
+
invalidate() {
|
|
278
|
+
this.updateStaticText();
|
|
279
|
+
super.invalidate();
|
|
280
|
+
}
|
|
209
281
|
handleInput(data) {
|
|
210
282
|
this.modelList.handleInput(data);
|
|
211
283
|
}
|
|
212
284
|
}
|
|
213
285
|
/** Persist a single override atomically; the UI only reflects the value on success. */
|
|
214
|
-
async function persistOverride(ctx, profile, patch
|
|
215
|
-
const next = "model" in patch ? patch.model : patch.thinking;
|
|
216
|
-
if (next === current)
|
|
217
|
-
return;
|
|
286
|
+
async function persistOverride(ctx, profile, patch) {
|
|
218
287
|
try {
|
|
219
|
-
await updateProfileOverride(profile.name, patch, getAgentDir());
|
|
288
|
+
return await updateProfileOverride(profile.name, patch, getAgentDir());
|
|
220
289
|
}
|
|
221
290
|
catch (error) {
|
|
222
|
-
ctx.ui.notify(`Could not save ${
|
|
291
|
+
ctx.ui.notify(`Could not save ${AGENT_PROFILE_LABELS[profile.name]} settings: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
292
|
+
return undefined;
|
|
223
293
|
}
|
|
224
294
|
}
|
|
225
295
|
async function refreshPickerModels(ctx, picker) {
|
|
@@ -245,7 +315,7 @@ async function refreshPickerModels(ctx, picker) {
|
|
|
245
315
|
}
|
|
246
316
|
const registryError = ctx.modelRegistry.getError();
|
|
247
317
|
if (registryError) {
|
|
248
|
-
picker.setRefreshStatus(
|
|
318
|
+
picker.setRefreshStatus(truncate(registryError, MAX_REFRESH_ERROR_LENGTH), "error");
|
|
249
319
|
return;
|
|
250
320
|
}
|
|
251
321
|
picker.setRefreshStatus(undefined);
|
|
@@ -256,130 +326,290 @@ async function refreshPickerModels(ctx, picker) {
|
|
|
256
326
|
const message = timedOut
|
|
257
327
|
? "Model refresh timed out; showing cached models."
|
|
258
328
|
: `Could not refresh model catalogs: ${error instanceof Error ? error.message : String(error)}`;
|
|
259
|
-
picker.setRefreshStatus(
|
|
329
|
+
picker.setRefreshStatus(truncate(message, MAX_REFRESH_ERROR_LENGTH), "error");
|
|
260
330
|
}
|
|
261
331
|
finally {
|
|
262
332
|
clearTimeout(timeout);
|
|
263
333
|
}
|
|
264
334
|
}
|
|
335
|
+
/** One mounted /agents TUI whose child pages switch without restoring the editor between them. */
|
|
336
|
+
class AgentsMenuComponent extends Container {
|
|
337
|
+
get focused() {
|
|
338
|
+
return this._focused;
|
|
339
|
+
}
|
|
340
|
+
set focused(value) {
|
|
341
|
+
this._focused = value;
|
|
342
|
+
if (this.activeScreen && isFocusable(this.activeScreen))
|
|
343
|
+
this.activeScreen.focused = value;
|
|
344
|
+
}
|
|
345
|
+
constructor(options) {
|
|
346
|
+
super();
|
|
347
|
+
this.route = { kind: "profiles" };
|
|
348
|
+
this.selectedSettingsAction = "model";
|
|
349
|
+
this.saving = false;
|
|
350
|
+
this.closed = false;
|
|
351
|
+
this._focused = false;
|
|
352
|
+
this.tui = options.tui;
|
|
353
|
+
this.theme = options.theme;
|
|
354
|
+
this.keybindings = options.keybindings;
|
|
355
|
+
this.ctx = options.ctx;
|
|
356
|
+
this.currentThinking = options.currentThinking;
|
|
357
|
+
this.config = options.config;
|
|
358
|
+
this.onDone = options.onDone;
|
|
359
|
+
this.showProfiles();
|
|
360
|
+
}
|
|
361
|
+
disposeActiveScreen() {
|
|
362
|
+
const screen = this.activeScreen;
|
|
363
|
+
this.activeScreen = undefined;
|
|
364
|
+
if (!screen)
|
|
365
|
+
return;
|
|
366
|
+
const disposable = screen;
|
|
367
|
+
if (isFocusable(screen))
|
|
368
|
+
screen.focused = false;
|
|
369
|
+
disposable.dispose?.();
|
|
370
|
+
}
|
|
371
|
+
setActiveScreen(screen) {
|
|
372
|
+
this.disposeActiveScreen();
|
|
373
|
+
this.clear();
|
|
374
|
+
this.activeScreen = screen;
|
|
375
|
+
this.addChild(screen);
|
|
376
|
+
if (isFocusable(screen))
|
|
377
|
+
screen.focused = this._focused;
|
|
378
|
+
this.tui.requestRender();
|
|
379
|
+
}
|
|
380
|
+
showProfiles() {
|
|
381
|
+
this.route = { kind: "profiles" };
|
|
382
|
+
const choices = AGENT_PROFILES.map((profile) => ({
|
|
383
|
+
label: AGENT_PROFILE_LABELS[profile.name],
|
|
384
|
+
value: profile,
|
|
385
|
+
}));
|
|
386
|
+
const initialIndex = choices.findIndex((choice) => choice.value.name === this.selectedProfile?.name);
|
|
387
|
+
this.setActiveScreen(new ChoiceMenuComponent({
|
|
388
|
+
theme: this.theme,
|
|
389
|
+
keybindings: this.keybindings,
|
|
390
|
+
title: "Agents",
|
|
391
|
+
choices,
|
|
392
|
+
initialIndex,
|
|
393
|
+
cancelDescription: "close",
|
|
394
|
+
onDone: (choice) => {
|
|
395
|
+
if (!choice) {
|
|
396
|
+
this.onDone();
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
this.selectedProfile = choice.value;
|
|
400
|
+
this.selectedSettingsAction = "model";
|
|
401
|
+
this.showSettings(choice.value);
|
|
402
|
+
},
|
|
403
|
+
}));
|
|
404
|
+
}
|
|
405
|
+
showSettings(profile, selectedAction = this.selectedSettingsAction) {
|
|
406
|
+
this.selectedProfile = profile;
|
|
407
|
+
this.selectedSettingsAction = selectedAction;
|
|
408
|
+
this.route = { kind: "settings", profile, selectedAction };
|
|
409
|
+
this.setActiveScreen(new ProfileSettingsMenuComponent({
|
|
410
|
+
theme: this.theme,
|
|
411
|
+
keybindings: this.keybindings,
|
|
412
|
+
profile,
|
|
413
|
+
override: this.config.profiles[profile.name],
|
|
414
|
+
models: this.ctx.modelRegistry.getAvailable(),
|
|
415
|
+
currentSessionModel: this.ctx.model,
|
|
416
|
+
currentThinking: this.currentThinking,
|
|
417
|
+
selectedAction,
|
|
418
|
+
status: this.saving ? "Saving…" : undefined,
|
|
419
|
+
onDone: (action) => {
|
|
420
|
+
if (!action) {
|
|
421
|
+
this.showProfiles();
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
this.selectedSettingsAction = action;
|
|
425
|
+
if (action === "model")
|
|
426
|
+
this.showModelPicker(profile);
|
|
427
|
+
else
|
|
428
|
+
this.showThinkingPicker(profile);
|
|
429
|
+
},
|
|
430
|
+
}));
|
|
431
|
+
}
|
|
432
|
+
showModelPicker(profile) {
|
|
433
|
+
this.route = { kind: "model", profile };
|
|
434
|
+
const override = this.config.profiles[profile.name];
|
|
435
|
+
const picker = new ModelPickerComponent({
|
|
436
|
+
tui: this.tui,
|
|
437
|
+
theme: this.theme,
|
|
438
|
+
keybindings: this.keybindings,
|
|
439
|
+
profile,
|
|
440
|
+
savedModelId: override?.model,
|
|
441
|
+
models: this.ctx.modelRegistry.getAvailable(),
|
|
442
|
+
scopedModels: this.ctx.scopedModels,
|
|
443
|
+
currentSessionModel: this.ctx.model,
|
|
444
|
+
onDone: (choice) => {
|
|
445
|
+
if (!choice) {
|
|
446
|
+
this.showSettings(profile, "model");
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
this.saveOverride(profile, { model: choice.modelId }, "model");
|
|
450
|
+
},
|
|
451
|
+
});
|
|
452
|
+
picker.setRefreshStatus("Refreshing model catalogs…");
|
|
453
|
+
this.setActiveScreen(picker);
|
|
454
|
+
void refreshPickerModels(this.ctx, picker);
|
|
455
|
+
}
|
|
456
|
+
showThinkingPicker(profile) {
|
|
457
|
+
this.route = { kind: "thinking", profile };
|
|
458
|
+
const override = this.config.profiles[profile.name];
|
|
459
|
+
const choices = [
|
|
460
|
+
...buildThinkingChoices({
|
|
461
|
+
currentSessionModel: this.ctx.model,
|
|
462
|
+
models: this.ctx.modelRegistry.getAvailable(),
|
|
463
|
+
override,
|
|
464
|
+
currentThinking: this.currentThinking,
|
|
465
|
+
}).entries(),
|
|
466
|
+
].map(([label, level]) => ({ label, value: level }));
|
|
467
|
+
const initialIndex = choices.findIndex((choice) => choice.label.endsWith(" ✓"));
|
|
468
|
+
this.setActiveScreen(new ChoiceMenuComponent({
|
|
469
|
+
theme: this.theme,
|
|
470
|
+
keybindings: this.keybindings,
|
|
471
|
+
title: `Thinking — ${AGENT_PROFILE_LABELS[profile.name]}`,
|
|
472
|
+
choices,
|
|
473
|
+
initialIndex,
|
|
474
|
+
cancelDescription: "back",
|
|
475
|
+
onDone: (choice) => {
|
|
476
|
+
if (!choice) {
|
|
477
|
+
this.showSettings(profile, "thinking");
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
this.saveOverride(profile, { thinking: choice.value }, "thinking");
|
|
481
|
+
},
|
|
482
|
+
}));
|
|
483
|
+
}
|
|
484
|
+
saveOverride(profile, patch, returnAction) {
|
|
485
|
+
if (this.saving)
|
|
486
|
+
return;
|
|
487
|
+
this.saving = true;
|
|
488
|
+
this.showSettings(profile, returnAction);
|
|
489
|
+
void persistOverride(this.ctx, profile, patch)
|
|
490
|
+
.then((config) => this.completeSave(profile, config), () => this.completeSave(profile, undefined))
|
|
491
|
+
.catch(() => {
|
|
492
|
+
// A reload can stale the command context while the queued file write finishes.
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
completeSave(profile, config) {
|
|
496
|
+
if (this.closed)
|
|
497
|
+
return;
|
|
498
|
+
this.saving = false;
|
|
499
|
+
if (config)
|
|
500
|
+
this.config = config;
|
|
501
|
+
if (this.route.kind === "settings" && this.route.profile.name === profile.name) {
|
|
502
|
+
this.showSettings(profile, this.route.selectedAction);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
handleInput(data) {
|
|
506
|
+
if (!this.saving) {
|
|
507
|
+
this.activeScreen?.handleInput?.(data);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
if (!this.keybindings.matches(data, "tui.select.cancel"))
|
|
511
|
+
return;
|
|
512
|
+
if (this.route.kind === "settings")
|
|
513
|
+
this.showProfiles();
|
|
514
|
+
else if (this.route.kind === "profiles")
|
|
515
|
+
this.onDone();
|
|
516
|
+
}
|
|
517
|
+
dispose() {
|
|
518
|
+
this.closed = true;
|
|
519
|
+
this.disposeActiveScreen();
|
|
520
|
+
this.clear();
|
|
521
|
+
}
|
|
522
|
+
}
|
|
265
523
|
async function selectProfile(ctx) {
|
|
266
|
-
const labels = AGENT_PROFILES.map((profile) =>
|
|
524
|
+
const labels = AGENT_PROFILES.map((profile) => AGENT_PROFILE_LABELS[profile.name]);
|
|
267
525
|
const label = await ctx.ui.select("Agents", labels);
|
|
268
|
-
return label === undefined
|
|
526
|
+
return label === undefined
|
|
527
|
+
? undefined
|
|
528
|
+
: AGENT_PROFILES.find((profile) => AGENT_PROFILE_LABELS[profile.name] === label);
|
|
269
529
|
}
|
|
270
|
-
async function
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
keybindings,
|
|
274
|
-
profile,
|
|
530
|
+
async function showDialogSettingsMenu(ctx, profile, override, currentThinking) {
|
|
531
|
+
const options = new Map();
|
|
532
|
+
for (const row of buildSettingsRows({
|
|
275
533
|
override,
|
|
276
534
|
models: ctx.modelRegistry.getAvailable(),
|
|
277
535
|
currentSessionModel: ctx.model,
|
|
278
536
|
currentThinking,
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const models = ctx.modelRegistry.getAvailable();
|
|
284
|
-
const effective = effectiveModel(ctx.model, models, override);
|
|
285
|
-
const modelValue = override?.model
|
|
286
|
-
? `override — ${override.model}${findModel(models, override.model) ? "" : " [unavailable]"}`
|
|
287
|
-
: `inherit — ${modelId(ctx.model)}`;
|
|
288
|
-
const thinkingValue = override?.thinking
|
|
289
|
-
? `override — ${override.thinking}`
|
|
290
|
-
: `inherit — ${inheritedThinking(effective, currentThinking)}`;
|
|
291
|
-
const options = new Map();
|
|
292
|
-
options.set(`Model — ${modelValue}`, "model");
|
|
293
|
-
options.set(`Thinking — ${thinkingValue}`, "thinking");
|
|
294
|
-
const label = await ctx.ui.select(PROFILE_LABELS[profile.name], [...options.keys()]);
|
|
537
|
+
})) {
|
|
538
|
+
options.set(`${row.label} — ${row.value}`, row.action);
|
|
539
|
+
}
|
|
540
|
+
const label = await ctx.ui.select(AGENT_PROFILE_LABELS[profile.name], [...options.keys()]);
|
|
295
541
|
return label === undefined ? undefined : options.get(label);
|
|
296
542
|
}
|
|
297
|
-
async function showTuiModelPicker(ctx, profile, override) {
|
|
298
|
-
const chosen = await ctx.ui.custom((tui, theme, keybindings, done) => {
|
|
299
|
-
const picker = new ModelPickerComponent({
|
|
300
|
-
tui,
|
|
301
|
-
theme,
|
|
302
|
-
keybindings,
|
|
303
|
-
profile,
|
|
304
|
-
savedModelId: override?.model,
|
|
305
|
-
models: ctx.modelRegistry.getAvailable(),
|
|
306
|
-
scopedModels: ctx.scopedModels,
|
|
307
|
-
currentSessionModel: ctx.model,
|
|
308
|
-
onDone: done,
|
|
309
|
-
});
|
|
310
|
-
picker.setRefreshStatus("Refreshing model catalogs…");
|
|
311
|
-
void refreshPickerModels(ctx, picker);
|
|
312
|
-
return picker;
|
|
313
|
-
});
|
|
314
|
-
return chosen === undefined ? undefined : { modelId: chosen.modelId };
|
|
315
|
-
}
|
|
316
543
|
async function showDialogModelPicker(ctx, profile, override) {
|
|
317
|
-
const models = ctx.modelRegistry.getAvailable().sort(
|
|
318
|
-
|
|
319
|
-
|
|
544
|
+
const models = [...ctx.modelRegistry.getAvailable()].sort(compareModels);
|
|
545
|
+
const choices = buildModelChoices({
|
|
546
|
+
models,
|
|
547
|
+
currentSessionModel: ctx.model,
|
|
548
|
+
savedModelId: override?.model,
|
|
320
549
|
});
|
|
321
|
-
const
|
|
322
|
-
choices.set(`inherit (${modelId(ctx.model)})${override?.model ? "" : " ✓"}`, undefined);
|
|
323
|
-
if (override?.model && !findModel(models, override.model)) {
|
|
324
|
-
choices.set(`${override.model} [unavailable] ✓`, override.model);
|
|
325
|
-
}
|
|
326
|
-
for (const model of models) {
|
|
327
|
-
const id = modelId(model);
|
|
328
|
-
choices.set(`${id}${override?.model === id ? " ✓" : ""}`, id);
|
|
329
|
-
}
|
|
330
|
-
const label = await ctx.ui.select(`Model — ${PROFILE_LABELS[profile.name]}`, [...choices.keys()]);
|
|
550
|
+
const label = await ctx.ui.select(`Model — ${AGENT_PROFILE_LABELS[profile.name]}`, [...choices.keys()]);
|
|
331
551
|
return label === undefined ? undefined : { modelId: choices.get(label) };
|
|
332
552
|
}
|
|
333
|
-
async function
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
choices.set(`${level}${override?.thinking === level ? " ✓" : ""}`, level);
|
|
342
|
-
}
|
|
343
|
-
const label = await ctx.ui.select(`Thinking — ${PROFILE_LABELS[profile.name]}`, [...choices.keys()]);
|
|
553
|
+
async function showDialogThinkingPicker(ctx, profile, override, currentThinking) {
|
|
554
|
+
const choices = buildThinkingChoices({
|
|
555
|
+
currentSessionModel: ctx.model,
|
|
556
|
+
models: ctx.modelRegistry.getAvailable(),
|
|
557
|
+
override,
|
|
558
|
+
currentThinking,
|
|
559
|
+
});
|
|
560
|
+
const label = await ctx.ui.select(`Thinking — ${AGENT_PROFILE_LABELS[profile.name]}`, [...choices.keys()]);
|
|
344
561
|
return label === undefined ? undefined : { level: choices.get(label) };
|
|
345
562
|
}
|
|
346
|
-
async function
|
|
563
|
+
async function runDialogProfileSettings(ctx, profile, currentThinking) {
|
|
347
564
|
const agentDir = getAgentDir();
|
|
348
|
-
const isTui = ctx.mode === "tui";
|
|
349
565
|
while (true) {
|
|
350
566
|
const config = await loadSubagentConfig(agentDir);
|
|
351
567
|
const override = config.profiles[profile.name];
|
|
352
|
-
const action =
|
|
353
|
-
? await showTuiSettingsMenu(ctx, profile, override, currentThinking)
|
|
354
|
-
: await showDialogSettingsMenu(ctx, profile, override, currentThinking);
|
|
568
|
+
const action = await showDialogSettingsMenu(ctx, profile, override, currentThinking);
|
|
355
569
|
if (!action)
|
|
356
570
|
return;
|
|
357
571
|
if (action === "model") {
|
|
358
|
-
const chosen =
|
|
359
|
-
? await showTuiModelPicker(ctx, profile, override)
|
|
360
|
-
: await showDialogModelPicker(ctx, profile, override);
|
|
572
|
+
const chosen = await showDialogModelPicker(ctx, profile, override);
|
|
361
573
|
if (!chosen)
|
|
362
574
|
continue;
|
|
363
|
-
await persistOverride(ctx, profile, { model: chosen.modelId }
|
|
575
|
+
await persistOverride(ctx, profile, { model: chosen.modelId });
|
|
364
576
|
}
|
|
365
577
|
else {
|
|
366
|
-
const chosen = await
|
|
578
|
+
const chosen = await showDialogThinkingPicker(ctx, profile, override, currentThinking);
|
|
367
579
|
if (!chosen)
|
|
368
580
|
continue;
|
|
369
|
-
await persistOverride(ctx, profile, { thinking: chosen.level }
|
|
581
|
+
await persistOverride(ctx, profile, { thinking: chosen.level });
|
|
370
582
|
}
|
|
371
583
|
}
|
|
372
584
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
585
|
+
async function showTuiAgentsCommand(ctx, currentThinking) {
|
|
586
|
+
const config = await loadSubagentConfig(getAgentDir());
|
|
587
|
+
await ctx.ui.custom((tui, theme, keybindings, done) => new AgentsMenuComponent({
|
|
588
|
+
tui,
|
|
589
|
+
theme,
|
|
590
|
+
keybindings,
|
|
591
|
+
ctx,
|
|
592
|
+
currentThinking,
|
|
593
|
+
config,
|
|
594
|
+
onDone: () => done(),
|
|
595
|
+
}));
|
|
596
|
+
}
|
|
597
|
+
async function showDialogAgentsCommand(ctx, currentThinking) {
|
|
378
598
|
while (true) {
|
|
379
599
|
const profile = await selectProfile(ctx);
|
|
380
600
|
if (!profile)
|
|
381
601
|
return;
|
|
382
|
-
await
|
|
602
|
+
await runDialogProfileSettings(ctx, profile, currentThinking);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
export async function showAgentsCommand(ctx, currentThinking) {
|
|
606
|
+
if (!ctx.hasUI) {
|
|
607
|
+
ctx.ui.notify("/agents requires an interactive UI.", "warning");
|
|
608
|
+
return;
|
|
383
609
|
}
|
|
610
|
+
if (ctx.mode === "tui")
|
|
611
|
+
await showTuiAgentsCommand(ctx, currentThinking);
|
|
612
|
+
else
|
|
613
|
+
await showDialogAgentsCommand(ctx, currentThinking);
|
|
384
614
|
}
|
|
385
615
|
//# sourceMappingURL=agents-command.js.map
|