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/ui.ts CHANGED
@@ -1,35 +1,60 @@
1
- import { Input, Key, matchesKey, fuzzyFilter, truncateToWidth, type Component, type Focusable } from "@earendil-works/pi-tui";
2
- import { Box, Text } from "@earendil-works/pi-tui";
3
1
  import type { Theme } from "@earendil-works/pi-coding-agent";
2
+ import {
3
+ type Component,
4
+ type Focusable,
5
+ fuzzyFilter,
6
+ Input,
7
+ Key,
8
+ type Keybindings,
9
+ type KeybindingsManager,
10
+ matchesKey,
11
+ truncateToWidth,
12
+ } from "@earendil-works/pi-tui";
13
+
14
+ interface RenderRequester {
15
+ requestRender: () => void;
16
+ }
17
+ interface SearchableModelSelectorOptions {
18
+ allOptions: string[];
19
+ keybindings: KeybindingsManager;
20
+ onCancel: () => void;
21
+ onSelect: (value: string) => void;
22
+ theme: Theme;
23
+ title: string;
24
+ tui: RenderRequester;
25
+ }
26
+ interface AdvisorSettingsSelectorOptions {
27
+ effortLevels: string[];
28
+ initial: AdvisorSettings;
29
+ onCancel: () => void;
30
+ onSave: (settings: AdvisorSettings) => void;
31
+ presets: ContextPreset[];
32
+ theme: Theme;
33
+ tui: RenderRequester;
34
+ }
4
35
 
5
36
  export class SearchableModelSelector implements Component, Focusable {
6
- private tui: any;
7
- private searchInput: Input;
8
- private allOptions: string[];
37
+ private readonly tui: RenderRequester;
38
+ private readonly searchInput: Input;
39
+ private readonly allOptions: string[];
9
40
  private filteredOptions: string[];
10
41
  private selectedIndex = 0;
11
- private title: string;
12
- private onSelect: (value: string) => void;
13
- private onCancel: () => void;
14
- private theme: Theme;
15
- private keybindings: any;
42
+ private readonly title: string;
43
+ private readonly onSelect: (value: string) => void;
44
+ private readonly onCancel: () => void;
45
+ private readonly theme: Theme;
46
+ private readonly keybindings: KeybindingsManager;
16
47
  private _focused = false;
17
48
 
18
- public get focused(): boolean { return this._focused; }
19
- public set focused(val: boolean) {
49
+ get focused(): boolean {
50
+ return this._focused;
51
+ }
52
+ set focused(val: boolean) {
20
53
  this._focused = val;
21
54
  this.searchInput.focused = val;
22
55
  }
23
56
 
24
- constructor(options: {
25
- tui: any;
26
- title: string;
27
- allOptions: string[];
28
- theme: Theme;
29
- keybindings: any;
30
- onSelect: (value: string) => void;
31
- onCancel: () => void;
32
- }) {
57
+ constructor(options: SearchableModelSelectorOptions) {
33
58
  this.tui = options.tui;
34
59
  this.title = options.title;
35
60
  this.allOptions = options.allOptions;
@@ -41,103 +66,177 @@ export class SearchableModelSelector implements Component, Focusable {
41
66
  this.filteredOptions = this.allOptions;
42
67
  }
43
68
 
44
- invalidate(): void { this.searchInput.invalidate(); }
69
+ invalidate(): void {
70
+ this.searchInput.invalidate();
71
+ }
45
72
 
46
73
  render(width: number): string[] {
47
74
  const lines: string[] = ["═".repeat(width)];
48
75
  lines.push(` ${this.theme.fg("accent", this.theme.bold(this.title))}`);
49
76
  const inputLines = this.searchInput.render(width - 12);
50
- lines.push(` ${this.theme.fg("accent", "Search: ")}${inputLines[0] || ""}`);
77
+ lines.push(
78
+ ` ${this.theme.fg("accent", "Search: ")}${inputLines[0] || ""}`
79
+ );
51
80
  lines.push("");
52
81
 
53
82
  const query = this.searchInput.getValue().trim();
54
- this.filteredOptions = query ? fuzzyFilter(this.allOptions, query, (item) => item) : this.allOptions;
55
- this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredOptions.length - 1));
83
+ this.filteredOptions = query
84
+ ? fuzzyFilter(this.allOptions, query, (item) => item)
85
+ : this.allOptions;
86
+ this.selectedIndex = Math.min(
87
+ this.selectedIndex,
88
+ Math.max(0, this.filteredOptions.length - 1)
89
+ );
56
90
 
57
91
  const maxVisible = 10;
58
92
  const total = this.filteredOptions.length;
59
93
  if (total === 0) {
60
- lines.push(" " + this.theme.fg("muted", "No matching models found."));
94
+ lines.push(` ${this.theme.fg("muted", "No matching models found.")}`);
61
95
  } else {
62
- const startIndex = Math.max(0, Math.min(this.selectedIndex - Math.floor(maxVisible / 2), total - maxVisible));
96
+ const startIndex = Math.max(
97
+ 0,
98
+ Math.min(
99
+ this.selectedIndex - Math.floor(maxVisible / 2),
100
+ total - maxVisible
101
+ )
102
+ );
63
103
  const endIndex = Math.min(startIndex + maxVisible, total);
64
- for (let i = startIndex; i < endIndex; i++) {
104
+ for (let i = startIndex; i < endIndex; i += 1) {
65
105
  const item = this.filteredOptions[i];
66
- if (i === this.selectedIndex) lines.push(` ${this.theme.fg("accent", "→ ")}${this.theme.fg("accent", item)}`);
67
- else lines.push(` ${this.theme.fg("text", item)}`);
106
+ if (i === this.selectedIndex) {
107
+ lines.push(
108
+ ` ${this.theme.fg("accent", "→ ")}${this.theme.fg("accent", item)}`
109
+ );
110
+ } else {
111
+ lines.push(` ${this.theme.fg("text", item)}`);
112
+ }
113
+ }
114
+ if (total > maxVisible) {
115
+ lines.push(
116
+ " " +
117
+ this.theme.fg("muted", ` (${this.selectedIndex + 1}/${total})`)
118
+ );
68
119
  }
69
- if (total > maxVisible) lines.push(" " + this.theme.fg("muted", ` (${this.selectedIndex + 1}/${total})`));
70
120
  }
71
121
  lines.push("");
72
- lines.push(` ${this.theme.fg("muted", "Type to search · ↑↓: navigate · Enter: select · Esc: cancel")}`);
122
+ lines.push(
123
+ ` ${this.theme.fg("muted", "Type to search · ↑↓: navigate · Enter: select · Esc: cancel")}`
124
+ );
73
125
  lines.push("═".repeat(width));
74
126
  return lines;
75
127
  }
76
128
 
77
129
  handleInput(keyData: string): void {
78
- const kb = this.keybindings;
79
- if (kb.matches(keyData, "tui.select.up") || keyData === "\u001b[A") {
80
- if (this.filteredOptions.length > 0) this.selectedIndex = this.selectedIndex === 0 ? this.filteredOptions.length - 1 : this.selectedIndex - 1;
81
- this.tui.requestRender();
82
- } else if (kb.matches(keyData, "tui.select.down") || keyData === "\u001b[B") {
83
- if (this.filteredOptions.length > 0) this.selectedIndex = this.selectedIndex === this.filteredOptions.length - 1 ? 0 : this.selectedIndex + 1;
84
- this.tui.requestRender();
85
- } else if (kb.matches(keyData, "tui.select.confirm") || keyData === "\n" || keyData === "\r") {
86
- if (this.filteredOptions.length > 0) this.onSelect(this.filteredOptions[this.selectedIndex]);
87
- } else if (kb.matches(keyData, "tui.select.cancel") || keyData === "\u001b") {
130
+ if (this.matchesAction(keyData, "tui.select.up", "\u001b[A")) {
131
+ this.moveSelection(-1);
132
+ return;
133
+ }
134
+ if (this.matchesAction(keyData, "tui.select.down", "\u001b[B")) {
135
+ this.moveSelection(1);
136
+ return;
137
+ }
138
+ if (
139
+ this.matchesAction(keyData, "tui.select.confirm", "\n") ||
140
+ keyData === "\r"
141
+ ) {
142
+ if (this.filteredOptions.length > 0) {
143
+ this.onSelect(this.filteredOptions[this.selectedIndex]);
144
+ }
145
+ return;
146
+ }
147
+ if (this.matchesAction(keyData, "tui.select.cancel", "\u001b")) {
88
148
  this.onCancel();
89
- } else {
90
- this.searchInput.handleInput(keyData);
91
- this.selectedIndex = 0;
92
- this.tui.requestRender();
149
+ return;
150
+ }
151
+ this.searchInput.handleInput(keyData);
152
+ this.selectedIndex = 0;
153
+ this.tui.requestRender();
154
+ }
155
+
156
+ private matchesAction(
157
+ keyData: string,
158
+ action: keyof Keybindings,
159
+ fallback: string
160
+ ) {
161
+ return this.keybindings.matches(keyData, action) || keyData === fallback;
162
+ }
163
+
164
+ private moveSelection(direction: -1 | 1) {
165
+ if (this.filteredOptions.length > 0) {
166
+ const lastIndex = this.filteredOptions.length - 1;
167
+ const nextIndex = this.selectedIndex + direction;
168
+ if (nextIndex < 0) {
169
+ this.selectedIndex = lastIndex;
170
+ } else if (nextIndex > lastIndex) {
171
+ this.selectedIndex = 0;
172
+ } else {
173
+ this.selectedIndex = nextIndex;
174
+ }
93
175
  }
176
+ this.tui.requestRender();
94
177
  }
95
178
  }
96
179
 
97
- export type ContextPreset = { label: string; value: number; description: string };
180
+ export interface ContextPreset {
181
+ description: string;
182
+ label: string;
183
+ value: number;
184
+ }
98
185
 
99
- export type AdvisorSettings = {
186
+ export interface AdvisorSettings {
187
+ autoLoopGate?: boolean;
188
+ blockOnBlocked?: boolean;
189
+ collapseResponses: boolean;
190
+ completionGate: boolean;
100
191
  contextMaxChars: number;
192
+ customRule?: string;
101
193
  effort?: string;
102
- planGate: boolean;
103
194
  failureGate: boolean;
104
- completionGate: boolean;
105
- collapseResponses: boolean;
106
- customRule?: string;
107
- blockOnBlocked?: boolean;
108
- autoLoopGate?: boolean;
195
+ failureMode?: "block-session" | "block-tool" | "warn-and-continue";
196
+ herdrIntegration?: boolean;
109
197
  loopThreshold?: number;
110
198
  maxCallsPerSession?: number;
199
+ planGate: boolean;
111
200
  sessionSummary?: boolean;
112
- };
201
+ toolResultMaxBytes?: number;
202
+ toolResultMaxLines?: number;
203
+ }
113
204
 
114
205
  export class AdvisorSettingsSelector implements Component, Focusable {
115
- private selectedRow = 0;
206
+ private selectedRow: number;
116
207
  private contextIndex: number;
117
208
  private effortIndex: number;
118
- private settings: AdvisorSettings;
119
- private customInput = new Input();
120
- private editingCustom = false;
209
+ private readonly settings: AdvisorSettings;
210
+ private readonly customInput = new Input();
211
+ private editingCustom: boolean;
121
212
  private _focused = false;
213
+ private readonly options: AdvisorSettingsSelectorOptions;
122
214
 
123
- public get focused() { return this._focused; }
124
- public set focused(value: boolean) {
215
+ get focused(): boolean {
216
+ return this._focused;
217
+ }
218
+ set focused(value: boolean) {
125
219
  this._focused = value;
126
220
  this.customInput.focused = value && this.editingCustom;
127
221
  }
128
222
 
129
- constructor(private options: {
130
- tui: any;
131
- theme: Theme;
132
- presets: ContextPreset[];
133
- effortLevels: string[];
134
- initial: AdvisorSettings;
135
- onSave: (settings: AdvisorSettings) => void;
136
- onCancel: () => void;
137
- }) {
223
+ constructor(options: AdvisorSettingsSelectorOptions) {
224
+ this.selectedRow = 0;
225
+ this.editingCustom = false;
226
+ this.options = options;
138
227
  this.settings = { ...options.initial };
139
- this.contextIndex = Math.max(0, options.presets.findIndex((preset) => preset.value === this.settings.contextMaxChars));
140
- this.effortIndex = Math.max(0, options.effortLevels.indexOf(this.settings.effort || "Default (Model Default)"));
228
+ this.contextIndex = Math.max(
229
+ 0,
230
+ options.presets.findIndex(
231
+ (preset) => preset.value === this.settings.contextMaxChars
232
+ )
233
+ );
234
+ this.effortIndex = Math.max(
235
+ 0,
236
+ options.effortLevels.indexOf(
237
+ this.settings.effort || "Default (Model Default)"
238
+ )
239
+ );
141
240
  this.customInput.onSubmit = (value) => {
142
241
  this.settings.customRule = value.trim() || undefined;
143
242
  this.editingCustom = false;
@@ -151,46 +250,124 @@ export class AdvisorSettingsSelector implements Component, Focusable {
151
250
  };
152
251
  }
153
252
 
154
- invalidate(): void { this.options.tui.requestRender(); }
253
+ invalidate(): void {
254
+ this.options.tui.requestRender();
255
+ }
155
256
 
156
- private currentContext() { return this.options.presets[this.contextIndex]; }
257
+ private currentContext(): ContextPreset {
258
+ const preset = this.options.presets.find(
259
+ (item) => item.value === this.settings.contextMaxChars
260
+ );
261
+ return (
262
+ preset ?? {
263
+ description: "Current custom context limit",
264
+ label: String(this.settings.contextMaxChars),
265
+ value: this.settings.contextMaxChars,
266
+ }
267
+ );
268
+ }
269
+ private currentEffort() {
270
+ return this.settings.effort || "Default (Model Default)";
271
+ }
157
272
  private row(label: string, value: string, index: number) {
158
273
  const { theme } = this.options;
159
274
  const prefix = index === this.selectedRow ? theme.fg("accent", "›") : " ";
160
275
  const text = `${prefix} ${label.padEnd(28)} ${value}`;
161
- return index === this.selectedRow ? theme.fg("accent", theme.bold(text)) : theme.fg("text", text);
276
+ return index === this.selectedRow
277
+ ? theme.fg("accent", theme.bold(text))
278
+ : theme.fg("text", text);
162
279
  }
163
280
 
164
281
  render(width: number): string[] {
165
282
  const { theme, presets } = this.options;
166
283
  const trackWidth = Math.max(24, Math.min(60, width - 4));
167
- const positions = presets.map((_, index) => Math.round(index * (trackWidth - 1) / (presets.length - 1)));
284
+ const positions = presets.map((_, index) =>
285
+ Math.round((index * (trackWidth - 1)) / Math.max(1, presets.length - 1))
286
+ );
168
287
  const track = Array.from({ length: trackWidth }, () => "─");
169
288
  track[positions[this.contextIndex]] = "▲";
170
289
  const labels = Array.from({ length: trackWidth }, () => " ");
171
- for (let index = 0; index < presets.length; index++) {
172
- const label = presets[index].label;
173
- const start = Math.max(0, Math.min(trackWidth - label.length, positions[index] - Math.floor(label.length / 2)));
174
- for (let char = 0; char < label.length; char++) labels[start + char] = label[char];
290
+ for (let index = 0; index < presets.length; index += 1) {
291
+ const { label } = presets[index];
292
+ const start = Math.max(
293
+ 0,
294
+ Math.min(
295
+ trackWidth - label.length,
296
+ positions[index] - Math.floor(label.length / 2)
297
+ )
298
+ );
299
+ for (let char = 0; char < label.length; char += 1) {
300
+ labels[start + char] = label[char];
301
+ }
175
302
  }
176
303
  const heading = `Recent history${" ".repeat(Math.max(1, trackWidth - "Recent history".length - "Full branch".length))}Full branch`;
177
- const onOff = (value: boolean) => value ? "On" : "Off";
304
+ const onOff = (value: boolean) => (value ? "On" : "Off");
178
305
  const rows = [
179
306
  this.row("Context window", this.currentContext().label, 0),
180
- this.row("Advisor reasoning", this.options.effortLevels[this.effortIndex], 1),
307
+ this.row("Advisor reasoning", this.currentEffort(), 1),
181
308
  this.row("Plan gate", onOff(this.settings.planGate), 2),
182
309
  this.row("Failure gate", onOff(this.settings.failureGate), 3),
183
310
  this.row("Completion gate", onOff(this.settings.completionGate), 4),
184
- this.row("Collapse long responses", onOff(this.settings.collapseResponses), 5),
311
+ this.row(
312
+ "Collapse long responses",
313
+ onOff(this.settings.collapseResponses),
314
+ 5
315
+ ),
185
316
  this.row("Custom invocation", this.settings.customRule || "None", 6),
186
- this.row("Block on critical advice", onOff(this.settings.blockOnBlocked ?? true), 7),
187
- this.row("Automatic loop gate", onOff(this.settings.autoLoopGate ?? true), 8),
188
- this.row("Loop threshold", `After ${this.settings.loopThreshold ?? 3} repeats`, 9),
189
- this.row("Max Advisor calls/session", this.settings.maxCallsPerSession === undefined ? "∞" : String(this.settings.maxCallsPerSession), 10),
190
- this.row("Session Advisor Summary", onOff(this.settings.sessionSummary ?? true), 11),
317
+ this.row(
318
+ "Block on critical advice",
319
+ onOff(this.settings.blockOnBlocked ?? true),
320
+ 7
321
+ ),
322
+ this.row(
323
+ "Automatic loop gate",
324
+ onOff(this.settings.autoLoopGate ?? true),
325
+ 8
326
+ ),
327
+ this.row(
328
+ "Loop threshold",
329
+ `After ${this.settings.loopThreshold ?? 3} repeats`,
330
+ 9
331
+ ),
332
+ this.row(
333
+ "Max Advisor calls/session",
334
+ this.settings.maxCallsPerSession === undefined
335
+ ? "∞"
336
+ : String(this.settings.maxCallsPerSession),
337
+ 10
338
+ ),
339
+ this.row(
340
+ "Session Advisor Summary",
341
+ onOff(this.settings.sessionSummary ?? true),
342
+ 11
343
+ ),
344
+ this.row(
345
+ "Gate failure mode",
346
+ this.settings.failureMode ?? "block-session",
347
+ 12
348
+ ),
349
+ this.row(
350
+ "Herdr integration",
351
+ onOff(this.settings.herdrIntegration ?? true),
352
+ 13
353
+ ),
354
+ this.row(
355
+ "Tool result lines",
356
+ String(this.settings.toolResultMaxLines ?? 2000),
357
+ 14
358
+ ),
359
+ this.row(
360
+ "Tool result bytes",
361
+ String(this.settings.toolResultMaxBytes ?? 50 * 1024),
362
+ 15
363
+ ),
191
364
  ];
192
- if (this.editingCustom) rows.push(` ${this.customInput.render(Math.max(10, width - 6))[0] || ""}`);
193
- rows.push(this.row("Save changes", "", 12));
365
+ if (this.editingCustom) {
366
+ rows.push(
367
+ ` ${this.customInput.render(Math.max(10, width - 6))[0] || ""}`
368
+ );
369
+ }
370
+ rows.push(this.row("Save changes", "", 16));
194
371
  return [
195
372
  theme.fg("accent", theme.bold(" Advisor settings")),
196
373
  "",
@@ -213,7 +390,7 @@ export class AdvisorSettingsSelector implements Component, Focusable {
213
390
  if (matchesKey(keyData, Key.up)) {
214
391
  this.selectedRow = Math.max(0, this.selectedRow - 1);
215
392
  } else if (matchesKey(keyData, Key.down)) {
216
- this.selectedRow = Math.min(12, this.selectedRow + 1);
393
+ this.selectedRow = Math.min(16, this.selectedRow + 1);
217
394
  } else if (matchesKey(keyData, Key.left)) {
218
395
  this.adjust(-1);
219
396
  } else if (matchesKey(keyData, Key.right)) {
@@ -226,32 +403,125 @@ export class AdvisorSettingsSelector implements Component, Focusable {
226
403
  tui.requestRender();
227
404
  return;
228
405
  }
229
- if (this.selectedRow === 12) return this.options.onSave({ ...this.settings, contextMaxChars: this.currentContext().value, effort: this.options.effortLevels[this.effortIndex] });
406
+ if (this.selectedRow === 16) {
407
+ this.options.onSave({
408
+ ...this.settings,
409
+ contextMaxChars: this.currentContext().value,
410
+ effort: this.currentEffort(),
411
+ });
412
+ return;
413
+ }
230
414
  this.adjust(1);
231
415
  } else if (matchesKey(keyData, Key.escape)) {
232
- return this.options.onCancel();
233
- } else return;
416
+ this.options.onCancel();
417
+ return;
418
+ } else {
419
+ return;
420
+ }
234
421
  tui.requestRender();
235
422
  }
236
423
 
237
424
  private adjust(direction: number) {
238
425
  switch (this.selectedRow) {
239
- case 0: this.contextIndex = Math.max(0, Math.min(this.options.presets.length - 1, this.contextIndex + direction)); break;
240
- case 1: this.effortIndex = Math.max(0, Math.min(this.options.effortLevels.length - 1, this.effortIndex + direction)); break;
241
- case 2: this.settings.planGate = !this.settings.planGate; break;
242
- case 3: this.settings.failureGate = !this.settings.failureGate; break;
243
- case 4: this.settings.completionGate = !this.settings.completionGate; break;
244
- case 5: this.settings.collapseResponses = !this.settings.collapseResponses; break;
245
- case 7: this.settings.blockOnBlocked = !(this.settings.blockOnBlocked ?? true); break;
246
- case 8: this.settings.autoLoopGate = !(this.settings.autoLoopGate ?? true); break;
247
- case 9: this.settings.loopThreshold = Math.max(2, (this.settings.loopThreshold ?? 3) + direction); break;
426
+ case 0:
427
+ this.contextIndex = Math.max(
428
+ 0,
429
+ Math.min(
430
+ this.options.presets.length - 1,
431
+ this.contextIndex + direction
432
+ )
433
+ );
434
+ this.settings.contextMaxChars =
435
+ this.options.presets[this.contextIndex].value;
436
+ break;
437
+ case 1:
438
+ this.effortIndex = Math.max(
439
+ 0,
440
+ Math.min(
441
+ this.options.effortLevels.length - 1,
442
+ this.effortIndex + direction
443
+ )
444
+ );
445
+ this.settings.effort = this.options.effortLevels[this.effortIndex];
446
+ break;
447
+ case 2:
448
+ this.settings.planGate = !this.settings.planGate;
449
+ break;
450
+ case 3:
451
+ this.settings.failureGate = !this.settings.failureGate;
452
+ break;
453
+ case 4:
454
+ this.settings.completionGate = !this.settings.completionGate;
455
+ break;
456
+ case 5:
457
+ this.settings.collapseResponses = !this.settings.collapseResponses;
458
+ break;
459
+ case 7:
460
+ this.settings.blockOnBlocked = !(this.settings.blockOnBlocked ?? true);
461
+ break;
462
+ case 8:
463
+ this.settings.autoLoopGate = !(this.settings.autoLoopGate ?? true);
464
+ break;
465
+ case 9:
466
+ this.settings.loopThreshold = Math.max(
467
+ 2,
468
+ (this.settings.loopThreshold ?? 3) + direction
469
+ );
470
+ break;
248
471
  case 10: {
249
472
  const values = [undefined, 0, 1, 2, 3, 5, 10, 25, 50];
250
- const index = Math.max(0, values.indexOf(this.settings.maxCallsPerSession));
251
- this.settings.maxCallsPerSession = values[Math.max(0, Math.min(values.length - 1, index + direction))];
473
+ const index = Math.max(
474
+ 0,
475
+ values.indexOf(this.settings.maxCallsPerSession)
476
+ );
477
+ this.settings.maxCallsPerSession =
478
+ values[Math.max(0, Math.min(values.length - 1, index + direction))];
252
479
  break;
253
480
  }
254
- case 11: this.settings.sessionSummary = !(this.settings.sessionSummary ?? true); break;
481
+ case 11:
482
+ this.settings.sessionSummary = !(this.settings.sessionSummary ?? true);
483
+ break;
484
+ case 12: {
485
+ const modes: AdvisorSettings["failureMode"][] = [
486
+ "block-session",
487
+ "block-tool",
488
+ "warn-and-continue",
489
+ ];
490
+ const index = Math.max(
491
+ 0,
492
+ modes.indexOf(this.settings.failureMode ?? "block-session")
493
+ );
494
+ this.settings.failureMode =
495
+ modes[Math.max(0, Math.min(modes.length - 1, index + direction))];
496
+ break;
497
+ }
498
+ case 13:
499
+ this.settings.herdrIntegration = !(
500
+ this.settings.herdrIntegration ?? true
501
+ );
502
+ break;
503
+ case 14: {
504
+ const values = [0, 500, 1000, 2000, 5000, 10_000];
505
+ const index = Math.max(
506
+ 0,
507
+ values.indexOf(this.settings.toolResultMaxLines ?? 2000)
508
+ );
509
+ this.settings.toolResultMaxLines =
510
+ values[Math.max(0, Math.min(values.length - 1, index + direction))];
511
+ break;
512
+ }
513
+ case 15: {
514
+ const values = [0, 10 * 1024, 50 * 1024, 100 * 1024, 500 * 1024];
515
+ const index = Math.max(
516
+ 0,
517
+ values.indexOf(this.settings.toolResultMaxBytes ?? 50 * 1024)
518
+ );
519
+ this.settings.toolResultMaxBytes =
520
+ values[Math.max(0, Math.min(values.length - 1, index + direction))];
521
+ break;
522
+ }
523
+ default:
524
+ break;
255
525
  }
256
526
  }
257
527
  }