pi-advisor-flow 0.9.2-dev.4 → 0.10.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 +11 -0
- package/README.md +16 -7
- package/dist/index.js +938 -287
- package/package.json +2 -2
- package/src/commands/activation-preparation.ts +4 -0
- package/src/commands/manual-consultation.ts +20 -3
- package/src/commands/model-commands.ts +5 -1
- package/src/commands/model-picker.ts +44 -2
- package/src/commands/outcome-stats.ts +98 -0
- package/src/commands/registration.ts +2 -0
- package/src/commands/renderers.ts +51 -0
- package/src/commands/runtime.ts +9 -1
- package/src/commands/settings-persistence.ts +4 -0
- package/src/commands/types.ts +5 -2
- package/src/config/defaults.ts +13 -0
- package/src/config/schema.ts +14 -0
- package/src/config/state.ts +10 -0
- package/src/config/types.ts +2 -0
- package/src/config.ts +4 -0
- package/src/follow-up.ts +85 -0
- package/src/outcome-stats.ts +154 -0
- package/src/outcomes.ts +8 -1
- package/src/preferences.ts +90 -12
- package/src/session-state.ts +33 -0
- package/src/tools/consult-context.ts +12 -1
- package/src/tools/consultation.ts +344 -47
- package/src/tools/jev-turn-gate.ts +2 -0
- package/src/tools/loop-gate.ts +2 -0
- package/src/tools/model-access.ts +7 -3
- package/src/tools/prompts.ts +28 -2
- package/src/tools/register-ask-advisor.ts +110 -23
- package/src/tools/register-lifecycle.ts +11 -0
- package/src/tools/register-renderers.ts +40 -6
- package/src/tools/render-advisor-result.ts +6 -0
- package/src/tools/render-common.ts +6 -2
- package/src/tools/types.ts +5 -0
- package/src/ui/settings-items.ts +43 -0
- package/src/ui/settings-mutations.ts +9 -0
- package/src/ui/settings-selector.ts +9 -0
- package/src/ui/types.ts +4 -0
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
isSimpleMode,
|
|
12
12
|
} from "../config/state.ts";
|
|
13
13
|
import { loadConfig } from "../config/storage.ts";
|
|
14
|
+
import type { AdvisorFollowUpPayload } from "../follow-up.ts";
|
|
14
15
|
import { notifyHerdrAdvisorFailure } from "../herdr.ts";
|
|
15
16
|
import {
|
|
16
17
|
ADVISOR_STREAM_UPDATE_INTERVAL_MS,
|
|
@@ -21,10 +22,12 @@ import {
|
|
|
21
22
|
advisorUsageForPi,
|
|
22
23
|
snapshotAdvisorUsage,
|
|
23
24
|
} from "../usage.ts";
|
|
25
|
+
import { assertAdvisorFollowUpPrivacy } from "./consultation.ts";
|
|
24
26
|
import { notifyLocalFailure, updateAdvisorUsageStatus } from "./gate-policy.ts";
|
|
25
27
|
import { normalizeScreeningQuestion, screeningSkipText } from "./jev-filter.ts";
|
|
26
28
|
import {
|
|
27
29
|
advisorModelAccessReason,
|
|
30
|
+
fallbackSameModelAdvisorNotice,
|
|
28
31
|
sameModelAdvisorDisabled,
|
|
29
32
|
sameModelAdvisorNotice,
|
|
30
33
|
} from "./model-access.ts";
|
|
@@ -47,6 +50,65 @@ const assertAdvisorModelAccess = (ctx: ExtensionContext) => {
|
|
|
47
50
|
}
|
|
48
51
|
};
|
|
49
52
|
|
|
53
|
+
interface AskAdvisorParams {
|
|
54
|
+
draft?: string;
|
|
55
|
+
followUpTo?: string;
|
|
56
|
+
force?: boolean;
|
|
57
|
+
gitContext?: "none" | "summary" | "full";
|
|
58
|
+
includeTrackedFiles?: string[];
|
|
59
|
+
includeUntracked?: string[];
|
|
60
|
+
question?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const resolveFollowUp = (
|
|
64
|
+
params: AskAdvisorParams,
|
|
65
|
+
session: ToolRegistrationContext["session"]
|
|
66
|
+
): { adviceId: string; payload: AdvisorFollowUpPayload } | undefined => {
|
|
67
|
+
if (!params.followUpTo) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
if (
|
|
71
|
+
!params.question?.trim() ||
|
|
72
|
+
params.draft !== undefined ||
|
|
73
|
+
params.gitContext !== undefined ||
|
|
74
|
+
params.includeTrackedFiles?.length ||
|
|
75
|
+
params.includeUntracked?.length
|
|
76
|
+
) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
"Follow-up consultations accept only followUpTo and a non-empty question. Issue a fresh consultation for new context or attachments."
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
const cached = session.followUpFor(params.followUpTo);
|
|
82
|
+
if (!cached.payload) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Follow-up unavailable for adviceId ${params.followUpTo}: ${cached.reason ?? "the cached payload is unavailable"}. Issue a fresh consultation.`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
return { adviceId: params.followUpTo, payload: cached.payload };
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const sameModelNoticeFor = (modelRef: string | undefined) =>
|
|
91
|
+
(modelRef ?? advisorRef) === advisorRef
|
|
92
|
+
? sameModelAdvisorNotice
|
|
93
|
+
: fallbackSameModelAdvisorNotice;
|
|
94
|
+
|
|
95
|
+
const skippedSameModelResult = (
|
|
96
|
+
id: string,
|
|
97
|
+
notice: string,
|
|
98
|
+
reservedCalls: Set<string>,
|
|
99
|
+
session: ToolRegistrationContext["session"]
|
|
100
|
+
) => {
|
|
101
|
+
session.releaseCall(id);
|
|
102
|
+
reservedCalls.delete(id);
|
|
103
|
+
return {
|
|
104
|
+
content: [{ text: notice, type: "text" as const }],
|
|
105
|
+
details: {
|
|
106
|
+
skipReason: notice,
|
|
107
|
+
text: notice,
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
|
|
50
112
|
const claimTrackedHandoff = (
|
|
51
113
|
session: ToolRegistrationContext["session"],
|
|
52
114
|
includeTrackedFiles: string[] | undefined
|
|
@@ -76,23 +138,25 @@ export const registerAskAdvisorTool = ({
|
|
|
76
138
|
}: ToolRegistrationContext): void => {
|
|
77
139
|
pi.registerTool({
|
|
78
140
|
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
|
|
141
|
+
"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.",
|
|
80
142
|
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
|
-
};
|
|
143
|
+
let followUp:
|
|
144
|
+
| { adviceId: string; payload: AdvisorFollowUpPayload }
|
|
145
|
+
| undefined;
|
|
92
146
|
try {
|
|
93
147
|
loadConfig(ctx);
|
|
94
|
-
|
|
95
|
-
|
|
148
|
+
followUp = resolveFollowUp(params, session);
|
|
149
|
+
if (followUp) {
|
|
150
|
+
assertAdvisorFollowUpPrivacy(followUp.payload, ctx);
|
|
151
|
+
}
|
|
152
|
+
const modelRef = followUp?.payload.model;
|
|
153
|
+
if (sameModelAdvisorDisabled(ctx, ctx.model, modelRef)) {
|
|
154
|
+
return skippedSameModelResult(
|
|
155
|
+
_id,
|
|
156
|
+
sameModelNoticeFor(modelRef),
|
|
157
|
+
reservedCalls,
|
|
158
|
+
session
|
|
159
|
+
);
|
|
96
160
|
}
|
|
97
161
|
assertAdvisorModelAccess(ctx);
|
|
98
162
|
} catch (error) {
|
|
@@ -148,8 +212,13 @@ export const registerAskAdvisorTool = ({
|
|
|
148
212
|
},
|
|
149
213
|
};
|
|
150
214
|
}
|
|
151
|
-
if (sameModelAdvisorDisabled(ctx)) {
|
|
152
|
-
return
|
|
215
|
+
if (sameModelAdvisorDisabled(ctx, ctx.model, followUp?.payload.model)) {
|
|
216
|
+
return skippedSameModelResult(
|
|
217
|
+
_id,
|
|
218
|
+
sameModelNoticeFor(followUp?.payload.model),
|
|
219
|
+
reservedCalls,
|
|
220
|
+
session
|
|
221
|
+
);
|
|
153
222
|
}
|
|
154
223
|
if (
|
|
155
224
|
!simpleMode &&
|
|
@@ -191,7 +260,8 @@ export const registerAskAdvisorTool = ({
|
|
|
191
260
|
coalescedUpdate.update({
|
|
192
261
|
content: [{ text: tx, type: "text" }],
|
|
193
262
|
details: {
|
|
194
|
-
advisor: advisorRef,
|
|
263
|
+
advisor: followUp?.payload.model ?? advisorRef,
|
|
264
|
+
followUp: Boolean(followUp),
|
|
195
265
|
question: resolveAdvisorRequest(params.question),
|
|
196
266
|
scout: scoutDetails,
|
|
197
267
|
text: tx,
|
|
@@ -209,13 +279,17 @@ export const registerAskAdvisorTool = ({
|
|
|
209
279
|
coalescedUpdate.update({
|
|
210
280
|
content: [{ text: scoutDetails.text ?? "", type: "text" }],
|
|
211
281
|
details: {
|
|
212
|
-
advisor: advisorRef,
|
|
282
|
+
advisor: followUp?.payload.model ?? advisorRef,
|
|
283
|
+
followUp: Boolean(followUp),
|
|
213
284
|
question: resolveAdvisorRequest(params.question),
|
|
214
285
|
scout: scoutDetails,
|
|
215
286
|
},
|
|
216
287
|
});
|
|
217
288
|
},
|
|
218
|
-
_id
|
|
289
|
+
_id,
|
|
290
|
+
followUp,
|
|
291
|
+
(adviceId, payload, replacesAdviceId) =>
|
|
292
|
+
session.captureFollowUp(adviceId, payload, replacesAdviceId)
|
|
219
293
|
);
|
|
220
294
|
flushUpdate();
|
|
221
295
|
session.issueAdvice(
|
|
@@ -228,6 +302,7 @@ export const registerAskAdvisorTool = ({
|
|
|
228
302
|
session.recordInvocation({
|
|
229
303
|
cost: advisorUsageCost(result.usage),
|
|
230
304
|
executionEffect: "continued",
|
|
305
|
+
followUp: result.followUp,
|
|
231
306
|
kind: "markdown",
|
|
232
307
|
model: result.model,
|
|
233
308
|
trigger: "executor-requested",
|
|
@@ -239,7 +314,9 @@ export const registerAskAdvisorTool = ({
|
|
|
239
314
|
const details: AdvisorToolDetails = {
|
|
240
315
|
adviceId: result.adviceId,
|
|
241
316
|
advisor: result.model,
|
|
317
|
+
agentRulesBytes: result.agentRulesBytes,
|
|
242
318
|
draftBytes: result.draftBytes,
|
|
319
|
+
followUp: result.followUp,
|
|
243
320
|
imageBytes: result.imageBytes,
|
|
244
321
|
imageCount: result.imageCount,
|
|
245
322
|
imageOmissions: result.imageOmissions,
|
|
@@ -258,7 +335,7 @@ export const registerAskAdvisorTool = ({
|
|
|
258
335
|
const response: AgentToolResult<AdvisorToolDetails> = {
|
|
259
336
|
content: [
|
|
260
337
|
{
|
|
261
|
-
text:
|
|
338
|
+
text: `${result.followUp ? "Advisor follow-up" : "Advisor"} (${result.model})\n\n${result.markdown}`,
|
|
262
339
|
type: "text",
|
|
263
340
|
},
|
|
264
341
|
],
|
|
@@ -279,7 +356,7 @@ export const registerAskAdvisorTool = ({
|
|
|
279
356
|
executionEffect: "continued",
|
|
280
357
|
failure: "provider-error",
|
|
281
358
|
kind: "markdown",
|
|
282
|
-
model: advisorRef,
|
|
359
|
+
model: followUp?.payload.model ?? advisorRef,
|
|
283
360
|
trigger: "executor-requested",
|
|
284
361
|
});
|
|
285
362
|
updateAdvisorUsageStatus(ctx, session);
|
|
@@ -302,6 +379,12 @@ export const registerAskAdvisorTool = ({
|
|
|
302
379
|
"Concise untrusted draft for plan or completion review; claims are not verification evidence.",
|
|
303
380
|
})
|
|
304
381
|
),
|
|
382
|
+
followUpTo: Type.Optional(
|
|
383
|
+
Type.String({
|
|
384
|
+
description:
|
|
385
|
+
"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.",
|
|
386
|
+
})
|
|
387
|
+
),
|
|
305
388
|
force: Type.Optional(
|
|
306
389
|
Type.Boolean({
|
|
307
390
|
description:
|
|
@@ -341,12 +424,16 @@ export const registerAskAdvisorTool = ({
|
|
|
341
424
|
),
|
|
342
425
|
}),
|
|
343
426
|
promptGuidelines: [
|
|
344
|
-
"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.",
|
|
427
|
+
"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.",
|
|
345
428
|
],
|
|
346
429
|
promptSnippet:
|
|
347
430
|
"Consult the Advisor using its existing context; attach a draft for plan or completion review",
|
|
348
431
|
renderCall(args, theme) {
|
|
349
|
-
return renderAdvisorCallBox(
|
|
432
|
+
return renderAdvisorCallBox(
|
|
433
|
+
args.question?.trim(),
|
|
434
|
+
theme,
|
|
435
|
+
Boolean(args.followUpTo)
|
|
436
|
+
);
|
|
350
437
|
},
|
|
351
438
|
renderResult(result, options, theme, context) {
|
|
352
439
|
// 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();
|
|
@@ -20,29 +20,63 @@ interface CallMessageDetails {
|
|
|
20
20
|
|
|
21
21
|
interface TurnGateResultDetails {
|
|
22
22
|
advisor?: string;
|
|
23
|
+
agentRulesBytes?: number;
|
|
23
24
|
text?: string;
|
|
24
25
|
usage?: unknown;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
interface LoopResultDetails {
|
|
28
29
|
advisor?: string;
|
|
30
|
+
agentRulesBytes?: number;
|
|
29
31
|
decision?: GateDecision;
|
|
30
32
|
text?: string;
|
|
31
33
|
usage?: unknown;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
const
|
|
36
|
+
const formatByteSize = (bytes: number) => {
|
|
37
|
+
if (bytes >= 1024 ** 3) {
|
|
38
|
+
return `${(bytes / 1024 ** 3).toFixed(1)} GiB`;
|
|
39
|
+
}
|
|
40
|
+
if (bytes >= 1024 ** 2) {
|
|
41
|
+
return `${(bytes / 1024 ** 2).toFixed(1)} MiB`;
|
|
42
|
+
}
|
|
43
|
+
if (bytes >= 1024) {
|
|
44
|
+
return `${Math.round(bytes / 1024)} KiB`;
|
|
45
|
+
}
|
|
46
|
+
return `${bytes} B`;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const addAttachmentLine = (
|
|
35
50
|
box: Box,
|
|
36
51
|
details: TurnGateResultDetails | LoopResultDetails | undefined,
|
|
37
52
|
theme: Theme
|
|
38
53
|
) => {
|
|
39
|
-
if (
|
|
40
|
-
|
|
54
|
+
if (details?.agentRulesBytes) {
|
|
55
|
+
box.addChild(
|
|
56
|
+
new Text(
|
|
57
|
+
theme.fg(
|
|
58
|
+
"dim",
|
|
59
|
+
` AGENTS.md rules attached · ${formatByteSize(details.agentRulesBytes)}`
|
|
60
|
+
),
|
|
61
|
+
0,
|
|
62
|
+
0
|
|
63
|
+
)
|
|
64
|
+
);
|
|
41
65
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const addUsageLine = (
|
|
69
|
+
box: Box,
|
|
70
|
+
details: TurnGateResultDetails | LoopResultDetails | undefined,
|
|
71
|
+
theme: Theme
|
|
72
|
+
) => {
|
|
73
|
+
if (getAdvisorSettings().showUsageDetails) {
|
|
74
|
+
const usageText = formatAdvisorUsage(details?.usage);
|
|
75
|
+
if (usageText) {
|
|
76
|
+
box.addChild(new Text(theme.fg("dim", ` Usage: ${usageText}`), 0, 0));
|
|
77
|
+
}
|
|
45
78
|
}
|
|
79
|
+
addAttachmentLine(box, details, theme);
|
|
46
80
|
};
|
|
47
81
|
|
|
48
82
|
const callQuestionRenderer: MessageRenderer<CallMessageDetails> = (
|
|
@@ -60,6 +60,9 @@ const formatByteSize = (bytes: number): string => {
|
|
|
60
60
|
|
|
61
61
|
const attachmentLabels = (details: AdvisorToolDetails | undefined) =>
|
|
62
62
|
[
|
|
63
|
+
details?.agentRulesBytes
|
|
64
|
+
? `AGENTS.md rules attached · ${formatByteSize(details.agentRulesBytes)}`
|
|
65
|
+
: undefined,
|
|
63
66
|
details?.draftBytes
|
|
64
67
|
? `Draft attached · ${formatByteSize(details.draftBytes)}`
|
|
65
68
|
: undefined,
|
|
@@ -159,6 +162,9 @@ const finalResultLines = (
|
|
|
159
162
|
theme: Theme
|
|
160
163
|
): string[] => {
|
|
161
164
|
const lines = [renderAdvisorResponseHeader(hasSoundVerdict(advice), theme)];
|
|
165
|
+
if (details?.followUp) {
|
|
166
|
+
lines.push(theme.fg("dim", " Follow-up consultation"));
|
|
167
|
+
}
|
|
162
168
|
if (details?.advisor) {
|
|
163
169
|
lines.push(theme.fg("dim", ` ${details.advisor}`));
|
|
164
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
|
@@ -46,7 +46,9 @@ export interface AdvisorGateFailure {
|
|
|
46
46
|
|
|
47
47
|
export interface AdvisorConsultationResult {
|
|
48
48
|
adviceId: string;
|
|
49
|
+
agentRulesBytes?: number;
|
|
49
50
|
draftBytes?: number;
|
|
51
|
+
followUp?: boolean;
|
|
50
52
|
imageBytes?: number;
|
|
51
53
|
imageCount?: number;
|
|
52
54
|
imageOmissions?: number;
|
|
@@ -63,6 +65,7 @@ export interface AdvisorConsultationResult {
|
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
export interface AdvisorGateResult {
|
|
68
|
+
agentRulesBytes?: number;
|
|
66
69
|
decision: GateDecision;
|
|
67
70
|
markdown: string;
|
|
68
71
|
model: string;
|
|
@@ -86,6 +89,8 @@ export type GateFailureCategory =
|
|
|
86
89
|
export interface AdvisorToolDetails {
|
|
87
90
|
adviceId?: string;
|
|
88
91
|
advisor?: string;
|
|
92
|
+
agentRulesBytes?: number;
|
|
93
|
+
followUp?: boolean;
|
|
89
94
|
draftBytes?: number;
|
|
90
95
|
imageBytes?: number;
|
|
91
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(
|
|
@@ -472,6 +508,13 @@ export const createSettingsItems = ({
|
|
|
472
508
|
settings.redactSecrets,
|
|
473
509
|
false
|
|
474
510
|
),
|
|
511
|
+
toggle(
|
|
512
|
+
"agentsMdContext",
|
|
513
|
+
"AGENTS.md context",
|
|
514
|
+
"Include trusted project and global AGENTS.md rules in Advisor calls.",
|
|
515
|
+
settings.agentsMdContext,
|
|
516
|
+
true
|
|
517
|
+
),
|
|
475
518
|
{
|
|
476
519
|
currentValue: settings.gitContext ?? "summary",
|
|
477
520
|
description: "How much repository context is shared with the Advisor.",
|
|
@@ -1,7 +1,9 @@
|
|
|
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 = [
|
|
6
|
+
"agentsMdContext",
|
|
5
7
|
"autoLoopGate",
|
|
6
8
|
"blockOnBlocked",
|
|
7
9
|
"collapseResponses",
|
|
@@ -54,6 +56,13 @@ const applyCoreMutation = (
|
|
|
54
56
|
settings.alwaysOn = value === "On";
|
|
55
57
|
return true;
|
|
56
58
|
}
|
|
59
|
+
case "fallbackModel": {
|
|
60
|
+
settings.fallbackModel =
|
|
61
|
+
value === FALLBACK_ADVISOR_MODEL_DISABLED
|
|
62
|
+
? undefined
|
|
63
|
+
: value.trim() || undefined;
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
57
66
|
case "effort": {
|
|
58
67
|
settings.effort = value;
|
|
59
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,7 +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 {
|
|
72
|
+
agentsMdContext?: boolean;
|
|
73
|
+
fallbackModel?: string;
|
|
70
74
|
alwaysOn?: boolean;
|
|
71
75
|
autoLoopGate?: boolean;
|
|
72
76
|
blockOnBlocked?: boolean;
|