pi-advisor-flow 0.9.2-dev.5 → 0.10.1-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +40 -5
- package/dist/index.js +464 -86
- package/package.json +2 -2
- package/src/commands/activation-preparation.ts +4 -0
- package/src/commands/manual-consultation.ts +41 -29
- package/src/commands/model-commands.ts +5 -1
- package/src/commands/model-picker.ts +44 -2
- package/src/commands/runtime.ts +9 -1
- package/src/commands/settings-persistence.ts +2 -0
- package/src/commands/types.ts +2 -0
- package/src/config/defaults.ts +6 -0
- package/src/config/schema.ts +7 -0
- package/src/config/state.ts +5 -0
- package/src/config/types.ts +1 -0
- package/src/config.ts +2 -0
- package/src/follow-up.ts +85 -0
- package/src/session-state.ts +33 -0
- package/src/tools/advisor-output.ts +28 -0
- package/src/tools/consultation.ts +311 -41
- package/src/tools/model-access.ts +7 -3
- package/src/tools/register-ask-advisor.ts +133 -31
- package/src/tools/register-lifecycle.ts +11 -0
- package/src/tools/render-advisor-result.ts +3 -0
- package/src/tools/render-common.ts +6 -2
- package/src/tools/types.ts +2 -0
- package/src/ui/settings-items.ts +36 -0
- package/src/ui/settings-mutations.ts +8 -0
- package/src/ui/settings-selector.ts +9 -0
- package/src/ui/types.ts +3 -0
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
ExtensionContext,
|
|
4
4
|
} from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { Type } from "typebox";
|
|
6
|
+
import type { Static } from "typebox";
|
|
6
7
|
|
|
7
8
|
import {
|
|
8
9
|
advisorRef,
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
isSimpleMode,
|
|
12
13
|
} from "../config/state.ts";
|
|
13
14
|
import { loadConfig } from "../config/storage.ts";
|
|
15
|
+
import type { AdvisorFollowUpPayload } from "../follow-up.ts";
|
|
14
16
|
import { notifyHerdrAdvisorFailure } from "../herdr.ts";
|
|
15
17
|
import {
|
|
16
18
|
ADVISOR_STREAM_UPDATE_INTERVAL_MS,
|
|
@@ -21,10 +23,13 @@ import {
|
|
|
21
23
|
advisorUsageForPi,
|
|
22
24
|
snapshotAdvisorUsage,
|
|
23
25
|
} from "../usage.ts";
|
|
26
|
+
import { advisorOutputSchema } from "./advisor-output.ts";
|
|
27
|
+
import { assertAdvisorFollowUpPrivacy } from "./consultation.ts";
|
|
24
28
|
import { notifyLocalFailure, updateAdvisorUsageStatus } from "./gate-policy.ts";
|
|
25
29
|
import { normalizeScreeningQuestion, screeningSkipText } from "./jev-filter.ts";
|
|
26
30
|
import {
|
|
27
31
|
advisorModelAccessReason,
|
|
32
|
+
fallbackSameModelAdvisorNotice,
|
|
28
33
|
sameModelAdvisorDisabled,
|
|
29
34
|
sameModelAdvisorNotice,
|
|
30
35
|
} from "./model-access.ts";
|
|
@@ -47,6 +52,66 @@ const assertAdvisorModelAccess = (ctx: ExtensionContext) => {
|
|
|
47
52
|
}
|
|
48
53
|
};
|
|
49
54
|
|
|
55
|
+
interface AskAdvisorParams {
|
|
56
|
+
draft?: string;
|
|
57
|
+
followUpTo?: string;
|
|
58
|
+
force?: boolean;
|
|
59
|
+
gitContext?: "none" | "summary" | "full";
|
|
60
|
+
includeTrackedFiles?: string[];
|
|
61
|
+
includeUntracked?: string[];
|
|
62
|
+
question?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const resolveFollowUp = (
|
|
66
|
+
params: AskAdvisorParams,
|
|
67
|
+
session: ToolRegistrationContext["session"]
|
|
68
|
+
): { adviceId: string; payload: AdvisorFollowUpPayload } | undefined => {
|
|
69
|
+
if (!params.followUpTo) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
if (
|
|
73
|
+
!params.question?.trim() ||
|
|
74
|
+
params.draft !== undefined ||
|
|
75
|
+
params.gitContext !== undefined ||
|
|
76
|
+
params.includeTrackedFiles?.length ||
|
|
77
|
+
params.includeUntracked?.length
|
|
78
|
+
) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
"Follow-up consultations accept only followUpTo and a non-empty question. Issue a fresh consultation for new context or attachments."
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
const cached = session.followUpFor(params.followUpTo);
|
|
84
|
+
if (!cached.payload) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`Follow-up unavailable for adviceId ${params.followUpTo}: ${cached.reason ?? "the cached payload is unavailable"}. Issue a fresh consultation.`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return { adviceId: params.followUpTo, payload: cached.payload };
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const sameModelNoticeFor = (modelRef: string | undefined) =>
|
|
93
|
+
(modelRef ?? advisorRef) === advisorRef
|
|
94
|
+
? sameModelAdvisorNotice
|
|
95
|
+
: fallbackSameModelAdvisorNotice;
|
|
96
|
+
|
|
97
|
+
const skippedSameModelResult = (
|
|
98
|
+
id: string,
|
|
99
|
+
notice: string,
|
|
100
|
+
reservedCalls: Set<string>,
|
|
101
|
+
session: ToolRegistrationContext["session"]
|
|
102
|
+
) => {
|
|
103
|
+
session.releaseCall(id);
|
|
104
|
+
reservedCalls.delete(id);
|
|
105
|
+
return {
|
|
106
|
+
content: [{ text: notice, type: "text" as const }],
|
|
107
|
+
details: {
|
|
108
|
+
skipReason: notice,
|
|
109
|
+
text: notice,
|
|
110
|
+
},
|
|
111
|
+
structuredContent: { skipReason: notice, text: notice },
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
|
|
50
115
|
const claimTrackedHandoff = (
|
|
51
116
|
session: ToolRegistrationContext["session"],
|
|
52
117
|
includeTrackedFiles: string[] | undefined
|
|
@@ -76,23 +141,25 @@ export const registerAskAdvisorTool = ({
|
|
|
76
141
|
}: ToolRegistrationContext): void => {
|
|
77
142
|
pi.registerTool({
|
|
78
143
|
description:
|
|
79
|
-
"Consult the on-demand Advisor model for strategic guidance. Call with an empty object for a contextual review; attach an optional draft for concrete plan or completion review. If the Advisor explicitly names a missing file, you may make a sequential
|
|
144
|
+
"Consult the on-demand Advisor model for strategic guidance. Call with an empty object for a contextual review; attach an optional draft for concrete plan or completion review. Use followUpTo with a new question to continue one cached consultation without rebuilding its redacted context. If the Advisor explicitly names a missing file, you may make a sequential fresh consultation with includeTrackedFiles when enabled and relevant. In Pi Codemode, gather and filter deterministic tool results first, then call tools.ask_advisor. Nested results are not automatically reconstructed: pass only permitted, concise summaries in draft (untrusted, capped at 8 KiB, not independently verified). Draft does not inherit source-tool disclosure policies; use gitContext for patches within the user's allowance.",
|
|
80
145
|
async execute(_id, params, signal, onUpdate, ctx) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
content: [{ text: sameModelAdvisorNotice, type: "text" as const }],
|
|
86
|
-
details: {
|
|
87
|
-
skipReason: sameModelAdvisorNotice,
|
|
88
|
-
text: sameModelAdvisorNotice,
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
-
};
|
|
146
|
+
let followUp:
|
|
147
|
+
| { adviceId: string; payload: AdvisorFollowUpPayload }
|
|
148
|
+
| undefined;
|
|
92
149
|
try {
|
|
93
150
|
loadConfig(ctx);
|
|
94
|
-
|
|
95
|
-
|
|
151
|
+
followUp = resolveFollowUp(params, session);
|
|
152
|
+
if (followUp) {
|
|
153
|
+
assertAdvisorFollowUpPrivacy(followUp.payload, ctx);
|
|
154
|
+
}
|
|
155
|
+
const modelRef = followUp?.payload.model;
|
|
156
|
+
if (sameModelAdvisorDisabled(ctx, ctx.model, modelRef)) {
|
|
157
|
+
return skippedSameModelResult(
|
|
158
|
+
_id,
|
|
159
|
+
sameModelNoticeFor(modelRef),
|
|
160
|
+
reservedCalls,
|
|
161
|
+
session
|
|
162
|
+
);
|
|
96
163
|
}
|
|
97
164
|
assertAdvisorModelAccess(ctx);
|
|
98
165
|
} catch (error) {
|
|
@@ -136,20 +203,27 @@ export const registerAskAdvisorTool = ({
|
|
|
136
203
|
const skipText = screeningSkipText(screening);
|
|
137
204
|
session.releaseCall(_id);
|
|
138
205
|
reservedCalls.delete(_id);
|
|
206
|
+
const skipped = {
|
|
207
|
+
jev: {
|
|
208
|
+
kind: screening.kind,
|
|
209
|
+
reason: screening.reason,
|
|
210
|
+
skipped: true,
|
|
211
|
+
},
|
|
212
|
+
text: skipText,
|
|
213
|
+
};
|
|
139
214
|
return {
|
|
140
215
|
content: [{ text: skipText, type: "text" }],
|
|
141
|
-
details:
|
|
142
|
-
|
|
143
|
-
kind: screening.kind,
|
|
144
|
-
reason: screening.reason,
|
|
145
|
-
skipped: true,
|
|
146
|
-
},
|
|
147
|
-
text: skipText,
|
|
148
|
-
},
|
|
216
|
+
details: skipped,
|
|
217
|
+
structuredContent: skipped,
|
|
149
218
|
};
|
|
150
219
|
}
|
|
151
|
-
if (sameModelAdvisorDisabled(ctx)) {
|
|
152
|
-
return
|
|
220
|
+
if (sameModelAdvisorDisabled(ctx, ctx.model, followUp?.payload.model)) {
|
|
221
|
+
return skippedSameModelResult(
|
|
222
|
+
_id,
|
|
223
|
+
sameModelNoticeFor(followUp?.payload.model),
|
|
224
|
+
reservedCalls,
|
|
225
|
+
session
|
|
226
|
+
);
|
|
153
227
|
}
|
|
154
228
|
if (
|
|
155
229
|
!simpleMode &&
|
|
@@ -191,7 +265,8 @@ export const registerAskAdvisorTool = ({
|
|
|
191
265
|
coalescedUpdate.update({
|
|
192
266
|
content: [{ text: tx, type: "text" }],
|
|
193
267
|
details: {
|
|
194
|
-
advisor: advisorRef,
|
|
268
|
+
advisor: followUp?.payload.model ?? advisorRef,
|
|
269
|
+
followUp: Boolean(followUp),
|
|
195
270
|
question: resolveAdvisorRequest(params.question),
|
|
196
271
|
scout: scoutDetails,
|
|
197
272
|
text: tx,
|
|
@@ -209,13 +284,17 @@ export const registerAskAdvisorTool = ({
|
|
|
209
284
|
coalescedUpdate.update({
|
|
210
285
|
content: [{ text: scoutDetails.text ?? "", type: "text" }],
|
|
211
286
|
details: {
|
|
212
|
-
advisor: advisorRef,
|
|
287
|
+
advisor: followUp?.payload.model ?? advisorRef,
|
|
288
|
+
followUp: Boolean(followUp),
|
|
213
289
|
question: resolveAdvisorRequest(params.question),
|
|
214
290
|
scout: scoutDetails,
|
|
215
291
|
},
|
|
216
292
|
});
|
|
217
293
|
},
|
|
218
|
-
_id
|
|
294
|
+
_id,
|
|
295
|
+
followUp,
|
|
296
|
+
(adviceId, payload, replacesAdviceId) =>
|
|
297
|
+
session.captureFollowUp(adviceId, payload, replacesAdviceId)
|
|
219
298
|
);
|
|
220
299
|
flushUpdate();
|
|
221
300
|
session.issueAdvice(
|
|
@@ -228,6 +307,7 @@ export const registerAskAdvisorTool = ({
|
|
|
228
307
|
session.recordInvocation({
|
|
229
308
|
cost: advisorUsageCost(result.usage),
|
|
230
309
|
executionEffect: "continued",
|
|
310
|
+
followUp: result.followUp,
|
|
231
311
|
kind: "markdown",
|
|
232
312
|
model: result.model,
|
|
233
313
|
trigger: "executor-requested",
|
|
@@ -241,6 +321,7 @@ export const registerAskAdvisorTool = ({
|
|
|
241
321
|
advisor: result.model,
|
|
242
322
|
agentRulesBytes: result.agentRulesBytes,
|
|
243
323
|
draftBytes: result.draftBytes,
|
|
324
|
+
followUp: result.followUp,
|
|
244
325
|
imageBytes: result.imageBytes,
|
|
245
326
|
imageCount: result.imageCount,
|
|
246
327
|
imageOmissions: result.imageOmissions,
|
|
@@ -256,14 +337,24 @@ export const registerAskAdvisorTool = ({
|
|
|
256
337
|
if (usage) {
|
|
257
338
|
details.usage = usage;
|
|
258
339
|
}
|
|
340
|
+
const structuredContent: Static<typeof advisorOutputSchema> = {
|
|
341
|
+
adviceId: result.adviceId,
|
|
342
|
+
advisor: result.model,
|
|
343
|
+
followUp: Boolean(result.followUp),
|
|
344
|
+
text: result.markdown,
|
|
345
|
+
};
|
|
346
|
+
if (usage) {
|
|
347
|
+
structuredContent.usage = usage;
|
|
348
|
+
}
|
|
259
349
|
const response: AgentToolResult<AdvisorToolDetails> = {
|
|
260
350
|
content: [
|
|
261
351
|
{
|
|
262
|
-
text:
|
|
352
|
+
text: `${result.followUp ? "Advisor follow-up" : "Advisor"} (${result.model})\n\n${result.markdown}`,
|
|
263
353
|
type: "text",
|
|
264
354
|
},
|
|
265
355
|
],
|
|
266
356
|
details,
|
|
357
|
+
structuredContent,
|
|
267
358
|
};
|
|
268
359
|
if (piUsage) {
|
|
269
360
|
response.usage = piUsage;
|
|
@@ -280,7 +371,7 @@ export const registerAskAdvisorTool = ({
|
|
|
280
371
|
executionEffect: "continued",
|
|
281
372
|
failure: "provider-error",
|
|
282
373
|
kind: "markdown",
|
|
283
|
-
model: advisorRef,
|
|
374
|
+
model: followUp?.payload.model ?? advisorRef,
|
|
284
375
|
trigger: "executor-requested",
|
|
285
376
|
});
|
|
286
377
|
updateAdvisorUsageStatus(ctx, session);
|
|
@@ -296,6 +387,7 @@ export const registerAskAdvisorTool = ({
|
|
|
296
387
|
},
|
|
297
388
|
label: "Ask Advisor",
|
|
298
389
|
name: "ask_advisor",
|
|
390
|
+
outputSchema: advisorOutputSchema,
|
|
299
391
|
parameters: Type.Object({
|
|
300
392
|
draft: Type.Optional(
|
|
301
393
|
Type.String({
|
|
@@ -303,6 +395,12 @@ export const registerAskAdvisorTool = ({
|
|
|
303
395
|
"Concise untrusted draft for plan or completion review; claims are not verification evidence.",
|
|
304
396
|
})
|
|
305
397
|
),
|
|
398
|
+
followUpTo: Type.Optional(
|
|
399
|
+
Type.String({
|
|
400
|
+
description:
|
|
401
|
+
"Opaque adviceId from a prior successful consultation. Requires a new non-empty question and reuses that consultation's redacted payload; do not combine with draft, Git context, or file attachments.",
|
|
402
|
+
})
|
|
403
|
+
),
|
|
306
404
|
force: Type.Optional(
|
|
307
405
|
Type.Boolean({
|
|
308
406
|
description:
|
|
@@ -342,12 +440,16 @@ export const registerAskAdvisorTool = ({
|
|
|
342
440
|
),
|
|
343
441
|
}),
|
|
344
442
|
promptGuidelines: [
|
|
345
|
-
"Call ask_advisor with an empty object for general consultation. For a plan or completion review, include a concise draft naming work, validation, and remaining risks; its claims are not evidence. If the Advisor explicitly says it cannot review a specifically named file, you may make a sequential follow-up call with includeTrackedFiles when the file is relevant, permitted, and worth the shared call budget; do not infer paths or retry automatically.",
|
|
443
|
+
"Call ask_advisor with an empty object for general consultation. For a plan or completion review, include a concise draft naming work, validation, and remaining risks; its claims are not evidence. To drill into an existing answer, pass its adviceId as followUpTo with a new question and no draft, Git context, or attachments. If the Advisor explicitly says it cannot review a specifically named file, you may make a sequential follow-up call with includeTrackedFiles when the file is relevant, permitted, and worth the shared call budget; do not infer paths or retry automatically.",
|
|
346
444
|
],
|
|
347
445
|
promptSnippet:
|
|
348
446
|
"Consult the Advisor using its existing context; attach a draft for plan or completion review",
|
|
349
447
|
renderCall(args, theme) {
|
|
350
|
-
return renderAdvisorCallBox(
|
|
448
|
+
return renderAdvisorCallBox(
|
|
449
|
+
args.question?.trim(),
|
|
450
|
+
theme,
|
|
451
|
+
Boolean(args.followUpTo)
|
|
452
|
+
);
|
|
351
453
|
},
|
|
352
454
|
renderResult(result, options, theme, context) {
|
|
353
455
|
// SAFETY: this tool's execute() only returns AdvisorToolDetails-shaped details.
|
|
@@ -95,6 +95,16 @@ export const registerToolLifecycle = ({
|
|
|
95
95
|
: undefined;
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
+
pi.on("input", () => {
|
|
99
|
+
session.clearFollowUps();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
pi.on("tool_result", (event) => {
|
|
103
|
+
if (event.toolName !== "ask_advisor") {
|
|
104
|
+
session.advanceFollowUpToolCall();
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
98
108
|
pi.on("tool_call", (event, ctx) => {
|
|
99
109
|
if (session.blocked) {
|
|
100
110
|
return {
|
|
@@ -149,6 +159,7 @@ export const registerToolLifecycle = ({
|
|
|
149
159
|
|
|
150
160
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
151
161
|
reservedCalls.clear();
|
|
162
|
+
session.clearFollowUps();
|
|
152
163
|
session.clearCallReservations();
|
|
153
164
|
scoutStatus.clear(ctx);
|
|
154
165
|
herdrBlock.clear();
|
|
@@ -162,6 +162,9 @@ const finalResultLines = (
|
|
|
162
162
|
theme: Theme
|
|
163
163
|
): string[] => {
|
|
164
164
|
const lines = [renderAdvisorResponseHeader(hasSoundVerdict(advice), theme)];
|
|
165
|
+
if (details?.followUp) {
|
|
166
|
+
lines.push(theme.fg("dim", " Follow-up consultation"));
|
|
167
|
+
}
|
|
165
168
|
if (details?.advisor) {
|
|
166
169
|
lines.push(theme.fg("dim", ` ${details.advisor}`));
|
|
167
170
|
}
|
|
@@ -85,11 +85,15 @@ export const resolveAdvisorRequest = (question?: string) =>
|
|
|
85
85
|
|
|
86
86
|
export const renderAdvisorCallBox = (
|
|
87
87
|
question: string | undefined,
|
|
88
|
-
theme: Theme
|
|
88
|
+
theme: Theme,
|
|
89
|
+
followUp = false
|
|
89
90
|
) => {
|
|
90
91
|
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
91
92
|
const label = theme.fg("customMessageLabel", theme.bold("[advisor]"));
|
|
92
|
-
const title = theme.fg(
|
|
93
|
+
const title = theme.fg(
|
|
94
|
+
"customMessageText",
|
|
95
|
+
followUp ? "Executor → Advisor · Follow-up" : "Executor → Advisor"
|
|
96
|
+
);
|
|
93
97
|
box.addChild(
|
|
94
98
|
new Text(
|
|
95
99
|
question
|
package/src/tools/types.ts
CHANGED
|
@@ -48,6 +48,7 @@ export interface AdvisorConsultationResult {
|
|
|
48
48
|
adviceId: string;
|
|
49
49
|
agentRulesBytes?: number;
|
|
50
50
|
draftBytes?: number;
|
|
51
|
+
followUp?: boolean;
|
|
51
52
|
imageBytes?: number;
|
|
52
53
|
imageCount?: number;
|
|
53
54
|
imageOmissions?: number;
|
|
@@ -89,6 +90,7 @@ export interface AdvisorToolDetails {
|
|
|
89
90
|
adviceId?: string;
|
|
90
91
|
advisor?: string;
|
|
91
92
|
agentRulesBytes?: number;
|
|
93
|
+
followUp?: boolean;
|
|
92
94
|
draftBytes?: number;
|
|
93
95
|
imageBytes?: number;
|
|
94
96
|
imageCount?: number;
|
package/src/ui/settings-items.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import { isValidAdvisorToolPolicies } from "../config/validation.ts";
|
|
18
18
|
import { JevSetupSubmenu } from "./jev-setup-submenu.ts";
|
|
19
19
|
import { SearchableModelMultiSelector } from "./model-multi-selector.ts";
|
|
20
|
+
import { SearchableModelSelector } from "./model-selector.ts";
|
|
20
21
|
import {
|
|
21
22
|
contextDescription,
|
|
22
23
|
currentContextLabel,
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
withCurrentValue,
|
|
29
30
|
} from "./settings-formatting.ts";
|
|
30
31
|
import { TextSettingSubmenu } from "./text-setting-submenu.ts";
|
|
32
|
+
import { FALLBACK_ADVISOR_MODEL_DISABLED } from "./types.ts";
|
|
31
33
|
import type {
|
|
32
34
|
AdvisorSettings,
|
|
33
35
|
ContextPreset,
|
|
@@ -36,6 +38,7 @@ import type {
|
|
|
36
38
|
|
|
37
39
|
export interface SettingsItemsOptions {
|
|
38
40
|
effortLevels: string[];
|
|
41
|
+
fallbackModel: SettingItem;
|
|
39
42
|
modelWhitelist: SettingItem;
|
|
40
43
|
presets: ContextPreset[];
|
|
41
44
|
settings: AdvisorSettings;
|
|
@@ -220,6 +223,37 @@ const jevItems = (
|
|
|
220
223
|
},
|
|
221
224
|
];
|
|
222
225
|
|
|
226
|
+
export const advisorFallbackModelItem = (
|
|
227
|
+
settings: AdvisorSettings,
|
|
228
|
+
modelRefs: string[] | undefined,
|
|
229
|
+
keybindings: KeybindingsManager | undefined,
|
|
230
|
+
theme: Theme,
|
|
231
|
+
tui: RenderRequester
|
|
232
|
+
): SettingItem => ({
|
|
233
|
+
currentValue: settings.fallbackModel ?? FALLBACK_ADVISOR_MODEL_DISABLED,
|
|
234
|
+
description:
|
|
235
|
+
"Retry a failed Advisor request once with this optional model; the primary model remains unchanged.",
|
|
236
|
+
id: "fallbackModel",
|
|
237
|
+
label: "Fallback Advisor model",
|
|
238
|
+
submenu: (_currentValue, done) =>
|
|
239
|
+
new SearchableModelSelector({
|
|
240
|
+
allOptions: [
|
|
241
|
+
FALLBACK_ADVISOR_MODEL_DISABLED,
|
|
242
|
+
...new Set(
|
|
243
|
+
modelRefs ?? (settings.fallbackModel ? [settings.fallbackModel] : [])
|
|
244
|
+
),
|
|
245
|
+
],
|
|
246
|
+
currentOption: settings.fallbackModel ?? FALLBACK_ADVISOR_MODEL_DISABLED,
|
|
247
|
+
keybindings: keybindings ?? getKeybindings(),
|
|
248
|
+
onCancel: done,
|
|
249
|
+
onSelect: (value) =>
|
|
250
|
+
done(value === FALLBACK_ADVISOR_MODEL_DISABLED ? "" : value),
|
|
251
|
+
theme,
|
|
252
|
+
title: "Fallback Advisor model",
|
|
253
|
+
tui,
|
|
254
|
+
}),
|
|
255
|
+
});
|
|
256
|
+
|
|
223
257
|
export const advisorModelWhitelistItem = (
|
|
224
258
|
settings: AdvisorSettings,
|
|
225
259
|
modelRefs: string[] | undefined,
|
|
@@ -252,6 +286,7 @@ export const advisorModelWhitelistItem = (
|
|
|
252
286
|
|
|
253
287
|
export const createSettingsItems = ({
|
|
254
288
|
effortLevels,
|
|
289
|
+
fallbackModel,
|
|
255
290
|
modelWhitelist,
|
|
256
291
|
presets,
|
|
257
292
|
settings,
|
|
@@ -282,6 +317,7 @@ export const createSettingsItems = ({
|
|
|
282
317
|
label: "Always on",
|
|
283
318
|
values: TOGGLE_VALUES,
|
|
284
319
|
},
|
|
320
|
+
fallbackModel,
|
|
285
321
|
];
|
|
286
322
|
if (settings.simpleMode) {
|
|
287
323
|
items.push(
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DEFAULT_JEV_MODEL } from "../config/types.ts";
|
|
2
|
+
import { FALLBACK_ADVISOR_MODEL_DISABLED } from "./types.ts";
|
|
2
3
|
import type { AdvisorSettings, ContextPreset } from "./types.ts";
|
|
3
4
|
|
|
4
5
|
const BOOLEAN_SETTING_FIELDS = [
|
|
@@ -55,6 +56,13 @@ const applyCoreMutation = (
|
|
|
55
56
|
settings.alwaysOn = value === "On";
|
|
56
57
|
return true;
|
|
57
58
|
}
|
|
59
|
+
case "fallbackModel": {
|
|
60
|
+
settings.fallbackModel =
|
|
61
|
+
value === FALLBACK_ADVISOR_MODEL_DISABLED
|
|
62
|
+
? undefined
|
|
63
|
+
: value.trim() || undefined;
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
58
66
|
case "effort": {
|
|
59
67
|
settings.effort = value;
|
|
60
68
|
return true;
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
SIMPLE_MODE_GRADIENT_INTERVAL_MS,
|
|
8
8
|
} from "./settings-formatting.ts";
|
|
9
9
|
import {
|
|
10
|
+
advisorFallbackModelItem,
|
|
10
11
|
advisorModelWhitelistItem,
|
|
11
12
|
createSettingsItems,
|
|
12
13
|
} from "./settings-items.ts";
|
|
@@ -96,6 +97,13 @@ export class AdvisorSettingsSelector implements Component, Focusable {
|
|
|
96
97
|
}
|
|
97
98
|
return defaultLabel(text, selected);
|
|
98
99
|
};
|
|
100
|
+
const fallbackModel = advisorFallbackModelItem(
|
|
101
|
+
this.settings,
|
|
102
|
+
this.options.modelRefs,
|
|
103
|
+
this.options.keybindings,
|
|
104
|
+
this.options.theme,
|
|
105
|
+
this.options.tui
|
|
106
|
+
);
|
|
99
107
|
const modelWhitelist = advisorModelWhitelistItem(
|
|
100
108
|
this.settings,
|
|
101
109
|
this.options.modelRefs,
|
|
@@ -105,6 +113,7 @@ export class AdvisorSettingsSelector implements Component, Focusable {
|
|
|
105
113
|
);
|
|
106
114
|
const items = createSettingsItems({
|
|
107
115
|
effortLevels: this.options.effortLevels,
|
|
116
|
+
fallbackModel,
|
|
108
117
|
modelWhitelist,
|
|
109
118
|
presets: this.presets,
|
|
110
119
|
settings: this.settings,
|
package/src/ui/types.ts
CHANGED
|
@@ -66,8 +66,11 @@ export interface ContextPreset {
|
|
|
66
66
|
value: number;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
export const FALLBACK_ADVISOR_MODEL_DISABLED = "Disabled (no fallback)";
|
|
70
|
+
|
|
69
71
|
export interface AdvisorSettings {
|
|
70
72
|
agentsMdContext?: boolean;
|
|
73
|
+
fallbackModel?: string;
|
|
71
74
|
alwaysOn?: boolean;
|
|
72
75
|
autoLoopGate?: boolean;
|
|
73
76
|
blockOnBlocked?: boolean;
|