pi-advisor-flow 0.6.0 → 0.8.0
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 +34 -17
- package/README.md +10 -8
- package/dist/index.js +2852 -868
- package/extensions/index.ts +2 -1
- package/package.json +55 -40
- package/src/attachments.ts +11 -10
- package/src/commands/activation-preparation.ts +1 -0
- package/src/commands/activation.ts +2 -0
- package/src/commands/lifecycle.ts +1 -0
- package/src/commands/manual-command.ts +7 -1
- package/src/commands/manual-consultation.ts +103 -79
- package/src/commands/manual-progress.ts +7 -5
- package/src/commands/model-commands.ts +1 -1
- package/src/commands/model-options.ts +14 -3
- package/src/commands/model-picker.ts +1 -0
- package/src/commands/registration.ts +1 -0
- package/src/commands/renderers.ts +67 -55
- package/src/commands/runtime.ts +16 -14
- package/src/commands/settings-commands.ts +10 -4
- package/src/commands/settings-persistence.ts +64 -2
- package/src/commands/types.ts +2 -0
- package/src/commands.ts +0 -1
- package/src/config/args.ts +1 -1
- package/src/config/defaults.ts +83 -1
- package/src/config/schema.ts +152 -15
- package/src/config/state.ts +80 -2
- package/src/config/storage.ts +38 -22
- package/src/config/types.ts +45 -3
- package/src/config/validation.ts +33 -21
- package/src/config.ts +3 -0
- package/src/content-utils.ts +24 -4
- package/src/conversation.ts +16 -15
- package/src/git.ts +10 -3
- package/src/herdr-block.ts +83 -0
- package/src/herdr-shared.ts +108 -0
- package/src/herdr.ts +30 -192
- package/src/jev/client.ts +338 -0
- package/src/jev/failure.ts +19 -0
- package/src/jev/key-store.ts +267 -0
- package/src/jev/ledger.ts +228 -0
- package/src/jev/questions.ts +122 -0
- package/src/jev/state.ts +50 -0
- package/src/jev/transport.ts +63 -0
- package/src/model-stream.ts +35 -37
- package/src/outcomes.ts +20 -12
- package/src/pi-settings.ts +4 -9
- package/src/preferences.ts +4 -2
- package/src/redaction.ts +10 -12
- package/src/scout-context.ts +59 -60
- package/src/scout-curation.ts +4 -13
- package/src/scout-groups.ts +178 -175
- package/src/scout-protocol.ts +74 -70
- package/src/scout-reconstruct.ts +1 -1
- package/src/scout.ts +115 -73
- package/src/session-state.ts +148 -39
- package/src/tool-result-cap.ts +18 -17
- package/src/tools/consult-context.ts +7 -15
- package/src/tools/consultation.ts +14 -4
- package/src/tools/gate-policy.ts +1 -0
- package/src/tools/gate-protocol.ts +36 -23
- package/src/tools/jev-filter.ts +204 -0
- package/src/tools/jev-turn-gate.ts +203 -0
- package/src/tools/loop-gate.ts +32 -20
- package/src/tools/model-access.ts +46 -0
- package/src/tools/outage-notifier.ts +30 -0
- package/src/tools/prompts.ts +20 -25
- package/src/tools/register-ask-advisor.ts +73 -17
- package/src/tools/register-lifecycle.ts +22 -0
- package/src/tools/register-outcome.ts +2 -0
- package/src/tools/register-renderers.ts +149 -73
- package/src/tools/registration.ts +15 -0
- package/src/tools/render-advisor-result.ts +77 -35
- package/src/tools/render-common.ts +16 -19
- package/src/tools/scout-status.ts +42 -27
- package/src/tools/types.ts +11 -0
- package/src/tools.ts +0 -1
- package/src/ui/jev-setup-submenu.ts +350 -0
- package/src/ui/manual-dialog-render.ts +1 -0
- package/src/ui/manual-dialog.ts +39 -43
- package/src/ui/masked-input.ts +62 -0
- package/src/ui/model-multi-selector.ts +9 -0
- package/src/ui/model-selector-adapter.ts +31 -0
- package/src/ui/model-selector.ts +14 -161
- package/src/ui/searchable-model-list.ts +230 -0
- package/src/ui/settings-formatting.ts +24 -14
- package/src/ui/settings-items.ts +200 -2
- package/src/ui/settings-list-adapter.ts +15 -12
- package/src/ui/settings-mutations.ts +169 -47
- package/src/ui/settings-selector.ts +18 -9
- package/src/ui/text-setting-submenu.ts +3 -6
- package/src/ui/types.ts +25 -0
- package/src/ui.ts +0 -1
- package/src/usage.ts +29 -20
package/src/ui/settings-items.ts
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import
|
|
2
|
+
import { getKeybindings } from "@earendil-works/pi-tui";
|
|
3
|
+
import type { KeybindingsManager, SettingItem } from "@earendil-works/pi-tui";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_JEV_DIGEST_MAX_CHARS,
|
|
7
|
+
DEFAULT_JEV_FILTER_NOUL_MARGIN,
|
|
8
|
+
DEFAULT_JEV_FILTER_OVERRIDE_WINDOW,
|
|
9
|
+
DEFAULT_JEV_FILTER_SKIP_CONFIDENCE,
|
|
10
|
+
DEFAULT_JEV_MODEL,
|
|
11
|
+
DEFAULT_JEV_PRICE_PER_MTOK,
|
|
12
|
+
DEFAULT_JEV_TIMEOUT_MS,
|
|
13
|
+
DEFAULT_JEV_TRANSPORT,
|
|
14
|
+
DEFAULT_JEV_TURN_GATE_NOUL_THRESHOLD,
|
|
15
|
+
} from "../config/types.ts";
|
|
3
16
|
import { isValidAdvisorToolPolicies } from "../config/validation.ts";
|
|
17
|
+
import { JevSetupSubmenu } from "./jev-setup-submenu.ts";
|
|
18
|
+
import { SearchableModelMultiSelector } from "./model-multi-selector.ts";
|
|
4
19
|
import {
|
|
5
20
|
contextDescription,
|
|
6
21
|
currentContextLabel,
|
|
@@ -20,6 +35,7 @@ import type {
|
|
|
20
35
|
|
|
21
36
|
export interface SettingsItemsOptions {
|
|
22
37
|
effortLevels: string[];
|
|
38
|
+
modelWhitelist: SettingItem;
|
|
23
39
|
presets: ContextPreset[];
|
|
24
40
|
settings: AdvisorSettings;
|
|
25
41
|
theme: Theme;
|
|
@@ -40,8 +56,187 @@ const toggle = (
|
|
|
40
56
|
values: TOGGLE_VALUES,
|
|
41
57
|
});
|
|
42
58
|
|
|
59
|
+
const jevItems = (
|
|
60
|
+
settings: AdvisorSettings,
|
|
61
|
+
theme: Theme,
|
|
62
|
+
tui: RenderRequester
|
|
63
|
+
): SettingItem[] => [
|
|
64
|
+
{
|
|
65
|
+
currentValue: settingValue(settings.jevFilterEnabled, false),
|
|
66
|
+
description:
|
|
67
|
+
"Screen low-stakes ask_advisor consultations with Jev; guided setup verifies credentials.",
|
|
68
|
+
id: "jevFilter",
|
|
69
|
+
label: "Jev consultation filter",
|
|
70
|
+
submenu: (currentValue, done) =>
|
|
71
|
+
new JevSetupSubmenu({ currentValue, done, theme, tui }),
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
currentValue: String(
|
|
75
|
+
settings.jevFilterSkipConfidence ?? DEFAULT_JEV_FILTER_SKIP_CONFIDENCE
|
|
76
|
+
),
|
|
77
|
+
description:
|
|
78
|
+
"Required probability on negligible stakes before a consultation is skipped.",
|
|
79
|
+
id: "jevFilterSkipConfidence",
|
|
80
|
+
label: "Jev skip confidence",
|
|
81
|
+
values: numericValues(
|
|
82
|
+
settings.jevFilterSkipConfidence ?? DEFAULT_JEV_FILTER_SKIP_CONFIDENCE,
|
|
83
|
+
[0.6, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95]
|
|
84
|
+
),
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
currentValue: String(
|
|
88
|
+
settings.jevFilterNoulMargin ?? DEFAULT_JEV_FILTER_NOUL_MARGIN
|
|
89
|
+
),
|
|
90
|
+
description:
|
|
91
|
+
"Extra margin over a coin flip required on self-answerability before skipping.",
|
|
92
|
+
id: "jevFilterNoulMargin",
|
|
93
|
+
label: "Jev Noul margin",
|
|
94
|
+
values: numericValues(
|
|
95
|
+
settings.jevFilterNoulMargin ?? DEFAULT_JEV_FILTER_NOUL_MARGIN,
|
|
96
|
+
[0.1, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45]
|
|
97
|
+
),
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
currentValue: `${settings.jevFilterOverrideWindow ?? DEFAULT_JEV_FILTER_OVERRIDE_WINDOW} turns`,
|
|
101
|
+
description:
|
|
102
|
+
"Turns after a skip during which the same question passes automatically.",
|
|
103
|
+
id: "jevFilterOverrideWindow",
|
|
104
|
+
label: "Jev override window",
|
|
105
|
+
values: numericValues(
|
|
106
|
+
settings.jevFilterOverrideWindow ?? DEFAULT_JEV_FILTER_OVERRIDE_WINDOW,
|
|
107
|
+
[0, 3, 5, 10, 20, 50]
|
|
108
|
+
).map((value) => `${value} turns`),
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
currentValue: settings.jevModel ?? DEFAULT_JEV_MODEL,
|
|
112
|
+
description: "TypeSafe Jev model used for screening and turn-gate checks.",
|
|
113
|
+
id: "jevModel",
|
|
114
|
+
label: "Jev model",
|
|
115
|
+
submenu: (_currentValue, done) =>
|
|
116
|
+
new TextSettingSubmenu({
|
|
117
|
+
description:
|
|
118
|
+
"Enter a TypeSafe model name (for example jev-latest or jev-1.13.0).",
|
|
119
|
+
initial: settings.jevModel ?? DEFAULT_JEV_MODEL,
|
|
120
|
+
onCancel: done,
|
|
121
|
+
onSubmit: (value) => ({ value: value.trim() || DEFAULT_JEV_MODEL }),
|
|
122
|
+
theme,
|
|
123
|
+
title: "Jev model",
|
|
124
|
+
tui,
|
|
125
|
+
}),
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
currentValue: String(settings.jevTimeoutMs ?? DEFAULT_JEV_TIMEOUT_MS),
|
|
129
|
+
description: "Total wall-time budget for one Jev call, including retries.",
|
|
130
|
+
id: "jevTimeoutMs",
|
|
131
|
+
label: "Jev timeout ms",
|
|
132
|
+
values: numericValues(
|
|
133
|
+
settings.jevTimeoutMs ?? DEFAULT_JEV_TIMEOUT_MS,
|
|
134
|
+
[1000, 2000, 5000, 8000, 15_000, 30_000]
|
|
135
|
+
),
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
currentValue: String(
|
|
139
|
+
settings.jevDigestMaxChars ?? DEFAULT_JEV_DIGEST_MAX_CHARS
|
|
140
|
+
),
|
|
141
|
+
description: "Conversation characters sent to Jev as screening evidence.",
|
|
142
|
+
id: "jevDigestMaxChars",
|
|
143
|
+
label: "Jev digest chars",
|
|
144
|
+
values: numericValues(
|
|
145
|
+
settings.jevDigestMaxChars ?? DEFAULT_JEV_DIGEST_MAX_CHARS,
|
|
146
|
+
[0, 1000, 2000, 4000, 8000, 15_000]
|
|
147
|
+
),
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
currentValue: String(
|
|
151
|
+
settings.jevPricePerMtok ?? DEFAULT_JEV_PRICE_PER_MTOK
|
|
152
|
+
),
|
|
153
|
+
description:
|
|
154
|
+
"Assumed TypeSafe price per million input tokens for cost lines.",
|
|
155
|
+
id: "jevPricePerMtok",
|
|
156
|
+
label: "Jev price/Mtok",
|
|
157
|
+
values: numericValues(
|
|
158
|
+
settings.jevPricePerMtok ?? DEFAULT_JEV_PRICE_PER_MTOK,
|
|
159
|
+
[0.01, 0.02, 0.042, 0.05, 0.1]
|
|
160
|
+
),
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
currentValue:
|
|
164
|
+
settings.jevTurnGateEveryTurns === 0 ||
|
|
165
|
+
settings.jevTurnGateEveryTurns === undefined
|
|
166
|
+
? "Off"
|
|
167
|
+
: `every ${settings.jevTurnGateEveryTurns} turns`,
|
|
168
|
+
description:
|
|
169
|
+
"Proactively consult the Advisor every N turns without a consultation (0 = off).",
|
|
170
|
+
id: "jevTurnGateEveryTurns",
|
|
171
|
+
label: "Jev turn gate",
|
|
172
|
+
values: [
|
|
173
|
+
"Off",
|
|
174
|
+
"every 3 turns",
|
|
175
|
+
"every 5 turns",
|
|
176
|
+
"every 10 turns",
|
|
177
|
+
"every 20 turns",
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
currentValue: String(
|
|
182
|
+
settings.jevTurnGateNoulThreshold ?? DEFAULT_JEV_TURN_GATE_NOUL_THRESHOLD
|
|
183
|
+
),
|
|
184
|
+
description:
|
|
185
|
+
"Jev confidence required before the turn gate interrupts with advice.",
|
|
186
|
+
id: "jevTurnGateNoulThreshold",
|
|
187
|
+
label: "Jev turn-gate threshold",
|
|
188
|
+
values: numericValues(
|
|
189
|
+
settings.jevTurnGateNoulThreshold ?? DEFAULT_JEV_TURN_GATE_NOUL_THRESHOLD,
|
|
190
|
+
[0.6, 0.7, 0.8, 0.85, 0.9, 0.95]
|
|
191
|
+
),
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
currentValue: settings.jevTransport ?? DEFAULT_JEV_TRANSPORT,
|
|
195
|
+
description:
|
|
196
|
+
"How Jev calls travel: auto reuses an OpenRouter login when no TypeSafe key is set.",
|
|
197
|
+
id: "jevTransport",
|
|
198
|
+
label: "Jev transport",
|
|
199
|
+
values: withCurrentValue(settings.jevTransport ?? DEFAULT_JEV_TRANSPORT, [
|
|
200
|
+
"auto",
|
|
201
|
+
"typesafe",
|
|
202
|
+
"openrouter",
|
|
203
|
+
]),
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
export const advisorModelWhitelistItem = (
|
|
208
|
+
settings: AdvisorSettings,
|
|
209
|
+
modelRefs: string[] | undefined,
|
|
210
|
+
keybindings: KeybindingsManager | undefined,
|
|
211
|
+
theme: Theme,
|
|
212
|
+
tui: RenderRequester
|
|
213
|
+
): SettingItem => ({
|
|
214
|
+
currentValue: settings.modelWhitelist?.length
|
|
215
|
+
? settings.modelWhitelist.join(", ")
|
|
216
|
+
: "Any model",
|
|
217
|
+
description:
|
|
218
|
+
"Only the exact provider/model references listed here may call the Advisor; an empty list allows every model.",
|
|
219
|
+
id: "modelWhitelist",
|
|
220
|
+
label: "Advisor model whitelist",
|
|
221
|
+
submenu: (_currentValue, done) =>
|
|
222
|
+
new SearchableModelMultiSelector({
|
|
223
|
+
allOptions: [
|
|
224
|
+
...new Set([...(modelRefs ?? []), ...(settings.modelWhitelist ?? [])]),
|
|
225
|
+
],
|
|
226
|
+
currentOptions: settings.modelWhitelist ?? [],
|
|
227
|
+
keybindings: keybindings ?? getKeybindings(),
|
|
228
|
+
multiSelect: true,
|
|
229
|
+
onCancel: done,
|
|
230
|
+
onSelect: (values) => done(values.join(",")),
|
|
231
|
+
theme,
|
|
232
|
+
title: "Advisor model whitelist",
|
|
233
|
+
tui,
|
|
234
|
+
}),
|
|
235
|
+
});
|
|
236
|
+
|
|
43
237
|
export const createSettingsItems = ({
|
|
44
238
|
effortLevels,
|
|
239
|
+
modelWhitelist,
|
|
45
240
|
presets,
|
|
46
241
|
settings,
|
|
47
242
|
theme,
|
|
@@ -73,6 +268,7 @@ export const createSettingsItems = ({
|
|
|
73
268
|
},
|
|
74
269
|
];
|
|
75
270
|
if (settings.simpleMode) {
|
|
271
|
+
items.push(modelWhitelist);
|
|
76
272
|
return items;
|
|
77
273
|
}
|
|
78
274
|
|
|
@@ -84,6 +280,7 @@ export const createSettingsItems = ({
|
|
|
84
280
|
label: "Advisor reasoning",
|
|
85
281
|
values: withCurrentValue(currentEffort(settings.effort), effortLevels),
|
|
86
282
|
},
|
|
283
|
+
modelWhitelist,
|
|
87
284
|
toggle(
|
|
88
285
|
"scoutEnabled",
|
|
89
286
|
"Experimental Advisor Scout",
|
|
@@ -317,7 +514,8 @@ export const createSettingsItems = ({
|
|
|
317
514
|
"Allow anonymized Advisor outcomes to be logged globally.",
|
|
318
515
|
settings.outcomeLogging,
|
|
319
516
|
false
|
|
320
|
-
)
|
|
517
|
+
),
|
|
518
|
+
...jevItems(settings, theme, tui)
|
|
321
519
|
);
|
|
322
520
|
return items;
|
|
323
521
|
};
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
type SettingsList,
|
|
1
|
+
import { Key, matchesKey } from "@earendil-works/pi-tui";
|
|
2
|
+
import type {
|
|
3
|
+
Component,
|
|
4
|
+
Focusable,
|
|
5
|
+
Input,
|
|
6
|
+
SettingItem,
|
|
7
|
+
SettingsList,
|
|
9
8
|
} from "@earendil-works/pi-tui";
|
|
10
9
|
|
|
11
10
|
interface SettingsListPrivateFields {
|
|
@@ -15,6 +14,8 @@ interface SettingsListPrivateFields {
|
|
|
15
14
|
submenuComponent?: Component | null;
|
|
16
15
|
}
|
|
17
16
|
|
|
17
|
+
type SettingsListWithPrivateFields = SettingsList & SettingsListPrivateFields;
|
|
18
|
+
|
|
18
19
|
/**
|
|
19
20
|
* Contains the narrow compatibility access needed for pi-tui's SettingsList.
|
|
20
21
|
* No other UI module should depend on its private fields.
|
|
@@ -45,6 +46,7 @@ export class SettingsListAdapter {
|
|
|
45
46
|
|
|
46
47
|
setFocused(value: boolean): void {
|
|
47
48
|
const fields = this.privateFields();
|
|
49
|
+
// SAFETY: a SettingsList submenu component is registered only when focusable.
|
|
48
50
|
const submenu = fields.submenuComponent as Focusable | undefined;
|
|
49
51
|
if (submenu) {
|
|
50
52
|
submenu.focused = value;
|
|
@@ -53,7 +55,7 @@ export class SettingsListAdapter {
|
|
|
53
55
|
|
|
54
56
|
setSelectedId(items: SettingItem[], selectedId: string): void {
|
|
55
57
|
const selectedIndex = items.findIndex((item) => item.id === selectedId);
|
|
56
|
-
if (selectedIndex
|
|
58
|
+
if (selectedIndex !== -1) {
|
|
57
59
|
this.privateFields().selectedIndex = selectedIndex;
|
|
58
60
|
}
|
|
59
61
|
}
|
|
@@ -63,9 +65,9 @@ export class SettingsListAdapter {
|
|
|
63
65
|
onChange: (id: string, value: string) => void
|
|
64
66
|
): boolean {
|
|
65
67
|
let direction = 0;
|
|
66
|
-
if (matchesKey(keyData, Key.left) || keyData === "\
|
|
68
|
+
if (matchesKey(keyData, Key.left) || keyData === "\u001B[D") {
|
|
67
69
|
direction = -1;
|
|
68
|
-
} else if (matchesKey(keyData, Key.right) || keyData === "\
|
|
70
|
+
} else if (matchesKey(keyData, Key.right) || keyData === "\u001B[C") {
|
|
69
71
|
direction = 1;
|
|
70
72
|
}
|
|
71
73
|
if (direction === 0) {
|
|
@@ -98,6 +100,7 @@ export class SettingsListAdapter {
|
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
private privateFields(): SettingsListPrivateFields {
|
|
101
|
-
|
|
103
|
+
// SAFETY: compatibility-only read of SettingsList's private layout fields.
|
|
104
|
+
return this.list as SettingsListWithPrivateFields;
|
|
102
105
|
}
|
|
103
106
|
}
|
|
@@ -1,80 +1,202 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { DEFAULT_JEV_MODEL } from "../config/types.ts";
|
|
2
|
+
import type { AdvisorSettings, ContextPreset } from "./types.ts";
|
|
2
3
|
|
|
3
|
-
const
|
|
4
|
-
"scoutEnabled",
|
|
5
|
-
"showUsageDetails",
|
|
6
|
-
"showUsageFooter",
|
|
7
|
-
"planGate",
|
|
8
|
-
"failureGate",
|
|
9
|
-
"completionGate",
|
|
10
|
-
"collapseResponses",
|
|
11
|
-
"blockOnBlocked",
|
|
4
|
+
const BOOLEAN_SETTING_FIELDS = [
|
|
12
5
|
"autoLoopGate",
|
|
13
|
-
"
|
|
6
|
+
"blockOnBlocked",
|
|
7
|
+
"collapseResponses",
|
|
8
|
+
"completionGate",
|
|
9
|
+
"failureGate",
|
|
14
10
|
"herdrIntegration",
|
|
11
|
+
"outcomeLogging",
|
|
12
|
+
"planGate",
|
|
15
13
|
"redactSecrets",
|
|
14
|
+
"scoutEnabled",
|
|
15
|
+
"sessionSummary",
|
|
16
|
+
"showUsageDetails",
|
|
17
|
+
"showUsageFooter",
|
|
16
18
|
"trackedFileContent",
|
|
17
19
|
"untrackedContent",
|
|
18
|
-
|
|
19
|
-
]);
|
|
20
|
+
] as const;
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
const BOOLEAN_SETTING_IDS = new Set<string>(BOOLEAN_SETTING_FIELDS);
|
|
23
|
+
|
|
24
|
+
type BooleanSettingField = (typeof BOOLEAN_SETTING_FIELDS)[number];
|
|
25
|
+
|
|
26
|
+
const parseModelWhitelist = (value: string) => [
|
|
27
|
+
...new Set(
|
|
28
|
+
value
|
|
29
|
+
.split(",")
|
|
30
|
+
.map((model) => model.trim())
|
|
31
|
+
.filter(Boolean)
|
|
32
|
+
),
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const applyCoreMutation = (
|
|
22
36
|
settings: AdvisorSettings,
|
|
23
37
|
id: string,
|
|
24
38
|
value: string,
|
|
25
39
|
presets: ContextPreset[]
|
|
26
|
-
):
|
|
40
|
+
): boolean => {
|
|
27
41
|
switch (id) {
|
|
28
|
-
case "context":
|
|
42
|
+
case "context": {
|
|
29
43
|
settings.contextMaxChars =
|
|
30
44
|
presets.find((preset) => preset.label === value)?.value ??
|
|
31
45
|
settings.contextMaxChars;
|
|
32
|
-
|
|
33
|
-
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
case "simpleMode": {
|
|
34
49
|
settings.simpleMode = value === "On";
|
|
35
|
-
|
|
36
|
-
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
case "alwaysOn": {
|
|
37
53
|
settings.alwaysOn = value === "On";
|
|
38
|
-
|
|
39
|
-
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
case "effort": {
|
|
40
57
|
settings.effort = value;
|
|
41
|
-
|
|
42
|
-
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
case "customRule": {
|
|
43
61
|
settings.customRule = value.trim() || undefined;
|
|
44
|
-
|
|
45
|
-
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
case "toolPolicies": {
|
|
65
|
+
// SAFETY: the value originates from the settings submenu's previously validated JSON editor.
|
|
46
66
|
settings.toolPolicies = JSON.parse(
|
|
47
67
|
value
|
|
48
68
|
) as AdvisorSettings["toolPolicies"];
|
|
49
|
-
|
|
50
|
-
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
case "loopThreshold": {
|
|
51
72
|
settings.loopThreshold = Number(
|
|
52
73
|
value.replace("After ", "").replace(" repeats", "")
|
|
53
74
|
);
|
|
54
|
-
|
|
55
|
-
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
case "maxCallsPerSession": {
|
|
56
78
|
settings.maxCallsPerSession = value === "∞" ? undefined : Number(value);
|
|
57
|
-
|
|
58
|
-
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
case "modelWhitelist": {
|
|
82
|
+
settings.modelWhitelist = parseModelWhitelist(value);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
case "failureMode": {
|
|
86
|
+
// SAFETY: value comes from the fixed failureMode options of the settings list.
|
|
59
87
|
settings.failureMode = value as AdvisorSettings["failureMode"];
|
|
60
|
-
|
|
61
|
-
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
case "gitContext": {
|
|
91
|
+
// SAFETY: value comes from the fixed GitContextLevel options of the settings list.
|
|
62
92
|
settings.gitContext = value as AdvisorSettings["gitContext"];
|
|
63
|
-
|
|
64
|
-
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
default: {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const applyNumericMutation = (
|
|
102
|
+
settings: AdvisorSettings,
|
|
103
|
+
id: string,
|
|
104
|
+
value: string
|
|
105
|
+
): boolean => {
|
|
106
|
+
switch (id) {
|
|
107
|
+
case "toolResultMaxLines": {
|
|
65
108
|
settings.toolResultMaxLines = Number(value);
|
|
66
|
-
|
|
67
|
-
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
case "toolResultMaxBytes": {
|
|
68
112
|
settings.toolResultMaxBytes = Number(value);
|
|
69
|
-
|
|
70
|
-
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
case "gitContextMaxChars": {
|
|
71
116
|
settings.gitContextMaxChars = Number(value);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
default: {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const applyJevMutation = (
|
|
126
|
+
settings: AdvisorSettings,
|
|
127
|
+
id: string,
|
|
128
|
+
value: string
|
|
129
|
+
): boolean => {
|
|
130
|
+
switch (id) {
|
|
131
|
+
case "jevFilter": {
|
|
132
|
+
settings.jevFilterEnabled = value === "On";
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
case "jevFilterSkipConfidence": {
|
|
136
|
+
settings.jevFilterSkipConfidence = Number(value);
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
case "jevFilterNoulMargin": {
|
|
140
|
+
settings.jevFilterNoulMargin = Number(value);
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
case "jevFilterOverrideWindow": {
|
|
144
|
+
settings.jevFilterOverrideWindow = Number(value.replace(" turns", ""));
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
case "jevTurnGateEveryTurns": {
|
|
148
|
+
settings.jevTurnGateEveryTurns =
|
|
149
|
+
value === "Off" ? 0 : Number(value.replaceAll(/[^0-9]/gu, ""));
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
case "jevTurnGateNoulThreshold": {
|
|
153
|
+
settings.jevTurnGateNoulThreshold = Number(value);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
case "jevModel": {
|
|
157
|
+
settings.jevModel = value.trim() || DEFAULT_JEV_MODEL;
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
case "jevTimeoutMs": {
|
|
161
|
+
settings.jevTimeoutMs = Number(value);
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
case "jevDigestMaxChars": {
|
|
165
|
+
settings.jevDigestMaxChars = Number(value);
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
case "jevPricePerMtok": {
|
|
169
|
+
settings.jevPricePerMtok = Number(value);
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
case "jevTransport": {
|
|
173
|
+
// SAFETY: value comes from the fixed jevTransport options of the settings list.
|
|
174
|
+
settings.jevTransport = value as AdvisorSettings["jevTransport"];
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
default: {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export const mutateAdvisorSettings = (
|
|
184
|
+
settings: AdvisorSettings,
|
|
185
|
+
id: string,
|
|
186
|
+
value: string,
|
|
187
|
+
presets: ContextPreset[]
|
|
188
|
+
): void => {
|
|
189
|
+
if (applyCoreMutation(settings, id, value, presets)) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (applyNumericMutation(settings, id, value)) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (applyJevMutation(settings, id, value)) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (BOOLEAN_SETTING_IDS.has(id)) {
|
|
199
|
+
// SAFETY: BOOLEAN_SETTING_IDS only contains AdvisorSettings boolean fields.
|
|
200
|
+
settings[id as BooleanSettingField] = value === "On";
|
|
79
201
|
}
|
|
80
202
|
};
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
SettingsList,
|
|
6
|
-
truncateToWidth,
|
|
7
|
-
} from "@earendil-works/pi-tui";
|
|
2
|
+
import { SettingsList, truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
import type { Component, Focusable } from "@earendil-works/pi-tui";
|
|
4
|
+
|
|
8
5
|
import {
|
|
9
6
|
rainbowGradient,
|
|
10
7
|
SIMPLE_MODE_GRADIENT_INTERVAL_MS,
|
|
11
8
|
} from "./settings-formatting.ts";
|
|
12
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
advisorModelWhitelistItem,
|
|
11
|
+
createSettingsItems,
|
|
12
|
+
} from "./settings-items.ts";
|
|
13
13
|
import { SettingsListAdapter } from "./settings-list-adapter.ts";
|
|
14
14
|
import { mutateAdvisorSettings } from "./settings-mutations.ts";
|
|
15
15
|
import type {
|
|
@@ -51,7 +51,7 @@ export class AdvisorSettingsSelector implements Component, Focusable {
|
|
|
51
51
|
label: String(configuredContext),
|
|
52
52
|
value: configuredContext,
|
|
53
53
|
},
|
|
54
|
-
].
|
|
54
|
+
].toSorted((a, b) => a.value - b.value);
|
|
55
55
|
if (this.settings.simpleMode) {
|
|
56
56
|
this.startSimpleModeGradient();
|
|
57
57
|
}
|
|
@@ -96,8 +96,16 @@ export class AdvisorSettingsSelector implements Component, Focusable {
|
|
|
96
96
|
}
|
|
97
97
|
return defaultLabel(text, selected);
|
|
98
98
|
};
|
|
99
|
+
const modelWhitelist = advisorModelWhitelistItem(
|
|
100
|
+
this.settings,
|
|
101
|
+
this.options.modelRefs,
|
|
102
|
+
this.options.keybindings,
|
|
103
|
+
this.options.theme,
|
|
104
|
+
this.options.tui
|
|
105
|
+
);
|
|
99
106
|
const items = createSettingsItems({
|
|
100
107
|
effortLevels: this.options.effortLevels,
|
|
108
|
+
modelWhitelist,
|
|
101
109
|
presets: this.presets,
|
|
102
110
|
settings: this.settings,
|
|
103
111
|
theme: this.options.theme,
|
|
@@ -147,12 +155,13 @@ export class AdvisorSettingsSelector implements Component, Focusable {
|
|
|
147
155
|
(this.options.onChange ?? this.options.onSave)?.({
|
|
148
156
|
...this.settings,
|
|
149
157
|
showUsageDetails: this.settings.showUsageDetails ?? true,
|
|
150
|
-
toolPolicies: { ...
|
|
158
|
+
toolPolicies: { ...this.settings.toolPolicies },
|
|
151
159
|
});
|
|
152
160
|
if (
|
|
153
161
|
id === "context" ||
|
|
154
162
|
id === "simpleMode" ||
|
|
155
163
|
id === "customRule" ||
|
|
164
|
+
id === "modelWhitelist" ||
|
|
156
165
|
id === "toolPolicies"
|
|
157
166
|
) {
|
|
158
167
|
this.settingsList = this.createSettingsList(id);
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
Input,
|
|
5
|
-
truncateToWidth,
|
|
6
|
-
} from "@earendil-works/pi-tui";
|
|
1
|
+
import { Input, truncateToWidth } from "@earendil-works/pi-tui";
|
|
2
|
+
import type { Component, Focusable } from "@earendil-works/pi-tui";
|
|
3
|
+
|
|
7
4
|
import type { TextSettingSubmenuOptions } from "./types.ts";
|
|
8
5
|
|
|
9
6
|
/** Small inline editor used by SettingsList for the two free-form settings. */
|
package/src/ui/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { KeybindingsManager, TUI } from "@earendil-works/pi-tui";
|
|
3
|
+
|
|
3
4
|
import type { GateFailureMode } from "../config/types.ts";
|
|
4
5
|
import type { GitContextLevel } from "../git.ts";
|
|
5
6
|
|
|
@@ -11,6 +12,7 @@ export interface SearchableModelSelectorOptions {
|
|
|
11
12
|
allOptions: string[];
|
|
12
13
|
currentOption?: string;
|
|
13
14
|
keybindings: KeybindingsManager;
|
|
15
|
+
multiSelect?: false;
|
|
14
16
|
onCancel: () => void;
|
|
15
17
|
onSelect: (value: string) => void;
|
|
16
18
|
theme: Theme;
|
|
@@ -18,6 +20,15 @@ export interface SearchableModelSelectorOptions {
|
|
|
18
20
|
tui: RenderRequester;
|
|
19
21
|
}
|
|
20
22
|
|
|
23
|
+
export interface SearchableModelMultiSelectorOptions extends Omit<
|
|
24
|
+
SearchableModelSelectorOptions,
|
|
25
|
+
"currentOption" | "multiSelect" | "onSelect"
|
|
26
|
+
> {
|
|
27
|
+
currentOptions: string[];
|
|
28
|
+
multiSelect: true;
|
|
29
|
+
onSelect: (values: string[]) => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
21
32
|
export interface ManualAdvisorRequest {
|
|
22
33
|
/** The repository disclosure level selected for this consultation. */
|
|
23
34
|
gitContext: GitContextLevel;
|
|
@@ -69,8 +80,20 @@ export interface AdvisorSettings {
|
|
|
69
80
|
gitContext?: GitContextLevel;
|
|
70
81
|
gitContextMaxChars?: number;
|
|
71
82
|
herdrIntegration?: boolean;
|
|
83
|
+
jevDigestMaxChars?: number;
|
|
84
|
+
jevFilterEnabled?: boolean;
|
|
85
|
+
jevFilterNoulMargin?: number;
|
|
86
|
+
jevFilterOverrideWindow?: number;
|
|
87
|
+
jevFilterSkipConfidence?: number;
|
|
88
|
+
jevModel?: string;
|
|
89
|
+
jevPricePerMtok?: number;
|
|
90
|
+
jevTimeoutMs?: number;
|
|
91
|
+
jevTransport?: "auto" | "typesafe" | "openrouter";
|
|
92
|
+
jevTurnGateEveryTurns?: number;
|
|
93
|
+
jevTurnGateNoulThreshold?: number;
|
|
72
94
|
loopThreshold?: number;
|
|
73
95
|
maxCallsPerSession?: number;
|
|
96
|
+
modelWhitelist?: string[];
|
|
74
97
|
outcomeLogging?: boolean;
|
|
75
98
|
planGate: boolean;
|
|
76
99
|
redactSecrets?: boolean;
|
|
@@ -89,6 +112,8 @@ export interface AdvisorSettings {
|
|
|
89
112
|
export interface AdvisorSettingsSelectorOptions {
|
|
90
113
|
effortLevels: string[];
|
|
91
114
|
initial: AdvisorSettings;
|
|
115
|
+
keybindings?: KeybindingsManager;
|
|
116
|
+
modelRefs?: string[];
|
|
92
117
|
onCancel: () => void;
|
|
93
118
|
onChange?: (settings: AdvisorSettings) => void;
|
|
94
119
|
/** @deprecated Use onChange; retained for extensions embedding this component. */
|
package/src/ui.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// biome-ignore lint/performance/noBarrelFile: this facade intentionally preserves the public UI module contract.
|
|
2
1
|
export { ManualAdvisorDialog } from "./ui/manual-dialog.ts";
|
|
3
2
|
export { SearchableModelSelector } from "./ui/model-selector.ts";
|
|
4
3
|
export { AdvisorSettingsSelector } from "./ui/settings-selector.ts";
|