pi-sage 0.2.6 → 0.2.7
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/.pi/extensions/sage/index.ts +143 -58
- package/package.json +1 -1
|
@@ -371,8 +371,14 @@ async function runSettingsWizard(ctx: ExtensionCommandContext): Promise<void> {
|
|
|
371
371
|
let draft = mergeSettings(loaded.settings);
|
|
372
372
|
let scope: SettingsScope = loaded.source === "global" ? "global" : "project";
|
|
373
373
|
|
|
374
|
+
const persistDraft = (): void => {
|
|
375
|
+
const target = getSettingsPathForScope(ctx.cwd, scope);
|
|
376
|
+
saveSettings(target, draft);
|
|
377
|
+
ctx.ui.notify(`Saved Sage settings to ${target}`, "info");
|
|
378
|
+
};
|
|
379
|
+
|
|
374
380
|
while (true) {
|
|
375
|
-
const action = await
|
|
381
|
+
const action = await selectScrollable(ctx, "Sage settings", [
|
|
376
382
|
`Enabled: ${onOff(draft.enabled)}`,
|
|
377
383
|
`Autonomous mode: ${onOff(draft.autonomousEnabled)}`,
|
|
378
384
|
`Explicit requests bypass soft limits: ${onOff(draft.explicitRequestAlwaysAllowed)}`,
|
|
@@ -393,20 +399,11 @@ async function runSettingsWizard(ctx: ExtensionCommandContext): Promise<void> {
|
|
|
393
399
|
`Sensitive denylist: ${(draft.toolPolicy.sensitivePathDenylist ?? []).join(",")}`,
|
|
394
400
|
`Cost cap/session: ${draft.maxEstimatedCostPerSession ?? "(none)"}`,
|
|
395
401
|
`Save scope: ${scope}`,
|
|
396
|
-
"Test Sage call"
|
|
397
|
-
"Save and exit",
|
|
398
|
-
"Exit without saving"
|
|
402
|
+
"Test Sage call"
|
|
399
403
|
]);
|
|
400
404
|
|
|
401
|
-
if (!action
|
|
402
|
-
ctx.ui.notify("Sage settings
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
if (action === "Save and exit") {
|
|
407
|
-
const target = getSettingsPathForScope(ctx.cwd, scope);
|
|
408
|
-
saveSettings(target, draft);
|
|
409
|
-
ctx.ui.notify(`Saved Sage settings to ${target}`, "info");
|
|
405
|
+
if (!action) {
|
|
406
|
+
ctx.ui.notify("Sage settings closed (changes are saved immediately)", "info");
|
|
410
407
|
return;
|
|
411
408
|
}
|
|
412
409
|
|
|
@@ -417,56 +414,88 @@ async function runSettingsWizard(ctx: ExtensionCommandContext): Promise<void> {
|
|
|
417
414
|
|
|
418
415
|
if (action.startsWith("Enabled:")) {
|
|
419
416
|
draft = { ...draft, enabled: !draft.enabled };
|
|
417
|
+
persistDraft();
|
|
420
418
|
continue;
|
|
421
419
|
}
|
|
422
420
|
if (action.startsWith("Autonomous mode:")) {
|
|
423
421
|
draft = { ...draft, autonomousEnabled: !draft.autonomousEnabled };
|
|
422
|
+
persistDraft();
|
|
424
423
|
continue;
|
|
425
424
|
}
|
|
426
425
|
if (action.startsWith("Explicit requests bypass soft limits:")) {
|
|
427
426
|
draft = { ...draft, explicitRequestAlwaysAllowed: !draft.explicitRequestAlwaysAllowed };
|
|
427
|
+
persistDraft();
|
|
428
428
|
continue;
|
|
429
429
|
}
|
|
430
430
|
if (action.startsWith("Model:")) {
|
|
431
431
|
const selected = await pickModel(ctx, draft.model);
|
|
432
|
-
if (selected)
|
|
432
|
+
if (selected) {
|
|
433
|
+
draft = { ...draft, model: selected };
|
|
434
|
+
persistDraft();
|
|
435
|
+
}
|
|
433
436
|
continue;
|
|
434
437
|
}
|
|
435
438
|
if (action.startsWith("Reasoning level:")) {
|
|
436
|
-
const selected = await ctx
|
|
439
|
+
const selected = await selectScrollable(ctx, "Reasoning level", [...REASONING_LEVELS]);
|
|
437
440
|
if (selected && REASONING_LEVELS.includes(selected as ReasoningLevel)) {
|
|
438
441
|
draft = { ...draft, reasoningLevel: selected as ReasoningLevel };
|
|
442
|
+
persistDraft();
|
|
439
443
|
}
|
|
440
444
|
continue;
|
|
441
445
|
}
|
|
442
446
|
if (action.startsWith("Timeout ms:")) {
|
|
443
|
-
|
|
447
|
+
const updated = await setNumberSetting(ctx, draft, "timeoutMs", "Timeout in milliseconds", 1000);
|
|
448
|
+
if (updated !== draft) {
|
|
449
|
+
draft = updated;
|
|
450
|
+
persistDraft();
|
|
451
|
+
}
|
|
444
452
|
continue;
|
|
445
453
|
}
|
|
446
454
|
if (action.startsWith("Max calls/turn:")) {
|
|
447
|
-
|
|
455
|
+
const updated = await setNumberSetting(ctx, draft, "maxCallsPerTurn", "Max Sage calls per turn", 1);
|
|
456
|
+
if (updated !== draft) {
|
|
457
|
+
draft = updated;
|
|
458
|
+
persistDraft();
|
|
459
|
+
}
|
|
448
460
|
continue;
|
|
449
461
|
}
|
|
450
462
|
if (action.startsWith("Max calls/session:")) {
|
|
451
|
-
|
|
463
|
+
const updated = await setNumberSetting(ctx, draft, "maxCallsPerSession", "Max Sage calls per session", 1);
|
|
464
|
+
if (updated !== draft) {
|
|
465
|
+
draft = updated;
|
|
466
|
+
persistDraft();
|
|
467
|
+
}
|
|
452
468
|
continue;
|
|
453
469
|
}
|
|
454
470
|
if (action.startsWith("Cooldown turns:")) {
|
|
455
|
-
|
|
471
|
+
const updated = await setNumberSetting(ctx, draft, "cooldownTurnsBetweenAutoCalls", "Cooldown turns", 0);
|
|
472
|
+
if (updated !== draft) {
|
|
473
|
+
draft = updated;
|
|
474
|
+
persistDraft();
|
|
475
|
+
}
|
|
456
476
|
continue;
|
|
457
477
|
}
|
|
458
478
|
if (action.startsWith("Max question chars:")) {
|
|
459
|
-
|
|
479
|
+
const updated = await setNumberSetting(ctx, draft, "maxQuestionChars", "Max question chars", 256);
|
|
480
|
+
if (updated !== draft) {
|
|
481
|
+
draft = updated;
|
|
482
|
+
persistDraft();
|
|
483
|
+
}
|
|
460
484
|
continue;
|
|
461
485
|
}
|
|
462
486
|
if (action.startsWith("Max context chars:")) {
|
|
463
|
-
|
|
487
|
+
const updated = await setNumberSetting(ctx, draft, "maxContextChars", "Max context chars", 512);
|
|
488
|
+
if (updated !== draft) {
|
|
489
|
+
draft = updated;
|
|
490
|
+
persistDraft();
|
|
491
|
+
}
|
|
464
492
|
continue;
|
|
465
493
|
}
|
|
466
494
|
if (action.startsWith("Tool profile:")) {
|
|
467
|
-
const selected = await ctx
|
|
495
|
+
const selected = await selectScrollable(ctx, "Tool profile", [...TOOL_PROFILES]);
|
|
468
496
|
if (selected && TOOL_PROFILES.includes(selected as ToolProfile)) {
|
|
469
497
|
draft = { ...draft, toolPolicy: { ...draft.toolPolicy, profile: selected as ToolProfile } };
|
|
498
|
+
persistDraft();
|
|
470
499
|
}
|
|
471
500
|
continue;
|
|
472
501
|
}
|
|
@@ -478,23 +507,40 @@ async function runSettingsWizard(ctx: ExtensionCommandContext): Promise<void> {
|
|
|
478
507
|
.map((item) => item.trim())
|
|
479
508
|
.filter(Boolean);
|
|
480
509
|
draft = { ...draft, toolPolicy: { ...draft.toolPolicy, customAllowedTools: tools } };
|
|
510
|
+
persistDraft();
|
|
481
511
|
}
|
|
482
512
|
continue;
|
|
483
513
|
}
|
|
484
514
|
if (action.startsWith("Max tool calls:")) {
|
|
485
|
-
|
|
515
|
+
const updated = await setToolPolicyNumberSetting(ctx, draft, "maxToolCalls", "Max tool calls", 1);
|
|
516
|
+
if (updated !== draft) {
|
|
517
|
+
draft = updated;
|
|
518
|
+
persistDraft();
|
|
519
|
+
}
|
|
486
520
|
continue;
|
|
487
521
|
}
|
|
488
522
|
if (action.startsWith("Max files read:")) {
|
|
489
|
-
|
|
523
|
+
const updated = await setToolPolicyNumberSetting(ctx, draft, "maxFilesRead", "Max files read", 1);
|
|
524
|
+
if (updated !== draft) {
|
|
525
|
+
draft = updated;
|
|
526
|
+
persistDraft();
|
|
527
|
+
}
|
|
490
528
|
continue;
|
|
491
529
|
}
|
|
492
530
|
if (action.startsWith("Max bytes/file:")) {
|
|
493
|
-
|
|
531
|
+
const updated = await setToolPolicyNumberSetting(ctx, draft, "maxBytesPerFile", "Max bytes per file", 1024);
|
|
532
|
+
if (updated !== draft) {
|
|
533
|
+
draft = updated;
|
|
534
|
+
persistDraft();
|
|
535
|
+
}
|
|
494
536
|
continue;
|
|
495
537
|
}
|
|
496
538
|
if (action.startsWith("Max total bytes:")) {
|
|
497
|
-
|
|
539
|
+
const updated = await setToolPolicyNumberSetting(ctx, draft, "maxTotalBytesRead", "Max total bytes", 1024);
|
|
540
|
+
if (updated !== draft) {
|
|
541
|
+
draft = updated;
|
|
542
|
+
persistDraft();
|
|
543
|
+
}
|
|
498
544
|
continue;
|
|
499
545
|
}
|
|
500
546
|
if (action.startsWith("Sensitive denylist:")) {
|
|
@@ -508,6 +554,7 @@ async function runSettingsWizard(ctx: ExtensionCommandContext): Promise<void> {
|
|
|
508
554
|
.map((item) => item.trim())
|
|
509
555
|
.filter(Boolean);
|
|
510
556
|
draft = { ...draft, toolPolicy: { ...draft.toolPolicy, sensitivePathDenylist: denylist } };
|
|
557
|
+
persistDraft();
|
|
511
558
|
}
|
|
512
559
|
continue;
|
|
513
560
|
}
|
|
@@ -519,10 +566,12 @@ async function runSettingsWizard(ctx: ExtensionCommandContext): Promise<void> {
|
|
|
519
566
|
const copy = { ...draft };
|
|
520
567
|
delete copy.maxEstimatedCostPerSession;
|
|
521
568
|
draft = copy;
|
|
569
|
+
persistDraft();
|
|
522
570
|
} else {
|
|
523
571
|
const parsed = Number(trimmed);
|
|
524
572
|
if (Number.isFinite(parsed) && parsed >= 0) {
|
|
525
573
|
draft = { ...draft, maxEstimatedCostPerSession: parsed };
|
|
574
|
+
persistDraft();
|
|
526
575
|
} else {
|
|
527
576
|
ctx.ui.notify("Invalid cost cap value", "warning");
|
|
528
577
|
}
|
|
@@ -530,8 +579,11 @@ async function runSettingsWizard(ctx: ExtensionCommandContext): Promise<void> {
|
|
|
530
579
|
continue;
|
|
531
580
|
}
|
|
532
581
|
if (action.startsWith("Save scope:")) {
|
|
533
|
-
const selected = await ctx
|
|
534
|
-
if (selected === "project" || selected === "global")
|
|
582
|
+
const selected = await selectScrollable(ctx, "Save scope", ["project", "global"]);
|
|
583
|
+
if (selected === "project" || selected === "global") {
|
|
584
|
+
scope = selected;
|
|
585
|
+
persistDraft();
|
|
586
|
+
}
|
|
535
587
|
continue;
|
|
536
588
|
}
|
|
537
589
|
}
|
|
@@ -556,7 +608,7 @@ async function pickModel(ctx: ExtensionContext, currentModel: string): Promise<s
|
|
|
556
608
|
})
|
|
557
609
|
];
|
|
558
610
|
|
|
559
|
-
const providerChoice = await
|
|
611
|
+
const providerChoice = await selectScrollable(ctx, "Choose Sage model provider", providerOptions);
|
|
560
612
|
if (!providerChoice) return undefined;
|
|
561
613
|
|
|
562
614
|
if (providerChoice.startsWith(MODEL_INHERIT_VALUE)) {
|
|
@@ -587,8 +639,8 @@ async function pickModel(ctx: ExtensionContext, currentModel: string): Promise<s
|
|
|
587
639
|
modelOptionMap.set(option, composite);
|
|
588
640
|
}
|
|
589
641
|
|
|
590
|
-
const modelChoice = await
|
|
591
|
-
if (!modelChoice)
|
|
642
|
+
const modelChoice = await selectScrollable(ctx, `Choose Sage model (${provider})`, modelOptions);
|
|
643
|
+
if (!modelChoice) continue;
|
|
592
644
|
if (modelChoice === "← Back to providers") continue;
|
|
593
645
|
|
|
594
646
|
const resolved = modelOptionMap.get(modelChoice);
|
|
@@ -664,44 +716,77 @@ function resolveModelSpec(
|
|
|
664
716
|
|
|
665
717
|
type HasUIContext = { ui: ExtensionContext["ui"] };
|
|
666
718
|
|
|
667
|
-
async function
|
|
719
|
+
async function selectScrollable(
|
|
668
720
|
ctx: HasUIContext,
|
|
669
721
|
title: string,
|
|
670
722
|
options: string[],
|
|
671
|
-
|
|
723
|
+
maxVisible = 10
|
|
672
724
|
): Promise<string | undefined> {
|
|
673
|
-
if (options.length
|
|
674
|
-
return await ctx.ui.select(title, options);
|
|
675
|
-
}
|
|
725
|
+
if (options.length === 0) return undefined;
|
|
676
726
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
const PREV = "← Previous page";
|
|
680
|
-
const NEXT = "→ Next page";
|
|
727
|
+
return await ctx.ui.custom<string | undefined>((tui, theme, _keybindings, done) => {
|
|
728
|
+
let selectedIndex = 0;
|
|
681
729
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
730
|
+
const move = (delta: number): void => {
|
|
731
|
+
const next = selectedIndex + delta;
|
|
732
|
+
selectedIndex = Math.max(0, Math.min(options.length - 1, next));
|
|
733
|
+
tui.requestRender();
|
|
734
|
+
};
|
|
686
735
|
|
|
687
|
-
|
|
688
|
-
|
|
736
|
+
const render = (width: number): string[] => {
|
|
737
|
+
const lines: string[] = [];
|
|
738
|
+
lines.push(theme.fg("accent", title));
|
|
739
|
+
lines.push("");
|
|
689
740
|
|
|
690
|
-
|
|
691
|
-
|
|
741
|
+
const visible = Math.max(3, Math.min(maxVisible, options.length));
|
|
742
|
+
const start = Math.max(0, Math.min(selectedIndex - Math.floor(visible / 2), options.length - visible));
|
|
743
|
+
const end = Math.min(options.length, start + visible);
|
|
692
744
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
745
|
+
for (let i = start; i < end; i += 1) {
|
|
746
|
+
const option = options[i] ?? "";
|
|
747
|
+
const prefix = i === selectedIndex ? "→ " : " ";
|
|
748
|
+
const raw = `${prefix}${option}`;
|
|
749
|
+
lines.push(i === selectedIndex ? theme.fg("accent", raw) : raw);
|
|
750
|
+
}
|
|
697
751
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
continue;
|
|
701
|
-
}
|
|
752
|
+
lines.push("");
|
|
753
|
+
lines.push(theme.fg("muted", `(${selectedIndex + 1}/${options.length}) ↑↓ navigate • enter select • esc back`));
|
|
702
754
|
|
|
703
|
-
|
|
704
|
-
|
|
755
|
+
return lines.map((line) => (line.length > width ? `${line.slice(0, Math.max(0, width - 1))}…` : line));
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
const handleInput = (data: string): void => {
|
|
759
|
+
if (data.includes("\u001b[A")) {
|
|
760
|
+
move(-1);
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
if (data.includes("\u001b[B")) {
|
|
764
|
+
move(1);
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
if (data === "k") {
|
|
768
|
+
move(-1);
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
if (data === "j") {
|
|
772
|
+
move(1);
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
if (data === "\r" || data === "\n") {
|
|
776
|
+
done(options[selectedIndex]);
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
if (data === "\u001b") {
|
|
780
|
+
done(undefined);
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
return {
|
|
785
|
+
render,
|
|
786
|
+
invalidate: () => {},
|
|
787
|
+
handleInput
|
|
788
|
+
};
|
|
789
|
+
});
|
|
705
790
|
}
|
|
706
791
|
|
|
707
792
|
function onOff(value: boolean): string {
|