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/README.md +116 -67
- package/package.json +1 -1
- package/src/commands.ts +111 -30
- package/src/config.ts +122 -4
- package/src/conversation.ts +102 -19
- package/src/tools.ts +71 -15
- package/src/ui.ts +374 -80
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
|
-
|
|
391
|
+
|
|
392
|
+
private row(label: string, value: string, rowId: AdvisorSettingsRow): string {
|
|
273
393
|
const { theme } = this.options;
|
|
274
|
-
const
|
|
394
|
+
const selected = rowId === this.selectedRowId();
|
|
395
|
+
const prefix = selected ? theme.fg("accent", "›") : " ";
|
|
275
396
|
const text = `${prefix} ${label.padEnd(28)} ${value}`;
|
|
276
|
-
|
|
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
|
-
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
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
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
Math.
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
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,
|
|
307
|
-
this.row("Advisor reasoning", this.currentEffort(),
|
|
308
|
-
this.row("Plan gate", onOff(this.settings.planGate),
|
|
309
|
-
this.row("Failure gate", onOff(this.settings.failureGate),
|
|
310
|
-
this.row(
|
|
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
|
-
|
|
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
|
-
|
|
493
|
+
"blockOnBlocked"
|
|
321
494
|
),
|
|
322
495
|
this.row(
|
|
323
496
|
"Automatic loop gate",
|
|
324
497
|
onOff(this.settings.autoLoopGate ?? true),
|
|
325
|
-
|
|
498
|
+
"autoLoopGate"
|
|
326
499
|
),
|
|
327
500
|
this.row(
|
|
328
501
|
"Loop threshold",
|
|
329
502
|
`After ${this.settings.loopThreshold ?? 3} repeats`,
|
|
330
|
-
|
|
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
|
-
|
|
510
|
+
"maxCallsPerSession"
|
|
338
511
|
),
|
|
339
512
|
this.row(
|
|
340
513
|
"Session Advisor Summary",
|
|
341
|
-
onOff(this.settings.sessionSummary ??
|
|
342
|
-
|
|
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
|
-
|
|
520
|
+
"failureMode"
|
|
348
521
|
),
|
|
349
522
|
this.row(
|
|
350
523
|
"Herdr integration",
|
|
351
524
|
onOff(this.settings.herdrIntegration ?? true),
|
|
352
|
-
|
|
525
|
+
"herdrIntegration"
|
|
353
526
|
),
|
|
354
527
|
this.row(
|
|
355
528
|
"Tool result lines",
|
|
356
529
|
String(this.settings.toolResultMaxLines ?? 2000),
|
|
357
|
-
|
|
530
|
+
"toolResultMaxLines"
|
|
358
531
|
),
|
|
359
532
|
this.row(
|
|
360
533
|
"Tool result bytes",
|
|
361
534
|
String(this.settings.toolResultMaxBytes ?? 50 * 1024),
|
|
362
|
-
|
|
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
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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(
|
|
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
|
-
|
|
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 (
|
|
407
|
-
this.
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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.
|
|
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.
|
|
426
|
-
case
|
|
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
|
|
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
|
|
738
|
+
case "planGate":
|
|
448
739
|
this.settings.planGate = !this.settings.planGate;
|
|
449
740
|
break;
|
|
450
|
-
case
|
|
741
|
+
case "failureGate":
|
|
451
742
|
this.settings.failureGate = !this.settings.failureGate;
|
|
452
743
|
break;
|
|
453
|
-
case
|
|
744
|
+
case "completionGate":
|
|
454
745
|
this.settings.completionGate = !this.settings.completionGate;
|
|
455
746
|
break;
|
|
456
|
-
case
|
|
747
|
+
case "collapseResponses":
|
|
457
748
|
this.settings.collapseResponses = !this.settings.collapseResponses;
|
|
458
749
|
break;
|
|
459
|
-
case
|
|
750
|
+
case "blockOnBlocked":
|
|
460
751
|
this.settings.blockOnBlocked = !(this.settings.blockOnBlocked ?? true);
|
|
461
752
|
break;
|
|
462
|
-
case
|
|
753
|
+
case "autoLoopGate":
|
|
463
754
|
this.settings.autoLoopGate = !(this.settings.autoLoopGate ?? true);
|
|
464
755
|
break;
|
|
465
|
-
case
|
|
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
|
|
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
|
|
482
|
-
this.settings.sessionSummary = !(this.settings.sessionSummary ??
|
|
772
|
+
case "sessionSummary":
|
|
773
|
+
this.settings.sessionSummary = !(this.settings.sessionSummary ?? false);
|
|
483
774
|
break;
|
|
484
|
-
case
|
|
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
|
|
789
|
+
case "herdrIntegration":
|
|
499
790
|
this.settings.herdrIntegration = !(
|
|
500
791
|
this.settings.herdrIntegration ?? true
|
|
501
792
|
);
|
|
502
793
|
break;
|
|
503
|
-
case
|
|
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
|
|
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,
|