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.
Files changed (40) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +16 -7
  3. package/dist/index.js +938 -287
  4. package/package.json +2 -2
  5. package/src/commands/activation-preparation.ts +4 -0
  6. package/src/commands/manual-consultation.ts +20 -3
  7. package/src/commands/model-commands.ts +5 -1
  8. package/src/commands/model-picker.ts +44 -2
  9. package/src/commands/outcome-stats.ts +98 -0
  10. package/src/commands/registration.ts +2 -0
  11. package/src/commands/renderers.ts +51 -0
  12. package/src/commands/runtime.ts +9 -1
  13. package/src/commands/settings-persistence.ts +4 -0
  14. package/src/commands/types.ts +5 -2
  15. package/src/config/defaults.ts +13 -0
  16. package/src/config/schema.ts +14 -0
  17. package/src/config/state.ts +10 -0
  18. package/src/config/types.ts +2 -0
  19. package/src/config.ts +4 -0
  20. package/src/follow-up.ts +85 -0
  21. package/src/outcome-stats.ts +154 -0
  22. package/src/outcomes.ts +8 -1
  23. package/src/preferences.ts +90 -12
  24. package/src/session-state.ts +33 -0
  25. package/src/tools/consult-context.ts +12 -1
  26. package/src/tools/consultation.ts +344 -47
  27. package/src/tools/jev-turn-gate.ts +2 -0
  28. package/src/tools/loop-gate.ts +2 -0
  29. package/src/tools/model-access.ts +7 -3
  30. package/src/tools/prompts.ts +28 -2
  31. package/src/tools/register-ask-advisor.ts +110 -23
  32. package/src/tools/register-lifecycle.ts +11 -0
  33. package/src/tools/register-renderers.ts +40 -6
  34. package/src/tools/render-advisor-result.ts +6 -0
  35. package/src/tools/render-common.ts +6 -2
  36. package/src/tools/types.ts +5 -0
  37. package/src/ui/settings-items.ts +43 -0
  38. package/src/ui/settings-mutations.ts +9 -0
  39. package/src/ui/settings-selector.ts +9 -0
  40. 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 follow-up call with includeTrackedFiles when enabled and relevant.",
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
- const skipSameModel = () => {
82
- session.releaseCall(_id);
83
- reservedCalls.delete(_id);
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
- if (sameModelAdvisorDisabled(ctx)) {
95
- return skipSameModel();
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 skipSameModel();
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: `Advisor (${result.model})\n\n${result.markdown}`,
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(args.question?.trim(), theme);
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 addUsageLine = (
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 (!getAdvisorSettings().showUsageDetails) {
40
- return;
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
- const usageText = formatAdvisorUsage(details?.usage);
43
- if (usageText) {
44
- box.addChild(new Text(theme.fg("dim", ` Usage: ${usageText}`), 0, 0));
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("customMessageText", "Executor → Advisor");
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
@@ -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;
@@ -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;