pi-advisor-flow 0.1.9 → 0.2.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/src/commands.ts CHANGED
@@ -1,152 +1,400 @@
1
- import { getMarkdownTheme, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
1
+ import {
2
+ type ExtensionAPI,
3
+ type ExtensionContext,
4
+ getMarkdownTheme,
5
+ } from "@earendil-works/pi-coding-agent";
2
6
  import { Box, Markdown, Text } from "@earendil-works/pi-tui";
3
7
  import {
4
- advisorAutoLoopGateRef, advisorMaxCallsPerSessionRef, advisorBlockOnBlockedRef, advisorCollapseResponsesRef, advisorCompletionGateRef, advisorCustomInvocationRef, advisorFailureGateRef, advisorLoopThresholdRef, advisorPlanGateRef, advisorSessionSummaryRef,
5
- advisorRef, advisorEffortRef, executorRef, executorEffortRef,
6
- setAdvisorAutoLoopGateRef, setAdvisorMaxCallsPerSessionRef, setAdvisorBlockOnBlockedRef, setAdvisorCollapseResponsesRef, setAdvisorCompletionGateRef, setAdvisorCustomInvocationRef, setAdvisorFailureGateRef, setAdvisorLoopThresholdRef, setAdvisorPlanGateRef, setAdvisorSessionSummaryRef,
7
- setAdvisorRef, setAdvisorEffortRef, setContextMaxCharsRef, setExecutorRef, setExecutorEffortRef,
8
- contextMaxCharsRef, loadConfig, saveConfig, parseArgs, splitRef,
8
+ advisorAutoLoopGateRef,
9
+ advisorBlockOnBlockedRef,
10
+ advisorCollapseResponsesRef,
11
+ advisorCompletionGateRef,
12
+ advisorCustomInvocationRef,
13
+ advisorEffortRef,
14
+ advisorFailureGateRef,
15
+ advisorFailureModeRef,
16
+ advisorHerdrIntegrationRef,
17
+ advisorLoopThresholdRef,
18
+ advisorMaxCallsPerSessionRef,
19
+ advisorPlanGateRef,
20
+ advisorRef,
21
+ advisorSessionSummaryRef,
22
+ advisorToolResultMaxBytesRef,
23
+ advisorToolResultMaxLinesRef,
24
+ contextMaxCharsRef,
25
+ executorEffortRef,
26
+ executorRef,
27
+ loadConfig,
28
+ parseArgs,
29
+ saveConfig,
30
+ setAdvisorAutoLoopGateRef,
31
+ setAdvisorBlockOnBlockedRef,
32
+ setAdvisorCollapseResponsesRef,
33
+ setAdvisorCompletionGateRef,
34
+ setAdvisorCustomInvocationRef,
35
+ setAdvisorEffortRef,
36
+ setAdvisorFailureGateRef,
37
+ setAdvisorFailureModeRef,
38
+ setAdvisorHerdrIntegrationRef,
39
+ setAdvisorLoopThresholdRef,
40
+ setAdvisorMaxCallsPerSessionRef,
41
+ setAdvisorPlanGateRef,
42
+ setAdvisorRef,
43
+ setAdvisorSessionSummaryRef,
44
+ setAdvisorToolResultMaxBytesRef,
45
+ setAdvisorToolResultMaxLinesRef,
46
+ setContextMaxCharsRef,
47
+ setExecutorEffortRef,
48
+ setExecutorRef,
49
+ splitRef,
9
50
  } from "./config.js";
10
- import { AdvisorSettingsSelector, SearchableModelSelector, type AdvisorSettings, type ContextPreset } from "./ui.js";
11
- import { herdrAdvisorActivity } from "./herdr.js";
12
- import { adviceForDisplay, advisorSessionState, consult, renderAdvisorCallBox, resolveAdvisorRequest } from "./tools.js";
51
+ import { herdrAdvisorActivity, notifyHerdrAdvisorFailure } from "./herdr.js";
52
+ import {
53
+ adviceForDisplay,
54
+ advisorSessionState,
55
+ consultAdvisor,
56
+ renderAdvisorCallBox,
57
+ resolveAdvisorRequest,
58
+ } from "./tools.js";
59
+ import {
60
+ type AdvisorSettings,
61
+ AdvisorSettingsSelector,
62
+ type ContextPreset,
63
+ SearchableModelSelector,
64
+ } from "./ui.js";
13
65
 
14
- const EFFORT_LEVELS = ["Default (Model Default)", "off", "minimal", "low", "medium", "high", "xhigh", "max"];
66
+ const EFFORT_LEVELS = [
67
+ "Default (Model Default)",
68
+ "off",
69
+ "minimal",
70
+ "low",
71
+ "medium",
72
+ "high",
73
+ "xhigh",
74
+ "max",
75
+ ];
15
76
 
16
77
  const CONTEXT_PRESETS: ContextPreset[] = [
17
- { label: "0", value: 0, description: "No conversation history. The Advisor receives only its standing instructions." },
18
- { label: "10k", value: 10_000, description: "The most recent 10,000 characters of the current branch." },
19
- { label: "25k", value: 25_000, description: "The most recent 25,000 characters of the current branch." },
20
- { label: "100k", value: 100_000, description: "The most recent 100,000 characters of the current branch." },
21
- { label: "200k", value: 200_000, description: "The most recent 200,000 characters of the current branch." },
22
- { label: "ALL", value: Number.MAX_SAFE_INTEGER, description: "The complete reconstructed conversation branch. Cost and model context limits apply." },
78
+ {
79
+ description:
80
+ "No conversation history. The Advisor receives only its standing instructions.",
81
+ label: "0",
82
+ value: 0,
83
+ },
84
+ {
85
+ description: "The most recent 10,000 characters of the current branch.",
86
+ label: "10k",
87
+ value: 10_000,
88
+ },
89
+ {
90
+ description: "The most recent 25,000 characters of the current branch.",
91
+ label: "25k",
92
+ value: 25_000,
93
+ },
94
+ {
95
+ description: "The most recent 100,000 characters of the current branch.",
96
+ label: "100k",
97
+ value: 100_000,
98
+ },
99
+ {
100
+ description: "The most recent 200,000 characters of the current branch.",
101
+ label: "200k",
102
+ value: 200_000,
103
+ },
104
+ {
105
+ description:
106
+ "The complete reconstructed conversation branch. Cost and model context limits apply.",
107
+ label: "ALL",
108
+ value: Number.MAX_SAFE_INTEGER,
109
+ },
23
110
  ];
24
111
 
25
- type ManualConsult = (ctx: ExtensionContext, question?: string, signal?: AbortSignal) => Promise<{ advice: string; thinkingText: string }>;
112
+ type ManualConsult = (
113
+ ctx: ExtensionContext,
114
+ question?: string,
115
+ signal?: AbortSignal
116
+ ) => Promise<{ markdown: string; thinkingText: string }>;
117
+ type ThinkingLevel = Parameters<ExtensionAPI["setThinkingLevel"]>[0];
118
+
119
+ const notify = (
120
+ ctx: ExtensionContext,
121
+ message: string,
122
+ level: "error" | "info" | "warning"
123
+ ) => {
124
+ if (ctx.hasUI) {
125
+ ctx.ui.notify(message, level);
126
+ }
127
+ };
128
+
129
+ const findConfiguredModel = (ctx: ExtensionContext, ref: string) => {
130
+ const [provider, modelId] = splitRef(ref);
131
+ return ctx.modelRegistry.find(provider, modelId);
132
+ };
26
133
 
27
- export const registerCommands = (pi: ExtensionAPI, dependencies: { consult?: ManualConsult } = {}) => {
134
+ export const registerCommands = (
135
+ pi: ExtensionAPI,
136
+ dependencies: { consult?: ManualConsult } = {}
137
+ ) => {
28
138
  const flowEnabled = () => pi.getActiveTools().includes("ask_advisor");
29
- const requestAdvisor = dependencies.consult ?? consult;
139
+ const requestAdvisor =
140
+ dependencies.consult ??
141
+ ((ctx, question, signal) =>
142
+ consultAdvisor(ctx, question, signal, undefined, "manual"));
30
143
  const manualConsultations = new Set<AbortController>();
31
144
 
32
- pi.registerEntryRenderer?.("advisor-manual-call", (entry, _options, theme) => {
33
- const { question } = (entry.data ?? {}) as { question?: string };
34
- return renderAdvisorCallBox(question, theme);
35
- });
145
+ const startManualConsultation = (
146
+ ctx: ExtensionContext,
147
+ question: string | undefined,
148
+ controller: AbortController
149
+ ) => {
150
+ herdrAdvisorActivity.start();
151
+ return requestAdvisor(ctx, question, controller.signal)
152
+ .then(({ markdown }) => {
153
+ advisorSessionState.recordInvocation({
154
+ executionEffect: "continued",
155
+ kind: "markdown",
156
+ model: advisorRef,
157
+ trigger: "manual",
158
+ });
159
+ if (controller.signal.aborted) {
160
+ return;
161
+ }
162
+ pi.sendMessage(
163
+ {
164
+ content: `Manual Advisor consultation${question ? ` (${question})` : ""}:\n\n${markdown}`,
165
+ customType: "advisor-manual-result",
166
+ details: { advisor: advisorRef, question, text: markdown },
167
+ display: true,
168
+ },
169
+ {
170
+ // Steer lets the current turn finish its active work; the Executor sees
171
+ // the result before its next model call rather than being interrupted.
172
+ deliverAs: "steer",
173
+ triggerTurn: true,
174
+ }
175
+ );
176
+ })
177
+ .catch((error: unknown) => {
178
+ if (controller.signal.aborted) {
179
+ return;
180
+ }
181
+ const message = error instanceof Error ? error.message : String(error);
182
+ advisorSessionState.recordInvocation({
183
+ executionEffect: "continued",
184
+ failure: "provider-error",
185
+ kind: "markdown",
186
+ model: advisorRef,
187
+ trigger: "manual",
188
+ });
189
+ pi.sendMessage(
190
+ {
191
+ content: `Manual Advisor consultation failed: ${message}`,
192
+ customType: "advisor-manual-result",
193
+ details: {
194
+ advisor: advisorRef,
195
+ text: `**Advisor consultation failed:** ${message}`,
196
+ },
197
+ display: true,
198
+ },
199
+ { deliverAs: "steer", triggerTurn: true }
200
+ );
201
+ notify(ctx, `Advisor consultation failed: ${message}`, "error");
202
+ notifyHerdrAdvisorFailure("Advisor consultation failed", message);
203
+ })
204
+ .finally(() => {
205
+ manualConsultations.delete(controller);
206
+ herdrAdvisorActivity.finish();
207
+ });
208
+ };
36
209
 
37
- pi.registerMessageRenderer?.("advisor-manual-result", (message, { expanded }, theme) => {
38
- const details = message.details as { advisor?: string; text?: string } | undefined;
39
- const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
40
- box.addChild(new Text(theme.fg("warning", theme.bold("◆ ADVISOR RESPONSE")), 0, 0));
41
- if (details?.advisor) box.addChild(new Text(theme.fg("dim", ` ${details.advisor}`), 0, 0));
42
- const advice = details?.text ?? (typeof message.content === "string" ? message.content : "(Advisor returned no advice.)");
43
- box.addChild(new Markdown(adviceForDisplay(advice, expanded), 0, 0, getMarkdownTheme()));
44
- return box;
45
- });
210
+ const activateAdvisor = async (args: string, ctx: ExtensionContext) => {
211
+ loadConfig(ctx);
212
+ const argumentError = parseArgs(args);
213
+ if (argumentError) {
214
+ notify(ctx, argumentError, "error");
215
+ return;
216
+ }
217
+ const executor = findConfiguredModel(ctx, executorRef);
218
+ if (!executor) {
219
+ notify(ctx, `Executor model not found: ${executorRef}`, "error");
220
+ return;
221
+ }
222
+ if (!findConfiguredModel(ctx, advisorRef)) {
223
+ notify(ctx, `Advisor model not found: ${advisorRef}`, "error");
224
+ return;
225
+ }
226
+ if (!(await pi.setModel(executor))) {
227
+ notify(ctx, `No API key for Executor ${executorRef}`, "error");
228
+ return;
229
+ }
230
+ if (executorEffortRef) {
231
+ pi.setThinkingLevel(executorEffortRef as ThinkingLevel);
232
+ }
233
+ if (!flowEnabled()) {
234
+ pi.setActiveTools([...pi.getActiveTools(), "ask_advisor"]);
235
+ }
236
+ notify(
237
+ ctx,
238
+ `Advisor flow ready — Executor: ${executorRef} (thinking: ${executorEffortRef || "default"}) · Advisor: ${advisorRef} (thinking: ${advisorEffortRef || "default"})`,
239
+ "info"
240
+ );
241
+ };
242
+
243
+ pi.registerEntryRenderer?.(
244
+ "advisor-manual-call",
245
+ (entry, _options, theme) => {
246
+ const { question } = (entry.data ?? {}) as { question?: string };
247
+ return renderAdvisorCallBox(question, theme);
248
+ }
249
+ );
250
+
251
+ pi.registerMessageRenderer?.(
252
+ "advisor-manual-result",
253
+ (message, { expanded }, theme) => {
254
+ const details = message.details as
255
+ | { advisor?: string; text?: string }
256
+ | undefined;
257
+ const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
258
+ box.addChild(
259
+ new Text(theme.fg("warning", theme.bold("◆ ADVISOR RESPONSE")), 0, 0)
260
+ );
261
+ if (details?.advisor) {
262
+ box.addChild(new Text(theme.fg("dim", ` ${details.advisor}`), 0, 0));
263
+ }
264
+ const advice =
265
+ details?.text ??
266
+ (typeof message.content === "string"
267
+ ? message.content
268
+ : "(Advisor returned no advice.)");
269
+ box.addChild(
270
+ new Markdown(
271
+ adviceForDisplay(advice, expanded),
272
+ 0,
273
+ 0,
274
+ getMarkdownTheme()
275
+ )
276
+ );
277
+ return box;
278
+ }
279
+ );
46
280
 
47
281
  pi.on("session_shutdown", () => {
48
- for (const controller of manualConsultations) controller.abort();
282
+ for (const controller of manualConsultations) {
283
+ controller.abort();
284
+ }
49
285
  manualConsultations.clear();
50
286
  herdrAdvisorActivity.clear();
51
287
  });
52
288
 
53
289
  pi.registerCommand("advisor-manual", {
54
- description: "Consult the Advisor in parallel; accepts an optional focused question and fans its response out to the Executor",
55
- handler: async (args, ctx) => {
290
+ description:
291
+ "Consult the Advisor in parallel; accepts an optional focused question and fans its response out to the Executor",
292
+ handler: (args, ctx) => {
56
293
  loadConfig(ctx);
57
- if (!advisorSessionState.canUseAutomaticCall(advisorMaxCallsPerSessionRef)) {
58
- if (ctx.hasUI) ctx.ui.notify("Advisor call budget exhausted for this session.", "warning");
59
- return;
294
+ if (!advisorSessionState.canConsult(advisorMaxCallsPerSessionRef)) {
295
+ const message = "Advisor call budget exhausted for this session.";
296
+ notify(ctx, message, "warning");
297
+ notifyHerdrAdvisorFailure("Advisor budget exhausted", message);
298
+ return Promise.resolve();
60
299
  }
61
- advisorSessionState.consumeAutomaticCall();
300
+ advisorSessionState.consumeCall();
62
301
  const question = resolveAdvisorRequest(args);
63
302
  // A single visible progress surface avoids competing consultations overwriting
64
303
  // each other's streamed state. A newer manual request replaces the previous one.
65
- for (const pending of manualConsultations) pending.abort();
304
+ for (const pending of manualConsultations) {
305
+ pending.abort();
306
+ }
66
307
  manualConsultations.clear();
67
308
  const controller = new AbortController();
68
309
  manualConsultations.add(controller);
69
310
  pi.appendEntry?.("advisor-manual-call", { question });
70
-
71
- herdrAdvisorActivity.start();
72
- void requestAdvisor(ctx, question, controller.signal)
73
- .then(({ advice }) => {
74
- advisorSessionState.recordConsultation("manual");
75
- if (controller.signal.aborted) return;
76
- pi.sendMessage({
77
- customType: "advisor-manual-result",
78
- content: `Manual Advisor consultation${question ? ` (${question})` : ""}:\n\n${advice}`,
79
- display: true,
80
- details: { advisor: advisorRef, question, text: advice },
81
- }, {
82
- // Steer lets the current turn finish its active work; the Executor sees
83
- // the result before its next model call rather than being interrupted.
84
- deliverAs: "steer",
85
- triggerTurn: true,
86
- });
87
- })
88
- .catch((error: unknown) => {
89
- if (controller.signal.aborted) return;
90
- const message = error instanceof Error ? error.message : String(error);
91
- if (ctx.hasUI) ctx.ui.notify(`Advisor consultation failed: ${message}`, "error");
92
- })
93
- .finally(() => {
94
- manualConsultations.delete(controller);
95
- herdrAdvisorActivity.finish();
96
- });
311
+ startManualConsultation(ctx, question, controller);
312
+ return Promise.resolve();
97
313
  },
98
314
  });
99
315
 
100
316
  pi.registerCommand("advisor", {
101
- description: "Enable the Executor/Advisor flow and switch to the configured Executor model; accepts contextMaxChars=N",
102
- handler: async (args, ctx) => {
103
- loadConfig(ctx);
104
- const argumentError = parseArgs(args);
105
- if (argumentError) {
106
- if (ctx.hasUI) ctx.ui.notify(argumentError, "error");
107
- return;
108
- }
109
- const [provider, modelId] = splitRef(executorRef);
110
- const executor = ctx.modelRegistry.find(provider, modelId);
111
- if (!executor) return ctx.hasUI ? ctx.ui.notify(`Executor model not found: ${executorRef}`, "error") : undefined;
112
- const [ap, am] = splitRef(advisorRef);
113
- if (!ctx.modelRegistry.find(ap, am)) return ctx.hasUI ? ctx.ui.notify(`Advisor model not found: ${advisorRef}`, "error") : undefined;
114
- if (!(await pi.setModel(executor))) return ctx.hasUI ? ctx.ui.notify(`No API key for Executor ${executorRef}`, "error") : undefined;
115
- if (executorEffortRef) pi.setThinkingLevel(executorEffortRef as any);
116
- if (!flowEnabled()) pi.setActiveTools([...pi.getActiveTools(), "ask_advisor"]);
117
- if (ctx.hasUI) ctx.ui.notify(`Advisor flow ready — Executor: ${executorRef} (thinking: ${executorEffortRef || "default"}) · Advisor: ${advisorRef} (thinking: ${advisorEffortRef || "default"})`, "info");
118
- },
317
+ description:
318
+ "Enable the Executor/Advisor flow and switch to the configured Executor model; accepts contextMaxChars=N",
319
+ handler: (args, ctx) => activateAdvisor(args, ctx),
119
320
  });
120
321
 
121
322
  pi.registerCommand("advisor-models", {
122
- description: "Select and persist the Executor and Advisor models with reasoning levels",
323
+ description:
324
+ "Select and persist the Executor and Advisor models with reasoning levels",
123
325
  handler: async (_args, ctx) => {
124
- if (!ctx.hasUI) return;
125
- const refs = ctx.modelRegistry.getAvailable().map((m) => `${m.provider}/${m.id}`);
326
+ loadConfig(ctx);
327
+ if (!ctx.hasUI) {
328
+ return;
329
+ }
330
+ const refs = ctx.modelRegistry
331
+ .getAvailable()
332
+ .map((m) => `${m.provider}/${m.id}`);
126
333
 
127
- const executor = await ctx.ui.custom<string | undefined>((tui, theme, keybindings, done) =>
128
- new SearchableModelSelector({ tui, title: "Select Executor Model", allOptions: refs, theme, keybindings, onSelect: done, onCancel: () => done(undefined) })
334
+ const executor = await ctx.ui.custom<string | undefined>(
335
+ (tui, theme, keybindings, done) =>
336
+ new SearchableModelSelector({
337
+ allOptions: refs,
338
+ keybindings,
339
+ onCancel: () => done(undefined),
340
+ onSelect: done,
341
+ theme,
342
+ title: "Select Executor Model",
343
+ tui,
344
+ })
129
345
  );
130
- if (!executor) return;
346
+ if (!executor) {
347
+ return;
348
+ }
131
349
 
132
- const executorEffort = await ctx.ui.select("Select Executor Reasoning/Thinking Level", EFFORT_LEVELS);
133
- if (!executorEffort) return;
350
+ const executorEffort = await ctx.ui.select(
351
+ "Select Executor Reasoning/Thinking Level",
352
+ EFFORT_LEVELS
353
+ );
354
+ if (!executorEffort) {
355
+ return;
356
+ }
134
357
 
135
- const advisor = await ctx.ui.custom<string | undefined>((tui, theme, keybindings, done) =>
136
- new SearchableModelSelector({ tui, title: "Select Advisor Model", allOptions: refs, theme, keybindings, onSelect: done, onCancel: () => done(undefined) })
358
+ const advisor = await ctx.ui.custom<string | undefined>(
359
+ (tui, theme, keybindings, done) =>
360
+ new SearchableModelSelector({
361
+ allOptions: refs,
362
+ keybindings,
363
+ onCancel: () => done(undefined),
364
+ onSelect: done,
365
+ theme,
366
+ title: "Select Advisor Model",
367
+ tui,
368
+ })
137
369
  );
138
- if (!advisor) return;
370
+ if (!advisor) {
371
+ return;
372
+ }
139
373
 
140
- const advisorEffort = await ctx.ui.select("Select Advisor Reasoning/Thinking Level", EFFORT_LEVELS);
141
- if (!advisorEffort) return;
374
+ const advisorEffort = await ctx.ui.select(
375
+ "Select Advisor Reasoning/Thinking Level",
376
+ EFFORT_LEVELS
377
+ );
378
+ if (!advisorEffort) {
379
+ return;
380
+ }
142
381
 
143
382
  setExecutorRef(executor);
144
383
  setAdvisorRef(advisor);
145
- setExecutorEffortRef(executorEffort === "Default (Model Default)" ? undefined : executorEffort);
146
- setAdvisorEffortRef(advisorEffort === "Default (Model Default)" ? undefined : advisorEffort);
384
+ setExecutorEffortRef(
385
+ executorEffort === "Default (Model Default)"
386
+ ? undefined
387
+ : executorEffort
388
+ );
389
+ setAdvisorEffortRef(
390
+ advisorEffort === "Default (Model Default)" ? undefined : advisorEffort
391
+ );
147
392
 
148
393
  const path = saveConfig(ctx);
149
- ctx.ui.notify(`Saved Executor + Advisor configurations to ${path}`, "info");
394
+ ctx.ui.notify(
395
+ `Saved Executor + Advisor configurations to ${path}`,
396
+ "info"
397
+ );
150
398
  },
151
399
  });
152
400
 
@@ -154,36 +402,49 @@ export const registerCommands = (pi: ExtensionAPI, dependencies: { consult?: Man
154
402
  description: "Configure Advisor context and reasoning effort",
155
403
  handler: async (_args, ctx) => {
156
404
  loadConfig(ctx);
157
- if (!ctx.hasUI) return;
405
+ if (!ctx.hasUI) {
406
+ return;
407
+ }
158
408
 
159
409
  const initial: AdvisorSettings = {
410
+ autoLoopGate: advisorAutoLoopGateRef,
411
+ blockOnBlocked: advisorBlockOnBlockedRef,
412
+ collapseResponses: advisorCollapseResponsesRef,
413
+ completionGate: advisorCompletionGateRef,
160
414
  contextMaxChars: contextMaxCharsRef,
415
+ customRule: advisorCustomInvocationRef,
161
416
  effort: advisorEffortRef,
162
- planGate: advisorPlanGateRef,
163
417
  failureGate: advisorFailureGateRef,
164
- completionGate: advisorCompletionGateRef,
165
- collapseResponses: advisorCollapseResponsesRef,
166
- customRule: advisorCustomInvocationRef,
167
- blockOnBlocked: advisorBlockOnBlockedRef,
168
- autoLoopGate: advisorAutoLoopGateRef,
418
+ failureMode: advisorFailureModeRef,
419
+ herdrIntegration: advisorHerdrIntegrationRef,
169
420
  loopThreshold: advisorLoopThresholdRef,
170
421
  maxCallsPerSession: advisorMaxCallsPerSessionRef,
422
+ planGate: advisorPlanGateRef,
171
423
  sessionSummary: advisorSessionSummaryRef,
424
+ toolResultMaxBytes: advisorToolResultMaxBytesRef,
425
+ toolResultMaxLines: advisorToolResultMaxLinesRef,
172
426
  };
173
- const settings = await ctx.ui.custom<AdvisorSettings | undefined>((tui, theme, _keybindings, done) =>
174
- new AdvisorSettingsSelector({
175
- tui,
176
- theme,
177
- presets: CONTEXT_PRESETS,
178
- effortLevels: EFFORT_LEVELS,
179
- initial,
180
- onSave: done,
181
- onCancel: () => done(undefined),
182
- })
427
+ const settings = await ctx.ui.custom<AdvisorSettings | undefined>(
428
+ (tui, theme, _keybindings, done) =>
429
+ new AdvisorSettingsSelector({
430
+ effortLevels: EFFORT_LEVELS,
431
+ initial,
432
+ onCancel: () => done(undefined),
433
+ onSave: done,
434
+ presets: CONTEXT_PRESETS,
435
+ theme,
436
+ tui,
437
+ })
183
438
  );
184
- if (!settings) return;
439
+ if (!settings) {
440
+ return;
441
+ }
185
442
 
186
- setAdvisorEffortRef(settings.effort === "Default (Model Default)" ? undefined : settings.effort);
443
+ setAdvisorEffortRef(
444
+ settings.effort === "Default (Model Default)"
445
+ ? undefined
446
+ : settings.effort
447
+ );
187
448
  setContextMaxCharsRef(settings.contextMaxChars);
188
449
  setAdvisorPlanGateRef(settings.planGate);
189
450
  setAdvisorFailureGateRef(settings.failureGate);
@@ -195,6 +456,10 @@ export const registerCommands = (pi: ExtensionAPI, dependencies: { consult?: Man
195
456
  setAdvisorLoopThresholdRef(settings.loopThreshold ?? 3);
196
457
  setAdvisorMaxCallsPerSessionRef(settings.maxCallsPerSession);
197
458
  setAdvisorSessionSummaryRef(settings.sessionSummary ?? true);
459
+ setAdvisorFailureModeRef(settings.failureMode ?? "block-session");
460
+ setAdvisorHerdrIntegrationRef(settings.herdrIntegration ?? true);
461
+ setAdvisorToolResultMaxLinesRef(settings.toolResultMaxLines ?? 2000);
462
+ setAdvisorToolResultMaxBytesRef(settings.toolResultMaxBytes ?? 50 * 1024);
198
463
  const path = saveConfig(ctx);
199
464
  ctx.ui.notify(`Saved Advisor settings to ${path}`, "info");
200
465
  },
@@ -202,9 +467,17 @@ export const registerCommands = (pi: ExtensionAPI, dependencies: { consult?: Man
202
467
 
203
468
  pi.registerCommand("advisor-off", {
204
469
  description: "Disable on-demand Advisor calls; keep the current model",
205
- handler: async (_args, ctx) => {
206
- pi.setActiveTools(pi.getActiveTools().filter((name) => name !== "ask_advisor"));
207
- if (ctx.hasUI) ctx.ui.notify("Advisor flow disabled. Current model unchanged.", "info");
470
+ handler: (_args, ctx) => {
471
+ pi.setActiveTools(
472
+ pi.getActiveTools().filter((name) => name !== "ask_advisor")
473
+ );
474
+ if (ctx.hasUI) {
475
+ ctx.ui.notify(
476
+ "Advisor flow disabled. Current model unchanged.",
477
+ "info"
478
+ );
479
+ }
480
+ return Promise.resolve();
208
481
  },
209
482
  });
210
483
  };