pi-advisor-flow 0.2.2 → 0.2.4

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
@@ -10,6 +10,7 @@ import {
10
10
  matchesKey,
11
11
  truncateToWidth,
12
12
  } from "@earendil-works/pi-tui";
13
+ import { isValidAdvisorToolPolicies } from "./config.js";
13
14
 
14
15
  interface RenderRequester {
15
16
  requestRender: () => void;
@@ -184,6 +185,7 @@ export interface ContextPreset {
184
185
  }
185
186
 
186
187
  export interface AdvisorSettings {
188
+ alwaysOn?: boolean;
187
189
  autoLoopGate?: boolean;
188
190
  blockOnBlocked?: boolean;
189
191
  collapseResponses: boolean;
@@ -197,18 +199,82 @@ export interface AdvisorSettings {
197
199
  loopThreshold?: number;
198
200
  maxCallsPerSession?: number;
199
201
  planGate: boolean;
202
+ redactSecrets?: boolean;
200
203
  sessionSummary?: boolean;
204
+ simpleMode?: boolean;
205
+ toolPolicies?: Record<string, "full" | "summary" | "exclude">;
201
206
  toolResultMaxBytes?: number;
202
207
  toolResultMaxLines?: number;
203
208
  }
204
209
 
210
+ type AdvisorSettingsRow =
211
+ | "simpleMode"
212
+ | "alwaysOn"
213
+ | "context"
214
+ | "effort"
215
+ | "planGate"
216
+ | "failureGate"
217
+ | "completionGate"
218
+ | "collapseResponses"
219
+ | "customRule"
220
+ | "blockOnBlocked"
221
+ | "autoLoopGate"
222
+ | "loopThreshold"
223
+ | "maxCallsPerSession"
224
+ | "sessionSummary"
225
+ | "failureMode"
226
+ | "herdrIntegration"
227
+ | "toolResultMaxLines"
228
+ | "toolResultMaxBytes"
229
+ | "redactSecrets"
230
+ | "toolPolicies"
231
+ | "save";
232
+
233
+ const ADVANCED_ROWS: AdvisorSettingsRow[] = [
234
+ "context",
235
+ "effort",
236
+ "planGate",
237
+ "failureGate",
238
+ "completionGate",
239
+ "collapseResponses",
240
+ "customRule",
241
+ "blockOnBlocked",
242
+ "autoLoopGate",
243
+ "loopThreshold",
244
+ "maxCallsPerSession",
245
+ "sessionSummary",
246
+ "failureMode",
247
+ "herdrIntegration",
248
+ "toolResultMaxLines",
249
+ "toolResultMaxBytes",
250
+ "redactSecrets",
251
+ "toolPolicies",
252
+ ];
253
+
254
+ const SIMPLE_MODE_GRADIENT_INTERVAL_MS = 100;
255
+ // #763FCD and nearby lighter/darker purple steps; white is the moving shine.
256
+ const SIMPLE_MODE_GRADIENT_COLORS = [
257
+ [125, 79, 205],
258
+ [143, 96, 218],
259
+ [160, 114, 230],
260
+ [178, 135, 238],
261
+ [195, 157, 245],
262
+ [168, 120, 230],
263
+ [143, 89, 215],
264
+ ] as const;
265
+
205
266
  export class AdvisorSettingsSelector implements Component, Focusable {
206
267
  private selectedRow: number;
207
268
  private contextIndex: number;
208
269
  private effortIndex: number;
209
270
  private readonly settings: AdvisorSettings;
210
271
  private readonly customInput = new Input();
272
+ private readonly policiesInput = new Input();
211
273
  private editingCustom: boolean;
274
+ private editingPolicies: boolean;
275
+ private policiesError: string | undefined;
276
+ private simpleModeGradientStartedAt: number | undefined;
277
+ private simpleModeGradientTimer: ReturnType<typeof setInterval> | undefined;
212
278
  private _focused = false;
213
279
  private readonly options: AdvisorSettingsSelectorOptions;
214
280
 
@@ -218,12 +284,16 @@ export class AdvisorSettingsSelector implements Component, Focusable {
218
284
  set focused(value: boolean) {
219
285
  this._focused = value;
220
286
  this.customInput.focused = value && this.editingCustom;
287
+ this.policiesInput.focused = value && this.editingPolicies;
221
288
  }
222
289
 
223
290
  constructor(options: AdvisorSettingsSelectorOptions) {
224
291
  this.selectedRow = 0;
225
292
  this.editingCustom = false;
293
+ this.editingPolicies = false;
294
+ this.policiesError = undefined;
226
295
  this.options = options;
296
+ // Retain every advanced value even when Simple mode hides its controls.
227
297
  this.settings = { ...options.initial };
228
298
  this.contextIndex = Math.max(
229
299
  0,
@@ -237,6 +307,9 @@ export class AdvisorSettingsSelector implements Component, Focusable {
237
307
  this.settings.effort || "Default (Model Default)"
238
308
  )
239
309
  );
310
+ if (this.settings.simpleMode) {
311
+ this.startSimpleModeGradient();
312
+ }
240
313
  this.customInput.onSubmit = (value) => {
241
314
  this.settings.customRule = value.trim() || undefined;
242
315
  this.editingCustom = false;
@@ -248,12 +321,57 @@ export class AdvisorSettingsSelector implements Component, Focusable {
248
321
  this.customInput.focused = false;
249
322
  this.options.tui.requestRender();
250
323
  };
324
+ this.policiesInput.onSubmit = (value) => {
325
+ let parsed: unknown;
326
+ try {
327
+ parsed = JSON.parse(value || "{}");
328
+ } catch {
329
+ this.policiesError = "Enter a valid JSON object.";
330
+ this.options.tui.requestRender();
331
+ return;
332
+ }
333
+ if (!isValidAdvisorToolPolicies(parsed)) {
334
+ this.policiesError =
335
+ "Use non-empty tool names with full, summary, or exclude values.";
336
+ this.options.tui.requestRender();
337
+ return;
338
+ }
339
+ this.settings.toolPolicies = parsed;
340
+ this.policiesError = undefined;
341
+ this.editingPolicies = false;
342
+ this.policiesInput.focused = false;
343
+ this.options.tui.requestRender();
344
+ };
345
+ this.policiesInput.onEscape = () => {
346
+ this.editingPolicies = false;
347
+ this.policiesInput.focused = false;
348
+ this.options.tui.requestRender();
349
+ };
251
350
  }
252
351
 
253
352
  invalidate(): void {
254
353
  this.options.tui.requestRender();
255
354
  }
256
355
 
356
+ /** Called by TUI teardown when it supports component disposal. */
357
+ dispose(): void {
358
+ this.stopSimpleModeGradient();
359
+ }
360
+
361
+ private visibleRows(): AdvisorSettingsRow[] {
362
+ return this.settings.simpleMode
363
+ ? ["context", "simpleMode", "alwaysOn", "save"]
364
+ : ["simpleMode", "alwaysOn", ...ADVANCED_ROWS, "save"];
365
+ }
366
+
367
+ private selectedRowId(): AdvisorSettingsRow {
368
+ return this.visibleRows()[this.selectedRow] ?? "simpleMode";
369
+ }
370
+
371
+ private focusRow(rowId: AdvisorSettingsRow): void {
372
+ this.selectedRow = Math.max(0, this.visibleRows().indexOf(rowId));
373
+ }
374
+
257
375
  private currentContext(): ContextPreset {
258
376
  const preset = this.options.presets.find(
259
377
  (item) => item.value === this.settings.contextMaxChars
@@ -266,100 +384,167 @@ export class AdvisorSettingsSelector implements Component, Focusable {
266
384
  }
267
385
  );
268
386
  }
387
+
269
388
  private currentEffort() {
270
389
  return this.settings.effort || "Default (Model Default)";
271
390
  }
272
- private row(label: string, value: string, index: number) {
391
+
392
+ private row(label: string, value: string, rowId: AdvisorSettingsRow): string {
273
393
  const { theme } = this.options;
274
- const prefix = index === this.selectedRow ? theme.fg("accent", "›") : " ";
394
+ const selected = rowId === this.selectedRowId();
395
+ const prefix = selected ? theme.fg("accent", "›") : " ";
275
396
  const text = `${prefix} ${label.padEnd(28)} ${value}`;
276
- return index === this.selectedRow
397
+ if (
398
+ rowId === "simpleMode" &&
399
+ this.settings.simpleMode &&
400
+ this.simpleModeGradientStartedAt !== undefined
401
+ ) {
402
+ return `${prefix} ${this.rainbowGradient(label)}${" ".repeat(Math.max(0, 28 - label.length))} ${value}`;
403
+ }
404
+ return selected
277
405
  ? theme.fg("accent", theme.bold(text))
278
406
  : theme.fg("text", text);
279
407
  }
280
408
 
281
- render(width: number): string[] {
282
- const { theme, presets } = this.options;
283
- const trackWidth = Math.max(24, Math.min(60, width - 4));
284
- const positions = presets.map((_, index) =>
285
- Math.round((index * (trackWidth - 1)) / Math.max(1, presets.length - 1))
409
+ private rainbowGradient(text: string): string {
410
+ const frame = Math.floor(
411
+ (Date.now() - (this.simpleModeGradientStartedAt ?? 0)) /
412
+ SIMPLE_MODE_GRADIENT_INTERVAL_MS
286
413
  );
287
- const track = Array.from({ length: trackWidth }, () => "─");
288
- track[positions[this.contextIndex]] = "▲";
289
- const labels = Array.from({ length: trackWidth }, () => " ");
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
- }
414
+ const shinePosition = frame % (text.length * 2);
415
+ return [...text]
416
+ .map((character, index) => {
417
+ const [baseRed, baseGreen, baseBlue] =
418
+ SIMPLE_MODE_GRADIENT_COLORS[
419
+ index % SIMPLE_MODE_GRADIENT_COLORS.length
420
+ ];
421
+ const distance = Math.abs(index - shinePosition);
422
+ let brightness = 0;
423
+ if (distance === 0) {
424
+ brightness = 0.7;
425
+ } else if (distance === 1) {
426
+ brightness = 0.35;
427
+ }
428
+ const red = Math.round(baseRed + (255 - baseRed) * brightness);
429
+ const green = Math.round(baseGreen + (255 - baseGreen) * brightness);
430
+ const blue = Math.round(baseBlue + (255 - baseBlue) * brightness);
431
+ return `\x1b[38;2;${red};${green};${blue}m${character}`;
432
+ })
433
+ .join("")
434
+ .concat("\x1b[0m");
435
+ }
436
+
437
+ private startSimpleModeGradient(): void {
438
+ this.stopSimpleModeGradient();
439
+ this.simpleModeGradientStartedAt = Date.now();
440
+ this.simpleModeGradientTimer = setInterval(() => {
441
+ this.options.tui.requestRender();
442
+ }, SIMPLE_MODE_GRADIENT_INTERVAL_MS);
443
+ this.simpleModeGradientTimer.unref?.();
444
+ }
445
+
446
+ private stopSimpleModeGradient(): void {
447
+ if (this.simpleModeGradientTimer) {
448
+ clearInterval(this.simpleModeGradientTimer);
449
+ this.simpleModeGradientTimer = undefined;
302
450
  }
303
- const heading = `Recent history${" ".repeat(Math.max(1, trackWidth - "Recent history".length - "Full branch".length))}Full branch`;
451
+ this.simpleModeGradientStartedAt = undefined;
452
+ }
453
+
454
+ private save(): void {
455
+ this.stopSimpleModeGradient();
456
+ this.options.onSave({
457
+ ...this.settings,
458
+ contextMaxChars: this.currentContext().value,
459
+ effort: this.currentEffort(),
460
+ });
461
+ }
462
+
463
+ private cancel(): void {
464
+ this.stopSimpleModeGradient();
465
+ this.options.onCancel();
466
+ }
467
+
468
+ private advancedRows(width: number): string[] {
304
469
  const onOff = (value: boolean) => (value ? "On" : "Off");
305
470
  const rows = [
306
- this.row("Context window", this.currentContext().label, 0),
307
- this.row("Advisor reasoning", this.currentEffort(), 1),
308
- this.row("Plan gate", onOff(this.settings.planGate), 2),
309
- this.row("Failure gate", onOff(this.settings.failureGate), 3),
310
- this.row("Completion gate", onOff(this.settings.completionGate), 4),
471
+ this.row("Context window", this.currentContext().label, "context"),
472
+ this.row("Advisor reasoning", this.currentEffort(), "effort"),
473
+ this.row("Plan gate", onOff(this.settings.planGate), "planGate"),
474
+ this.row("Failure gate", onOff(this.settings.failureGate), "failureGate"),
475
+ this.row(
476
+ "Completion gate",
477
+ onOff(this.settings.completionGate),
478
+ "completionGate"
479
+ ),
311
480
  this.row(
312
481
  "Collapse long responses",
313
482
  onOff(this.settings.collapseResponses),
314
- 5
483
+ "collapseResponses"
484
+ ),
485
+ this.row(
486
+ "Custom invocation",
487
+ this.settings.customRule || "None",
488
+ "customRule"
315
489
  ),
316
- this.row("Custom invocation", this.settings.customRule || "None", 6),
317
490
  this.row(
318
491
  "Block on critical advice",
319
492
  onOff(this.settings.blockOnBlocked ?? true),
320
- 7
493
+ "blockOnBlocked"
321
494
  ),
322
495
  this.row(
323
496
  "Automatic loop gate",
324
497
  onOff(this.settings.autoLoopGate ?? true),
325
- 8
498
+ "autoLoopGate"
326
499
  ),
327
500
  this.row(
328
501
  "Loop threshold",
329
502
  `After ${this.settings.loopThreshold ?? 3} repeats`,
330
- 9
503
+ "loopThreshold"
331
504
  ),
332
505
  this.row(
333
506
  "Max Advisor calls/session",
334
507
  this.settings.maxCallsPerSession === undefined
335
508
  ? "∞"
336
509
  : String(this.settings.maxCallsPerSession),
337
- 10
510
+ "maxCallsPerSession"
338
511
  ),
339
512
  this.row(
340
513
  "Session Advisor Summary",
341
- onOff(this.settings.sessionSummary ?? true),
342
- 11
514
+ onOff(this.settings.sessionSummary ?? false),
515
+ "sessionSummary"
343
516
  ),
344
517
  this.row(
345
518
  "Gate failure mode",
346
519
  this.settings.failureMode ?? "block-session",
347
- 12
520
+ "failureMode"
348
521
  ),
349
522
  this.row(
350
523
  "Herdr integration",
351
524
  onOff(this.settings.herdrIntegration ?? true),
352
- 13
525
+ "herdrIntegration"
353
526
  ),
354
527
  this.row(
355
528
  "Tool result lines",
356
529
  String(this.settings.toolResultMaxLines ?? 2000),
357
- 14
530
+ "toolResultMaxLines"
358
531
  ),
359
532
  this.row(
360
533
  "Tool result bytes",
361
534
  String(this.settings.toolResultMaxBytes ?? 50 * 1024),
362
- 15
535
+ "toolResultMaxBytes"
536
+ ),
537
+ this.row(
538
+ "Redact common secrets",
539
+ onOff(this.settings.redactSecrets ?? false),
540
+ "redactSecrets"
541
+ ),
542
+ this.row(
543
+ "Tool disclosure policies",
544
+ Object.keys(this.settings.toolPolicies ?? {}).length
545
+ ? "Exact names configured"
546
+ : "All tools: full",
547
+ "toolPolicies"
363
548
  ),
364
549
  ];
365
550
  if (this.editingCustom) {
@@ -367,18 +552,96 @@ export class AdvisorSettingsSelector implements Component, Focusable {
367
552
  ` ${this.customInput.render(Math.max(10, width - 6))[0] || ""}`
368
553
  );
369
554
  }
370
- rows.push(this.row("Save changes", "", 16));
371
- return [
372
- theme.fg("accent", theme.bold(" Advisor settings")),
373
- "",
374
- ` ${theme.fg("muted", heading)}`,
375
- ` ${theme.fg("muted", track.join(""))}`,
376
- ` ${theme.fg("text", labels.join(""))}`,
377
- "",
378
- ...rows.map((line) => ` ${line}`),
379
- "",
380
- ` ${theme.fg("muted", "↑/↓ select · ←/→ adjust · Enter edits or saves · Esc cancels")}`,
381
- ].map((line) => truncateToWidth(line, width));
555
+ if (this.editingPolicies) {
556
+ rows.push(
557
+ ` ${this.policiesInput.render(Math.max(10, width - 6))[0] || ""}`
558
+ );
559
+ if (this.policiesError) {
560
+ rows.push(` ${this.options.theme.fg("error", this.policiesError)}`);
561
+ }
562
+ }
563
+ rows.push(this.row("Save changes", "", "save"));
564
+ return rows;
565
+ }
566
+
567
+ render(width: number): string[] {
568
+ const { theme, presets } = this.options;
569
+ const simpleMode = this.settings.simpleMode ?? false;
570
+ const modeRows = [
571
+ this.row("Simple mode", simpleMode ? "On" : "Off", "simpleMode"),
572
+ this.row("Always on", this.settings.alwaysOn ? "On" : "Off", "alwaysOn"),
573
+ ];
574
+ const lines = [theme.fg("accent", theme.bold(" Advisor settings")), ""];
575
+ if (simpleMode) {
576
+ const trackWidth = Math.max(24, Math.min(60, width - 4));
577
+ const positions = presets.map((_, index) =>
578
+ Math.round((index * (trackWidth - 1)) / Math.max(1, presets.length - 1))
579
+ );
580
+ const track = Array.from({ length: trackWidth }, () => "─");
581
+ track[positions[this.contextIndex]] = "▲";
582
+ const labels = Array.from({ length: trackWidth }, () => " ");
583
+ for (let index = 0; index < presets.length; index += 1) {
584
+ const { label } = presets[index];
585
+ const start = Math.max(
586
+ 0,
587
+ Math.min(
588
+ trackWidth - label.length,
589
+ positions[index] - Math.floor(label.length / 2)
590
+ )
591
+ );
592
+ for (let char = 0; char < label.length; char += 1) {
593
+ labels[start + char] = label[char];
594
+ }
595
+ }
596
+ lines.push(
597
+ ` ${this.row("Context window", this.currentContext().label, "context")}`
598
+ );
599
+ lines.push(` ${theme.fg("muted", "Recent history")}`);
600
+ lines.push(` ${theme.fg("muted", track.join(""))}`);
601
+ lines.push(` ${theme.fg("text", labels.join(""))}`);
602
+ lines.push("");
603
+ lines.push(...modeRows.map((line) => ` ${line}`));
604
+ lines.push(` ${this.row("Save changes", "", "save")}`);
605
+ lines.push("");
606
+ lines.push(
607
+ ` ${theme.fg("muted", "↑/↓ select · ←/→ adjust · Enter saves · Esc cancels")}`
608
+ );
609
+ return lines.map((line) => truncateToWidth(line, width));
610
+ }
611
+
612
+ const trackWidth = Math.max(24, Math.min(60, width - 4));
613
+ const positions = presets.map((_, index) =>
614
+ Math.round((index * (trackWidth - 1)) / Math.max(1, presets.length - 1))
615
+ );
616
+ const track = Array.from({ length: trackWidth }, () => "─");
617
+ track[positions[this.contextIndex]] = "▲";
618
+ const labels = Array.from({ length: trackWidth }, () => " ");
619
+ for (let index = 0; index < presets.length; index += 1) {
620
+ const { label } = presets[index];
621
+ const start = Math.max(
622
+ 0,
623
+ Math.min(
624
+ trackWidth - label.length,
625
+ positions[index] - Math.floor(label.length / 2)
626
+ )
627
+ );
628
+ for (let char = 0; char < label.length; char += 1) {
629
+ labels[start + char] = label[char];
630
+ }
631
+ }
632
+ const heading = `Recent history${" ".repeat(Math.max(1, trackWidth - "Recent history".length - "Full branch".length))}Full branch`;
633
+ lines.push(` ${theme.fg("muted", heading)}`);
634
+ lines.push(` ${theme.fg("muted", track.join(""))}`);
635
+ lines.push(` ${theme.fg("text", labels.join(""))}`);
636
+ lines.push("");
637
+ lines.push(
638
+ ...[...modeRows, ...this.advancedRows(width)].map((line) => ` ${line}`)
639
+ );
640
+ lines.push("");
641
+ lines.push(
642
+ ` ${theme.fg("muted", "↑/↓ select · ←/→ adjust · Enter edits or saves · Esc cancels")}`
643
+ );
644
+ return lines.map((line) => truncateToWidth(line, width));
382
645
  }
383
646
 
384
647
  handleInput(keyData: string): void {
@@ -387,33 +650,47 @@ export class AdvisorSettingsSelector implements Component, Focusable {
387
650
  this.customInput.handleInput(keyData);
388
651
  return;
389
652
  }
653
+ if (this.editingPolicies) {
654
+ this.policiesInput.handleInput(keyData);
655
+ return;
656
+ }
390
657
  if (matchesKey(keyData, Key.up)) {
391
658
  this.selectedRow = Math.max(0, this.selectedRow - 1);
392
659
  } else if (matchesKey(keyData, Key.down)) {
393
- this.selectedRow = Math.min(16, this.selectedRow + 1);
660
+ this.selectedRow = Math.min(
661
+ this.visibleRows().length - 1,
662
+ this.selectedRow + 1
663
+ );
394
664
  } else if (matchesKey(keyData, Key.left)) {
395
665
  this.adjust(-1);
396
666
  } else if (matchesKey(keyData, Key.right)) {
397
667
  this.adjust(1);
398
668
  } else if (matchesKey(keyData, Key.enter)) {
399
- if (this.selectedRow === 6) {
669
+ const row = this.selectedRowId();
670
+ if (row === "customRule") {
400
671
  this.editingCustom = true;
401
672
  this.customInput.setValue(this.settings.customRule || "");
402
673
  this.customInput.focused = this.focused;
403
674
  tui.requestRender();
404
675
  return;
405
676
  }
406
- if (this.selectedRow === 16) {
407
- this.options.onSave({
408
- ...this.settings,
409
- contextMaxChars: this.currentContext().value,
410
- effort: this.currentEffort(),
411
- });
677
+ if (row === "toolPolicies") {
678
+ this.editingPolicies = true;
679
+ this.policiesError = undefined;
680
+ this.policiesInput.setValue(
681
+ JSON.stringify(this.settings.toolPolicies ?? {})
682
+ );
683
+ this.policiesInput.focused = this.focused;
684
+ tui.requestRender();
685
+ return;
686
+ }
687
+ if (row === "save") {
688
+ this.save();
412
689
  return;
413
690
  }
414
691
  this.adjust(1);
415
692
  } else if (matchesKey(keyData, Key.escape)) {
416
- this.options.onCancel();
693
+ this.cancel();
417
694
  return;
418
695
  } else {
419
696
  return;
@@ -421,9 +698,23 @@ export class AdvisorSettingsSelector implements Component, Focusable {
421
698
  tui.requestRender();
422
699
  }
423
700
 
424
- private adjust(direction: number) {
425
- switch (this.selectedRow) {
426
- case 0:
701
+ private adjust(direction: number): void {
702
+ switch (this.selectedRowId()) {
703
+ case "simpleMode":
704
+ this.settings.simpleMode = !(this.settings.simpleMode ?? false);
705
+ if (this.settings.simpleMode) {
706
+ this.startSimpleModeGradient();
707
+ } else {
708
+ this.stopSimpleModeGradient();
709
+ }
710
+ // The visible row set changes with the mode, so keep the cursor on the
711
+ // row the user is actually operating rather than on its old index.
712
+ this.focusRow("simpleMode");
713
+ break;
714
+ case "alwaysOn":
715
+ this.settings.alwaysOn = !(this.settings.alwaysOn ?? false);
716
+ break;
717
+ case "context":
427
718
  this.contextIndex = Math.max(
428
719
  0,
429
720
  Math.min(
@@ -434,7 +725,7 @@ export class AdvisorSettingsSelector implements Component, Focusable {
434
725
  this.settings.contextMaxChars =
435
726
  this.options.presets[this.contextIndex].value;
436
727
  break;
437
- case 1:
728
+ case "effort":
438
729
  this.effortIndex = Math.max(
439
730
  0,
440
731
  Math.min(
@@ -444,31 +735,31 @@ export class AdvisorSettingsSelector implements Component, Focusable {
444
735
  );
445
736
  this.settings.effort = this.options.effortLevels[this.effortIndex];
446
737
  break;
447
- case 2:
738
+ case "planGate":
448
739
  this.settings.planGate = !this.settings.planGate;
449
740
  break;
450
- case 3:
741
+ case "failureGate":
451
742
  this.settings.failureGate = !this.settings.failureGate;
452
743
  break;
453
- case 4:
744
+ case "completionGate":
454
745
  this.settings.completionGate = !this.settings.completionGate;
455
746
  break;
456
- case 5:
747
+ case "collapseResponses":
457
748
  this.settings.collapseResponses = !this.settings.collapseResponses;
458
749
  break;
459
- case 7:
750
+ case "blockOnBlocked":
460
751
  this.settings.blockOnBlocked = !(this.settings.blockOnBlocked ?? true);
461
752
  break;
462
- case 8:
753
+ case "autoLoopGate":
463
754
  this.settings.autoLoopGate = !(this.settings.autoLoopGate ?? true);
464
755
  break;
465
- case 9:
756
+ case "loopThreshold":
466
757
  this.settings.loopThreshold = Math.max(
467
758
  2,
468
759
  (this.settings.loopThreshold ?? 3) + direction
469
760
  );
470
761
  break;
471
- case 10: {
762
+ case "maxCallsPerSession": {
472
763
  const values = [undefined, 0, 1, 2, 3, 5, 10, 25, 50];
473
764
  const index = Math.max(
474
765
  0,
@@ -478,10 +769,10 @@ export class AdvisorSettingsSelector implements Component, Focusable {
478
769
  values[Math.max(0, Math.min(values.length - 1, index + direction))];
479
770
  break;
480
771
  }
481
- case 11:
482
- this.settings.sessionSummary = !(this.settings.sessionSummary ?? true);
772
+ case "sessionSummary":
773
+ this.settings.sessionSummary = !(this.settings.sessionSummary ?? false);
483
774
  break;
484
- case 12: {
775
+ case "failureMode": {
485
776
  const modes: AdvisorSettings["failureMode"][] = [
486
777
  "block-session",
487
778
  "block-tool",
@@ -495,12 +786,15 @@ export class AdvisorSettingsSelector implements Component, Focusable {
495
786
  modes[Math.max(0, Math.min(modes.length - 1, index + direction))];
496
787
  break;
497
788
  }
498
- case 13:
789
+ case "herdrIntegration":
499
790
  this.settings.herdrIntegration = !(
500
791
  this.settings.herdrIntegration ?? true
501
792
  );
502
793
  break;
503
- case 14: {
794
+ case "redactSecrets":
795
+ this.settings.redactSecrets = !(this.settings.redactSecrets ?? false);
796
+ break;
797
+ case "toolResultMaxLines": {
504
798
  const values = [0, 500, 1000, 2000, 5000, 10_000];
505
799
  const index = Math.max(
506
800
  0,
@@ -510,7 +804,7 @@ export class AdvisorSettingsSelector implements Component, Focusable {
510
804
  values[Math.max(0, Math.min(values.length - 1, index + direction))];
511
805
  break;
512
806
  }
513
- case 15: {
807
+ case "toolResultMaxBytes": {
514
808
  const values = [0, 10 * 1024, 50 * 1024, 100 * 1024, 500 * 1024];
515
809
  const index = Math.max(
516
810
  0,