@ilya-lesikov/pi-pi 0.9.0 → 0.10.0
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/3p/pi-ask-user/index.ts +71 -124
- package/3p/pi-ask-user/single-select-layout.ts +3 -14
- package/3p/pi-subagents/package.json +13 -7
- package/3p/pi-subagents/src/agent-manager.ts +243 -79
- package/3p/pi-subagents/src/agent-runner.ts +501 -365
- package/3p/pi-subagents/src/agent-types.ts +46 -57
- package/3p/pi-subagents/src/cross-extension-rpc.ts +52 -46
- package/3p/pi-subagents/src/custom-agents.ts +30 -6
- package/3p/pi-subagents/src/default-agents.ts +25 -43
- package/3p/pi-subagents/src/enabled-models.ts +180 -0
- package/3p/pi-subagents/src/index.ts +599 -839
- package/3p/pi-subagents/src/model-resolver.ts +26 -7
- package/3p/pi-subagents/src/output-file.ts +21 -2
- package/3p/pi-subagents/src/prompts.ts +17 -3
- package/3p/pi-subagents/src/schedule-store.ts +153 -0
- package/3p/pi-subagents/src/schedule.ts +365 -0
- package/3p/pi-subagents/src/settings.ts +261 -0
- package/3p/pi-subagents/src/skill-loader.ts +77 -54
- package/3p/pi-subagents/src/status-note.ts +25 -0
- package/3p/pi-subagents/src/types.ts +97 -6
- package/3p/pi-subagents/src/ui/agent-widget.ts +94 -21
- package/3p/pi-subagents/src/ui/conversation-viewer.ts +147 -35
- package/3p/pi-subagents/src/ui/schedule-menu.ts +104 -0
- package/3p/pi-subagents/src/ui/viewer-keys.ts +39 -0
- package/3p/pi-subagents/src/usage.ts +60 -0
- package/3p/pi-subagents/src/worktree.ts +49 -20
- package/extensions/orchestrator/agents/advisor.ts +13 -8
- package/extensions/orchestrator/agents/brainstorm-reviewer.ts +8 -3
- package/extensions/orchestrator/agents/code-reviewer.ts +8 -3
- package/extensions/orchestrator/agents/constraints.test.ts +26 -0
- package/extensions/orchestrator/agents/constraints.ts +12 -2
- package/extensions/orchestrator/agents/deep-debugger.ts +13 -8
- package/extensions/orchestrator/agents/explore.ts +12 -6
- package/extensions/orchestrator/agents/librarian.ts +13 -5
- package/extensions/orchestrator/agents/plan-reviewer.ts +8 -3
- package/extensions/orchestrator/agents/planner.ts +8 -3
- package/extensions/orchestrator/agents/pools.test.ts +56 -0
- package/extensions/orchestrator/agents/prompts.test.ts +52 -10
- package/extensions/orchestrator/agents/registry.test.ts +245 -0
- package/extensions/orchestrator/agents/registry.ts +145 -10
- package/extensions/orchestrator/agents/reviewer.ts +14 -8
- package/extensions/orchestrator/agents/task.ts +12 -6
- package/extensions/orchestrator/agents/tool-routing.ts +213 -85
- package/extensions/orchestrator/agents/wait-for-completion.test.ts +171 -0
- package/extensions/orchestrator/ast-search.test.ts +124 -0
- package/extensions/orchestrator/billing-spoof.test.ts +67 -0
- package/extensions/orchestrator/billing-spoof.ts +95 -0
- package/extensions/orchestrator/cbm.more.test.ts +358 -0
- package/extensions/orchestrator/command-handlers.ts +6 -0
- package/extensions/orchestrator/commands.test.ts +15 -2
- package/extensions/orchestrator/commands.ts +1 -1
- package/extensions/orchestrator/config.test.ts +89 -1
- package/extensions/orchestrator/config.ts +102 -19
- package/extensions/orchestrator/context.test.ts +46 -0
- package/extensions/orchestrator/context.ts +18 -5
- package/extensions/orchestrator/custom-footer.test.ts +24 -10
- package/extensions/orchestrator/custom-footer.ts +4 -2
- package/extensions/orchestrator/doctor.more.test.ts +315 -0
- package/extensions/orchestrator/doctor.ts +6 -2
- package/extensions/orchestrator/event-handlers.more.test.ts +561 -0
- package/extensions/orchestrator/event-handlers.test.ts +96 -9
- package/extensions/orchestrator/event-handlers.ts +344 -151
- package/extensions/orchestrator/exa.more.test.ts +118 -0
- package/extensions/orchestrator/flant-infra.more.test.ts +326 -0
- package/extensions/orchestrator/flant-infra.test.ts +127 -0
- package/extensions/orchestrator/flant-infra.ts +113 -39
- package/extensions/orchestrator/index.test.ts +76 -0
- package/extensions/orchestrator/index.ts +2 -0
- package/extensions/orchestrator/integration.test.ts +183 -65
- package/extensions/orchestrator/model-registry.test.ts +2 -1
- package/extensions/orchestrator/model-registry.ts +12 -2
- package/extensions/orchestrator/orchestrator.test.ts +67 -0
- package/extensions/orchestrator/orchestrator.ts +119 -27
- package/extensions/orchestrator/phases/brainstorm.test.ts +30 -1
- package/extensions/orchestrator/phases/brainstorm.ts +43 -6
- package/extensions/orchestrator/phases/implementation.ts +2 -0
- package/extensions/orchestrator/phases/machine.test.ts +17 -1
- package/extensions/orchestrator/phases/machine.ts +4 -1
- package/extensions/orchestrator/phases/planning.test.ts +47 -1
- package/extensions/orchestrator/phases/planning.ts +18 -3
- package/extensions/orchestrator/phases/review-task.ts +5 -0
- package/extensions/orchestrator/phases/review.test.ts +10 -0
- package/extensions/orchestrator/phases/review.ts +22 -7
- package/extensions/orchestrator/phases/spawn-blocking.test.ts +1 -0
- package/extensions/orchestrator/phases/verdict.ts +6 -5
- package/extensions/orchestrator/plannotator-open-failure.test.ts +67 -0
- package/extensions/orchestrator/plannotator.test.ts +38 -1
- package/extensions/orchestrator/plannotator.ts +50 -3
- package/extensions/orchestrator/pp-menu.leaves.test.ts +590 -0
- package/extensions/orchestrator/pp-menu.more.test.ts +524 -0
- package/extensions/orchestrator/pp-menu.test.ts +114 -7
- package/extensions/orchestrator/pp-menu.ts +579 -90
- package/extensions/orchestrator/pp-state-tools.test.ts +80 -0
- package/extensions/orchestrator/pp-state-tools.ts +65 -0
- package/extensions/orchestrator/rate-limit-fallback.more.test.ts +241 -0
- package/extensions/orchestrator/rate-limit-fallback.test.ts +20 -1
- package/extensions/orchestrator/rate-limit-fallback.ts +12 -0
- package/extensions/orchestrator/review-files.test.ts +26 -0
- package/extensions/orchestrator/review-files.ts +3 -0
- package/extensions/orchestrator/state.test.ts +73 -1
- package/extensions/orchestrator/state.ts +95 -23
- package/extensions/orchestrator/suppress-pierre-theme-spam.test.ts +56 -0
- package/extensions/orchestrator/suppress-pierre-theme-spam.ts +45 -0
- package/extensions/orchestrator/validate-artifacts.ts +1 -1
- package/package.json +6 -2
- package/scripts/test-3p.sh +52 -0
package/3p/pi-ask-user/index.ts
CHANGED
|
@@ -96,7 +96,6 @@ interface AskParams {
|
|
|
96
96
|
allowComment?: boolean;
|
|
97
97
|
displayMode?: AskDisplayMode;
|
|
98
98
|
overlayToggleKey?: string | null;
|
|
99
|
-
commentToggleKey?: string | null;
|
|
100
99
|
timeout?: number;
|
|
101
100
|
}
|
|
102
101
|
|
|
@@ -304,7 +303,7 @@ type ResolvedShortcut =
|
|
|
304
303
|
|
|
305
304
|
interface ResolvedAskShortcuts {
|
|
306
305
|
overlayToggle: ResolvedShortcut;
|
|
307
|
-
|
|
306
|
+
commentSelect: ResolvedShortcut;
|
|
308
307
|
}
|
|
309
308
|
|
|
310
309
|
const DISABLED_SHORTCUT: ResolvedShortcut = {
|
|
@@ -367,9 +366,13 @@ const SINGLE_SELECT_SPLIT_PANE_LEFT_MIN_WIDTH = 32;
|
|
|
367
366
|
const SINGLE_SELECT_SPLIT_PANE_RIGHT_MIN_WIDTH = 28;
|
|
368
367
|
const SINGLE_SELECT_SPLIT_PANE_SEPARATOR = " │ ";
|
|
369
368
|
const FREEFORM_SENTINEL = "\u270f\ufe0f Type custom response...";
|
|
370
|
-
|
|
369
|
+
|
|
371
370
|
const DEFAULT_OVERLAY_TOGGLE_KEY = "alt+o";
|
|
372
|
-
|
|
371
|
+
// Fixed "select this option AND add extra context" gesture. Plain Enter still
|
|
372
|
+
// selects with no comment. ctrl+e does not collide with select-mode navigation
|
|
373
|
+
// (which is arrows / ctrl+j/k / tab).
|
|
374
|
+
const COMMENT_SELECT_KEY = "ctrl+e";
|
|
375
|
+
const COMMENT_SELECT_LABEL = "add context";
|
|
373
376
|
|
|
374
377
|
// Vim-style aliases for navigating option lists. ctrl+j/k are safe in the
|
|
375
378
|
// searchable single-select because they don't collide with fuzzy-search input.
|
|
@@ -435,15 +438,14 @@ class MultiSelectList implements Component {
|
|
|
435
438
|
private allowComment: boolean;
|
|
436
439
|
private theme: Theme;
|
|
437
440
|
private keybindings: KeybindingsManager;
|
|
438
|
-
private
|
|
441
|
+
private commentSelect: ResolvedShortcut;
|
|
439
442
|
private selectedIndex = 0;
|
|
440
443
|
private checked = new Set<number>();
|
|
441
|
-
private commentEnabled = false;
|
|
442
444
|
private cachedWidth?: number;
|
|
443
445
|
private cachedLines?: string[];
|
|
444
446
|
|
|
445
447
|
public onCancel?: () => void;
|
|
446
|
-
public onSubmit?: (result: string[]) => void;
|
|
448
|
+
public onSubmit?: (result: string[], wantsComment: boolean) => void;
|
|
447
449
|
public onEnterFreeform?: () => void;
|
|
448
450
|
|
|
449
451
|
constructor(
|
|
@@ -452,18 +454,14 @@ class MultiSelectList implements Component {
|
|
|
452
454
|
allowComment: boolean,
|
|
453
455
|
theme: Theme,
|
|
454
456
|
keybindings: KeybindingsManager,
|
|
455
|
-
|
|
457
|
+
commentSelect: ResolvedShortcut,
|
|
456
458
|
) {
|
|
457
459
|
this.options = options;
|
|
458
460
|
this.allowFreeform = allowFreeform;
|
|
459
461
|
this.allowComment = allowComment;
|
|
460
462
|
this.theme = theme;
|
|
461
463
|
this.keybindings = keybindings;
|
|
462
|
-
this.
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
public isCommentEnabled(): boolean {
|
|
466
|
-
return this.commentEnabled;
|
|
464
|
+
this.commentSelect = commentSelect;
|
|
467
465
|
}
|
|
468
466
|
|
|
469
467
|
invalidate(): void {
|
|
@@ -472,20 +470,11 @@ class MultiSelectList implements Component {
|
|
|
472
470
|
}
|
|
473
471
|
|
|
474
472
|
private getItemCount(): number {
|
|
475
|
-
return this.options.length + (this.
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
private getCommentToggleIndex(): number | null {
|
|
479
|
-
return this.allowComment ? this.options.length : null;
|
|
473
|
+
return this.options.length + (this.allowFreeform ? 1 : 0);
|
|
480
474
|
}
|
|
481
475
|
|
|
482
476
|
private getFreeformIndex(): number {
|
|
483
|
-
return this.options.length
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
private isCommentToggleRow(index: number): boolean {
|
|
487
|
-
const toggleIndex = this.getCommentToggleIndex();
|
|
488
|
-
return toggleIndex !== null && index === toggleIndex;
|
|
477
|
+
return this.options.length;
|
|
489
478
|
}
|
|
490
479
|
|
|
491
480
|
private isFreeformRow(index: number): boolean {
|
|
@@ -498,10 +487,13 @@ class MultiSelectList implements Component {
|
|
|
498
487
|
else this.checked.add(index);
|
|
499
488
|
}
|
|
500
489
|
|
|
501
|
-
private
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
490
|
+
private currentSelection(): string[] {
|
|
491
|
+
const selectedTitles = Array.from(this.checked)
|
|
492
|
+
.sort((a, b) => a - b)
|
|
493
|
+
.map((i) => this.options[i]?.title)
|
|
494
|
+
.filter((t): t is string => !!t);
|
|
495
|
+
const fallback = this.options[this.selectedIndex]?.title;
|
|
496
|
+
return selectedTitles.length > 0 ? selectedTitles : fallback ? [fallback] : [];
|
|
505
497
|
}
|
|
506
498
|
|
|
507
499
|
handleInput(data: string): void {
|
|
@@ -516,8 +508,14 @@ class MultiSelectList implements Component {
|
|
|
516
508
|
return;
|
|
517
509
|
}
|
|
518
510
|
|
|
519
|
-
if (
|
|
520
|
-
this.
|
|
511
|
+
if (
|
|
512
|
+
this.allowComment &&
|
|
513
|
+
!this.commentSelect.disabled &&
|
|
514
|
+
this.commentSelect.matches(data) &&
|
|
515
|
+
!this.isFreeformRow(this.selectedIndex)
|
|
516
|
+
) {
|
|
517
|
+
const result = this.currentSelection();
|
|
518
|
+
if (result.length > 0) this.onSubmit?.(result, true);
|
|
521
519
|
return;
|
|
522
520
|
}
|
|
523
521
|
|
|
@@ -545,10 +543,6 @@ class MultiSelectList implements Component {
|
|
|
545
543
|
}
|
|
546
544
|
|
|
547
545
|
if (matchesKey(data, Key.space)) {
|
|
548
|
-
if (this.isCommentToggleRow(this.selectedIndex)) {
|
|
549
|
-
this.toggleComment();
|
|
550
|
-
return;
|
|
551
|
-
}
|
|
552
546
|
if (this.isFreeformRow(this.selectedIndex)) {
|
|
553
547
|
this.onEnterFreeform?.();
|
|
554
548
|
return;
|
|
@@ -559,24 +553,13 @@ class MultiSelectList implements Component {
|
|
|
559
553
|
}
|
|
560
554
|
|
|
561
555
|
if (this.keybindings.matches(data, "tui.select.confirm")) {
|
|
562
|
-
if (this.isCommentToggleRow(this.selectedIndex)) {
|
|
563
|
-
this.toggleComment();
|
|
564
|
-
return;
|
|
565
|
-
}
|
|
566
556
|
if (this.isFreeformRow(this.selectedIndex)) {
|
|
567
557
|
this.onEnterFreeform?.();
|
|
568
558
|
return;
|
|
569
559
|
}
|
|
570
560
|
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
.map((i) => this.options[i]?.title)
|
|
574
|
-
.filter((t): t is string => !!t);
|
|
575
|
-
|
|
576
|
-
const fallback = this.options[this.selectedIndex]?.title;
|
|
577
|
-
const result = selectedTitles.length > 0 ? selectedTitles : fallback ? [fallback] : [];
|
|
578
|
-
|
|
579
|
-
if (result.length > 0) this.onSubmit?.(result);
|
|
561
|
+
const result = this.currentSelection();
|
|
562
|
+
if (result.length > 0) this.onSubmit?.(result, false);
|
|
580
563
|
else this.onCancel?.();
|
|
581
564
|
}
|
|
582
565
|
}
|
|
@@ -605,15 +588,6 @@ class MultiSelectList implements Component {
|
|
|
605
588
|
const isSelected = i === this.selectedIndex;
|
|
606
589
|
const prefix = isSelected ? theme.fg("accent", "→") : " ";
|
|
607
590
|
|
|
608
|
-
if (this.isCommentToggleRow(i)) {
|
|
609
|
-
const checkbox = this.commentEnabled ? theme.fg("success", "[✓]") : theme.fg("dim", "[ ]");
|
|
610
|
-
const label = isSelected
|
|
611
|
-
? theme.fg("accent", theme.bold(COMMENT_TOGGLE_LABEL))
|
|
612
|
-
: theme.fg("text", theme.bold(COMMENT_TOGGLE_LABEL));
|
|
613
|
-
lines.push(truncateToWidth(`${prefix} ${checkbox} ${label}`, width, ""));
|
|
614
|
-
continue;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
591
|
if (this.isFreeformRow(i)) {
|
|
618
592
|
const label = theme.fg("text", theme.bold("Type something."));
|
|
619
593
|
const desc = theme.fg("muted", "Enter a custom response");
|
|
@@ -660,16 +634,15 @@ class WrappedSingleSelectList implements Component {
|
|
|
660
634
|
private allowComment: boolean;
|
|
661
635
|
private theme: Theme;
|
|
662
636
|
private keybindings: KeybindingsManager;
|
|
663
|
-
private
|
|
637
|
+
private commentSelect: ResolvedShortcut;
|
|
664
638
|
private selectedIndex = 0;
|
|
665
639
|
private searchQuery = "";
|
|
666
|
-
private commentEnabled = false;
|
|
667
640
|
private maxVisibleRows = 12;
|
|
668
641
|
private cachedWidth?: number;
|
|
669
642
|
private cachedLines?: string[];
|
|
670
643
|
|
|
671
644
|
public onCancel?: () => void;
|
|
672
|
-
public onSubmit?: (result: string) => void;
|
|
645
|
+
public onSubmit?: (result: string, wantsComment: boolean) => void;
|
|
673
646
|
public onEnterFreeform?: () => void;
|
|
674
647
|
|
|
675
648
|
constructor(
|
|
@@ -678,18 +651,14 @@ class WrappedSingleSelectList implements Component {
|
|
|
678
651
|
allowComment: boolean,
|
|
679
652
|
theme: Theme,
|
|
680
653
|
keybindings: KeybindingsManager,
|
|
681
|
-
|
|
654
|
+
commentSelect: ResolvedShortcut,
|
|
682
655
|
) {
|
|
683
656
|
this.options = options;
|
|
684
657
|
this.allowFreeform = allowFreeform;
|
|
685
658
|
this.allowComment = allowComment;
|
|
686
659
|
this.theme = theme;
|
|
687
660
|
this.keybindings = keybindings;
|
|
688
|
-
this.
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
public isCommentEnabled(): boolean {
|
|
692
|
-
return this.commentEnabled;
|
|
661
|
+
this.commentSelect = commentSelect;
|
|
693
662
|
}
|
|
694
663
|
|
|
695
664
|
setMaxVisibleRows(rows: number): void {
|
|
@@ -710,21 +679,11 @@ class WrappedSingleSelectList implements Component {
|
|
|
710
679
|
}
|
|
711
680
|
|
|
712
681
|
private getItemCount(filteredOptions: QuestionOption[]): number {
|
|
713
|
-
return filteredOptions.length + (this.
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
private isCommentToggleRow(index: number, filteredOptions: QuestionOption[]): boolean {
|
|
717
|
-
return this.allowComment && index === filteredOptions.length;
|
|
682
|
+
return filteredOptions.length + (this.allowFreeform ? 1 : 0);
|
|
718
683
|
}
|
|
719
684
|
|
|
720
685
|
private isFreeformRow(index: number, filteredOptions: QuestionOption[]): boolean {
|
|
721
|
-
return this.allowFreeform && index === filteredOptions.length
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
private toggleComment(): void {
|
|
725
|
-
if (!this.allowComment) return;
|
|
726
|
-
this.commentEnabled = !this.commentEnabled;
|
|
727
|
-
this.invalidate();
|
|
686
|
+
return this.allowFreeform && index === filteredOptions.length;
|
|
728
687
|
}
|
|
729
688
|
|
|
730
689
|
private setSearchQuery(query: string): void {
|
|
@@ -822,8 +781,6 @@ class WrappedSingleSelectList implements Component {
|
|
|
822
781
|
selectedIndex: this.selectedIndex,
|
|
823
782
|
width,
|
|
824
783
|
allowFreeform: this.allowFreeform,
|
|
825
|
-
allowComment: this.allowComment,
|
|
826
|
-
commentEnabled: this.commentEnabled,
|
|
827
784
|
maxRows,
|
|
828
785
|
hideDescriptions,
|
|
829
786
|
});
|
|
@@ -840,11 +797,7 @@ class WrappedSingleSelectList implements Component {
|
|
|
840
797
|
|
|
841
798
|
let md = "";
|
|
842
799
|
|
|
843
|
-
if (this.
|
|
844
|
-
md += "## Additional context\n\n";
|
|
845
|
-
md += `Currently: **${this.commentEnabled ? "Enabled" : "Disabled"}**\n\n`;
|
|
846
|
-
md += "Turn this on when the selected option needs extra explanation before the tool submits.\n";
|
|
847
|
-
} else if (this.isFreeformRow(this.selectedIndex, filteredOptions)) {
|
|
800
|
+
if (this.isFreeformRow(this.selectedIndex, filteredOptions)) {
|
|
848
801
|
md += "## Custom response\n\n";
|
|
849
802
|
md += "Open the editor to write **any** answer.\n\n";
|
|
850
803
|
md += "*Use this when none of the listed options fit.*\n";
|
|
@@ -903,14 +856,21 @@ class WrappedSingleSelectList implements Component {
|
|
|
903
856
|
return;
|
|
904
857
|
}
|
|
905
858
|
|
|
906
|
-
if (this.allowComment && !this.commentToggle.disabled && this.commentToggle.matches(data)) {
|
|
907
|
-
this.toggleComment();
|
|
908
|
-
return;
|
|
909
|
-
}
|
|
910
|
-
|
|
911
859
|
const filteredOptions = this.getFilteredOptions();
|
|
912
860
|
const count = this.getItemCount(filteredOptions);
|
|
913
861
|
|
|
862
|
+
if (
|
|
863
|
+
this.allowComment &&
|
|
864
|
+
!this.commentSelect.disabled &&
|
|
865
|
+
this.commentSelect.matches(data) &&
|
|
866
|
+
count > 0 &&
|
|
867
|
+
!this.isFreeformRow(this.selectedIndex, filteredOptions)
|
|
868
|
+
) {
|
|
869
|
+
const result = filteredOptions[this.selectedIndex]?.title;
|
|
870
|
+
if (result) this.onSubmit?.(result, true);
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
|
|
914
874
|
if (matchesSelectUp(data, this.keybindings) && count > 0) {
|
|
915
875
|
this.selectedIndex = this.selectedIndex === 0 ? count - 1 : this.selectedIndex - 1;
|
|
916
876
|
this.invalidate();
|
|
@@ -933,23 +893,14 @@ class WrappedSingleSelectList implements Component {
|
|
|
933
893
|
}
|
|
934
894
|
}
|
|
935
895
|
|
|
936
|
-
if (matchesKey(data, Key.space) && count > 0 && this.isCommentToggleRow(this.selectedIndex, filteredOptions)) {
|
|
937
|
-
this.toggleComment();
|
|
938
|
-
return;
|
|
939
|
-
}
|
|
940
|
-
|
|
941
896
|
if (this.keybindings.matches(data, "tui.select.confirm") && count > 0) {
|
|
942
|
-
if (this.isCommentToggleRow(this.selectedIndex, filteredOptions)) {
|
|
943
|
-
this.toggleComment();
|
|
944
|
-
return;
|
|
945
|
-
}
|
|
946
897
|
if (this.isFreeformRow(this.selectedIndex, filteredOptions)) {
|
|
947
898
|
this.onEnterFreeform?.();
|
|
948
899
|
return;
|
|
949
900
|
}
|
|
950
901
|
|
|
951
902
|
const result = filteredOptions[this.selectedIndex]?.title;
|
|
952
|
-
if (result) this.onSubmit?.(result);
|
|
903
|
+
if (result) this.onSubmit?.(result, false);
|
|
953
904
|
else this.onCancel?.();
|
|
954
905
|
return;
|
|
955
906
|
}
|
|
@@ -1169,8 +1120,8 @@ class AskComponent extends Container {
|
|
|
1169
1120
|
const overlayHint = this.displayMode === "overlay" && !this.shortcuts.overlayToggle.disabled
|
|
1170
1121
|
? literalHint(theme, this.shortcuts.overlayToggle.spec, "hide")
|
|
1171
1122
|
: null;
|
|
1172
|
-
const commentHint = this.allowComment && !this.shortcuts.
|
|
1173
|
-
? literalHint(theme, this.shortcuts.
|
|
1123
|
+
const commentHint = this.allowComment && !this.shortcuts.commentSelect.disabled
|
|
1124
|
+
? literalHint(theme, this.shortcuts.commentSelect.spec, COMMENT_SELECT_LABEL)
|
|
1174
1125
|
: null;
|
|
1175
1126
|
if (this.mode === "freeform" || this.mode === "comment") {
|
|
1176
1127
|
const alternateCancelKeys = this.keybindings
|
|
@@ -1232,9 +1183,9 @@ class AskComponent extends Container {
|
|
|
1232
1183
|
this.allowComment,
|
|
1233
1184
|
this.theme,
|
|
1234
1185
|
this.keybindings,
|
|
1235
|
-
this.shortcuts.
|
|
1186
|
+
this.shortcuts.commentSelect,
|
|
1236
1187
|
);
|
|
1237
|
-
list.onSubmit = (result) => this.handleSelectionSubmit([result],
|
|
1188
|
+
list.onSubmit = (result, wantsComment) => this.handleSelectionSubmit([result], wantsComment);
|
|
1238
1189
|
list.onCancel = () => this.onDone(makeCancel("user"));
|
|
1239
1190
|
list.onEnterFreeform = () => this.showFreeformMode();
|
|
1240
1191
|
|
|
@@ -1251,10 +1202,10 @@ class AskComponent extends Container {
|
|
|
1251
1202
|
this.allowComment,
|
|
1252
1203
|
this.theme,
|
|
1253
1204
|
this.keybindings,
|
|
1254
|
-
this.shortcuts.
|
|
1205
|
+
this.shortcuts.commentSelect,
|
|
1255
1206
|
);
|
|
1256
1207
|
list.onCancel = () => this.onDone(makeCancel("user"));
|
|
1257
|
-
list.onSubmit = (result) => this.handleSelectionSubmit(result,
|
|
1208
|
+
list.onSubmit = (result, wantsComment) => this.handleSelectionSubmit(result, wantsComment);
|
|
1258
1209
|
list.onEnterFreeform = () => this.showFreeformMode();
|
|
1259
1210
|
|
|
1260
1211
|
this.multiSelectList = list;
|
|
@@ -1487,7 +1438,6 @@ export async function askUser(
|
|
|
1487
1438
|
overlay?: boolean;
|
|
1488
1439
|
displayMode?: AskDisplayMode;
|
|
1489
1440
|
overlayToggleKey?: string | null;
|
|
1490
|
-
commentToggleKey?: string | null;
|
|
1491
1441
|
},
|
|
1492
1442
|
): Promise<AskResponse | AskCancel | null> {
|
|
1493
1443
|
const {
|
|
@@ -1502,7 +1452,6 @@ export async function askUser(
|
|
|
1502
1452
|
overlay,
|
|
1503
1453
|
displayMode,
|
|
1504
1454
|
overlayToggleKey,
|
|
1505
|
-
commentToggleKey,
|
|
1506
1455
|
} = opts;
|
|
1507
1456
|
|
|
1508
1457
|
const requestedMode = displayMode ?? (overlay === undefined ? undefined : overlay ? "overlay" : "inline");
|
|
@@ -1517,11 +1466,7 @@ export async function askUser(
|
|
|
1517
1466
|
process.env.PI_ASK_USER_OVERLAY_TOGGLE_KEY,
|
|
1518
1467
|
DEFAULT_OVERLAY_TOGGLE_KEY,
|
|
1519
1468
|
),
|
|
1520
|
-
|
|
1521
|
-
commentToggleKey,
|
|
1522
|
-
process.env.PI_ASK_USER_COMMENT_TOGGLE_KEY,
|
|
1523
|
-
DEFAULT_COMMENT_TOGGLE_KEY,
|
|
1524
|
-
),
|
|
1469
|
+
commentSelect: buildShortcut(COMMENT_SELECT_KEY),
|
|
1525
1470
|
};
|
|
1526
1471
|
|
|
1527
1472
|
const options = normalizeOptions(rawOptions);
|
|
@@ -1603,15 +1548,15 @@ export default function(pi: ExtensionAPI) {
|
|
|
1603
1548
|
name: "ask_user",
|
|
1604
1549
|
label: "Ask User",
|
|
1605
1550
|
description:
|
|
1606
|
-
"Ask the user a question with optional multiple-choice answers. Use this to gather information interactively. Ask exactly one focused question per call. Keep the `question` SHORT — one scannable line; put
|
|
1551
|
+
"Ask the user a question with optional multiple-choice answers. Use this to gather information interactively. Ask exactly one focused question per call — NEVER bundle multiple decisions into one prompt; spawn several asks in sequence instead. Keep the `question` SHORT — one scannable line; put the substantive context, reasoning, and findings in your message (or the `context` field) BEFORE the call, so the terse question and options are interpretable. Do NOT add an option like 'I'll answer in a comment' — the built-in freeform 'Type something' path already covers custom answers.",
|
|
1607
1552
|
promptSnippet:
|
|
1608
1553
|
"Ask the user one short, focused question with optional multiple-choice answers to gather information interactively",
|
|
1609
1554
|
promptGuidelines: [
|
|
1610
1555
|
"Keep the `question` field SHORT — a single scannable line (ideally under ~100 chars). The dialogue de-emphasizes it; the user reads your detail from the message rendered above the dialogue.",
|
|
1611
|
-
"
|
|
1556
|
+
"ALWAYS present substantive context BEFORE the ask (in your assistant message and/or the `context` field): the question and options are intentionally terse, so the surrounding context must make them interpretable.",
|
|
1612
1557
|
"Use ask_user when the user's intent is ambiguous, when a decision requires explicit user input, or when multiple valid options exist.",
|
|
1613
|
-
"Ask exactly one focused question per ask_user call.",
|
|
1614
|
-
"
|
|
1558
|
+
"Ask exactly one focused question per ask_user call. NEVER combine multiple numbered, multipart, or unrelated decisions into one prompt — spawn multiple focused asks in sequence instead.",
|
|
1559
|
+
"NEVER add an option that just says the user will answer in a comment / free text (e.g. 'I'll type my own answer') — the built-in 'Type something' freeform path already covers custom answers.",
|
|
1615
1560
|
],
|
|
1616
1561
|
// Block other tool calls in the same assistant turn until the user answers,
|
|
1617
1562
|
// so the model can't batch ask_user with bash/edit/write and let those run
|
|
@@ -1658,12 +1603,6 @@ export default function(pi: ExtensionAPI) {
|
|
|
1658
1603
|
"Shortcut for hiding/showing the overlay popup (overlay mode only), e.g. 'alt+o' or 'ctrl+shift+h'. Pass 'off' to disable. Default: PI_ASK_USER_OVERLAY_TOGGLE_KEY env var if set, otherwise 'alt+o'.",
|
|
1659
1604
|
}),
|
|
1660
1605
|
),
|
|
1661
|
-
commentToggleKey: Type.Optional(
|
|
1662
|
-
Type.String({
|
|
1663
|
-
description:
|
|
1664
|
-
"Shortcut for toggling the optional comment/extra-context row when allowComment is true, e.g. 'ctrl+g'. Pass 'off' to disable. Default: PI_ASK_USER_COMMENT_TOGGLE_KEY env var if set, otherwise 'ctrl+g'.",
|
|
1665
|
-
}),
|
|
1666
|
-
),
|
|
1667
1606
|
timeout: Type.Optional(
|
|
1668
1607
|
Type.Number({ description: "Auto-dismiss after N milliseconds. Returns null (cancelled) when expired." }),
|
|
1669
1608
|
),
|
|
@@ -1686,7 +1625,6 @@ export default function(pi: ExtensionAPI) {
|
|
|
1686
1625
|
allowComment = true,
|
|
1687
1626
|
displayMode,
|
|
1688
1627
|
overlayToggleKey,
|
|
1689
|
-
commentToggleKey,
|
|
1690
1628
|
timeout,
|
|
1691
1629
|
} = params as AskParams;
|
|
1692
1630
|
|
|
@@ -1712,6 +1650,11 @@ export default function(pi: ExtensionAPI) {
|
|
|
1712
1650
|
});
|
|
1713
1651
|
}
|
|
1714
1652
|
|
|
1653
|
+
pi.events.emit("ask:opened", {
|
|
1654
|
+
question,
|
|
1655
|
+
context: normalizedContext,
|
|
1656
|
+
options,
|
|
1657
|
+
});
|
|
1715
1658
|
let result: AskResponse | AskCancel | null;
|
|
1716
1659
|
try {
|
|
1717
1660
|
result = await askUser(ctx, {
|
|
@@ -1723,13 +1666,17 @@ export default function(pi: ExtensionAPI) {
|
|
|
1723
1666
|
allowComment,
|
|
1724
1667
|
displayMode,
|
|
1725
1668
|
overlayToggleKey,
|
|
1726
|
-
commentToggleKey,
|
|
1727
1669
|
timeout,
|
|
1728
1670
|
signal,
|
|
1729
1671
|
});
|
|
1730
1672
|
} catch (error) {
|
|
1731
1673
|
const message =
|
|
1732
1674
|
error instanceof Error ? `${error.message}\n${error.stack ?? ""}` : String(error);
|
|
1675
|
+
pi.events.emit("ask:cancelled", {
|
|
1676
|
+
question,
|
|
1677
|
+
context: normalizedContext,
|
|
1678
|
+
options,
|
|
1679
|
+
});
|
|
1733
1680
|
return {
|
|
1734
1681
|
content: [{ type: "text" as const, text: `Ask tool failed: ${message}` }],
|
|
1735
1682
|
isError: true,
|
|
@@ -13,8 +13,6 @@ export interface RenderSingleSelectRowsParams {
|
|
|
13
13
|
selectedIndex: number;
|
|
14
14
|
width: number;
|
|
15
15
|
allowFreeform: boolean;
|
|
16
|
-
allowComment?: boolean;
|
|
17
|
-
commentEnabled?: boolean;
|
|
18
16
|
maxRows?: number;
|
|
19
17
|
hideDescriptions?: boolean;
|
|
20
18
|
}
|
|
@@ -74,25 +72,18 @@ interface ItemBlock {
|
|
|
74
72
|
|
|
75
73
|
type ListItem =
|
|
76
74
|
| { type: "option"; option: QuestionOption }
|
|
77
|
-
| { type: "comment-toggle"; option: QuestionOption }
|
|
78
75
|
| { type: "freeform"; option: QuestionOption };
|
|
79
76
|
|
|
80
77
|
function buildItemBlocks(
|
|
81
78
|
options: QuestionOption[],
|
|
82
79
|
width: number,
|
|
83
80
|
allowFreeform: boolean,
|
|
84
|
-
allowComment: boolean,
|
|
85
|
-
commentEnabled: boolean,
|
|
86
81
|
selectedIndex: number,
|
|
87
82
|
hideDescriptions = false,
|
|
88
83
|
): ItemBlock[] {
|
|
89
84
|
const normalizedWidth = Math.max(12, width);
|
|
90
85
|
const freeformLabel = "Type something. — Enter a custom response";
|
|
91
|
-
const commentToggleLabel = `${commentEnabled ? "[✓]" : "[ ]"} Add extra context after selection`;
|
|
92
86
|
const allItems: ListItem[] = options.map((option) => ({ type: "option", option }));
|
|
93
|
-
if (allowComment) {
|
|
94
|
-
allItems.push({ type: "comment-toggle", option: { title: commentToggleLabel } });
|
|
95
|
-
}
|
|
96
87
|
if (allowFreeform) {
|
|
97
88
|
allItems.push({ type: "freeform", option: { title: freeformLabel } });
|
|
98
89
|
}
|
|
@@ -101,7 +92,7 @@ function buildItemBlocks(
|
|
|
101
92
|
const pointer = itemIndex === selectedIndex ? "→" : " ";
|
|
102
93
|
const lines: string[] = [];
|
|
103
94
|
|
|
104
|
-
if (item.type === "
|
|
95
|
+
if (item.type === "freeform") {
|
|
105
96
|
const prefix = `${pointer} `;
|
|
106
97
|
const wrapped = wrapText(item.option.title, Math.max(8, normalizedWidth - prefix.length));
|
|
107
98
|
wrapped.forEach((line, lineIndex) => {
|
|
@@ -146,13 +137,11 @@ export function renderSingleSelectRows({
|
|
|
146
137
|
selectedIndex,
|
|
147
138
|
width,
|
|
148
139
|
allowFreeform,
|
|
149
|
-
allowComment = false,
|
|
150
|
-
commentEnabled = false,
|
|
151
140
|
maxRows,
|
|
152
141
|
hideDescriptions,
|
|
153
142
|
}: RenderSingleSelectRowsParams): AnnotatedRow[] {
|
|
154
|
-
const itemCount = options.length + (
|
|
155
|
-
const blocks = buildItemBlocks(options, width, allowFreeform,
|
|
143
|
+
const itemCount = options.length + (allowFreeform ? 1 : 0);
|
|
144
|
+
const blocks = buildItemBlocks(options, width, allowFreeform, selectedIndex, hideDescriptions);
|
|
156
145
|
const allRows = flatten(blocks, selectedIndex);
|
|
157
146
|
|
|
158
147
|
if (!Number.isFinite(maxRows) || !maxRows || maxRows <= 0 || allRows.length <= maxRows) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tintinweb/pi-subagents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "A pi extension extension that brings smart Claude Code-style autonomous sub-agents to pi.",
|
|
5
5
|
"author": "tintinweb",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,31 +13,37 @@
|
|
|
13
13
|
"url": "https://github.com/tintinweb/pi-subagents/issues"
|
|
14
14
|
},
|
|
15
15
|
"keywords": [
|
|
16
|
+
"pi-package",
|
|
16
17
|
"pi",
|
|
17
18
|
"pi-extension",
|
|
18
19
|
"subagent",
|
|
19
20
|
"agent",
|
|
20
21
|
"autonomous"
|
|
21
22
|
],
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"@earendil-works/pi-ai": ">=0.74.0",
|
|
25
|
+
"@earendil-works/pi-coding-agent": ">=0.74.0",
|
|
26
|
+
"@earendil-works/pi-tui": ">=0.74.0"
|
|
27
|
+
},
|
|
22
28
|
"dependencies": {
|
|
23
|
-
"@
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"@sinclair/typebox": "latest"
|
|
29
|
+
"@sinclair/typebox": "^0.34.49",
|
|
30
|
+
"croner": "^10.0.1",
|
|
31
|
+
"nanoid": "^5.0.0"
|
|
27
32
|
},
|
|
28
33
|
"scripts": {
|
|
29
34
|
"build": "tsc",
|
|
30
35
|
"prepublishOnly": "npm run lint && npm run typecheck && npm run test && npm run build",
|
|
31
36
|
"test": "vitest run",
|
|
32
37
|
"test:watch": "vitest",
|
|
38
|
+
"test:e2e": "vitest run e2e --reporter=verbose",
|
|
33
39
|
"typecheck": "tsc --noEmit",
|
|
34
40
|
"lint": "biome check src/ test/",
|
|
35
41
|
"lint:fix": "biome check --fix src/ test/"
|
|
36
42
|
},
|
|
37
43
|
"devDependencies": {
|
|
38
|
-
"@biomejs/biome": "^2.
|
|
44
|
+
"@biomejs/biome": "^2.4.14",
|
|
39
45
|
"@types/node": "^25.5.0",
|
|
40
|
-
"typescript": "^
|
|
46
|
+
"typescript": "^6.0.0",
|
|
41
47
|
"vitest": "^4.0.18"
|
|
42
48
|
},
|
|
43
49
|
"pi": {
|