pi-advisor-flow 0.2.3 → 0.2.6

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