@pi-unipi/unipi 0.1.18 → 2.0.1
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 +1 -1
- package/package.json +7 -4
- package/packages/ask-user/README.md +30 -0
- package/packages/ask-user/ask-ui.ts +103 -42
- package/packages/ask-user/commands.ts +1 -1
- package/packages/ask-user/handoff.ts +163 -0
- package/packages/ask-user/launcher-ui.ts +5 -4
- package/packages/ask-user/skills/ask-user/SKILL.md +6 -6
- package/packages/ask-user/tools.ts +53 -18
- package/packages/ask-user/types.ts +22 -0
- package/packages/autocomplete/src/constants.ts +28 -14
- package/packages/cocoindex/README.md +93 -0
- package/packages/cocoindex/bridge.ts +842 -0
- package/packages/cocoindex/commands.ts +175 -0
- package/packages/cocoindex/index.ts +55 -0
- package/packages/cocoindex/installer.ts +397 -0
- package/packages/cocoindex/skills/cocoindex/SKILL.md +88 -0
- package/packages/cocoindex/tools.ts +131 -0
- package/packages/compactor/README.md +3 -1
- package/packages/compactor/src/commands/index.ts +79 -169
- package/packages/compactor/src/compaction/content.ts +2 -2
- package/packages/compactor/src/compaction/cut.ts +10 -6
- package/packages/compactor/src/compaction/hooks.ts +82 -52
- package/packages/compactor/src/compaction/recall-scope.ts +1 -1
- package/packages/compactor/src/config/manager.ts +0 -0
- package/packages/compactor/src/config/presets.ts +10 -10
- package/packages/compactor/src/executor/executor.ts +4 -4
- package/packages/compactor/src/index.ts +34 -45
- package/packages/compactor/src/info-screen.ts +97 -40
- package/packages/compactor/src/session/db.ts +40 -11
- package/packages/compactor/src/session/extract.ts +37 -0
- package/packages/compactor/src/tools/ctx-batch-execute.ts +5 -16
- package/packages/compactor/src/tools/ctx-doctor.ts +0 -18
- package/packages/compactor/src/tools/ctx-stats.ts +43 -10
- package/packages/compactor/src/tools/register.ts +30 -122
- package/packages/compactor/src/tui/settings-overlay.ts +12 -21
- package/packages/compactor/src/types.ts +8 -26
- package/packages/core/constants.ts +28 -7
- package/packages/core/events.ts +39 -3
- package/packages/core/global-types.ts +46 -0
- package/packages/core/global.d.ts +23 -0
- package/packages/core/sandbox.ts +61 -13
- package/packages/footer/src/commands.ts +2 -2
- package/packages/footer/src/config.ts +12 -0
- package/packages/footer/src/help.ts +2 -2
- package/packages/footer/src/index.ts +23 -14
- package/packages/footer/src/presets.ts +2 -2
- package/packages/footer/src/registry/index.ts +1 -1
- package/packages/footer/src/rendering/icons.ts +43 -43
- package/packages/footer/src/rendering/renderer.ts +67 -20
- package/packages/footer/src/segments/compactor.ts +58 -23
- package/packages/footer/src/segments/core.ts +2 -1
- package/packages/footer/src/segments/kanboard.ts +4 -4
- package/packages/footer/src/segments/mcp.ts +3 -11
- package/packages/footer/src/segments/memory.ts +2 -3
- package/packages/footer/src/segments/notify.ts +2 -2
- package/packages/footer/src/segments/ralph.ts +6 -8
- package/packages/footer/src/segments/workflow.ts +2 -1
- package/packages/footer/src/tui/settings-tui.ts +26 -7
- package/packages/info-screen/core-groups.ts +3 -2
- package/packages/info-screen/index.ts +10 -9
- package/packages/info-screen/registry.ts +3 -4
- package/packages/input-shortcuts/src/index.ts +2 -3
- package/packages/kanboard/commands.ts +5 -5
- package/packages/kanboard/index.ts +1 -2
- package/packages/kanboard/tui/kanboard-overlay.ts +10 -8
- package/packages/mcp/src/index.ts +7 -8
- package/packages/mcp/src/tui/add-overlay.ts +11 -9
- package/packages/mcp/src/tui/settings-overlay.ts +5 -4
- package/packages/memory/embedding.ts +7 -7
- package/packages/memory/index.ts +55 -22
- package/packages/memory/storage.ts +55 -30
- package/packages/memory/tools.ts +15 -5
- package/packages/milestone/commands.ts +3 -3
- package/packages/milestone/index.ts +2 -3
- package/packages/notify/commands.ts +5 -5
- package/packages/ralph/index.ts +1 -2
- package/packages/subagents/src/index.ts +5 -5
- package/packages/subagents/src/widget.ts +4 -3
- package/packages/unipi/index.ts +2 -0
- package/packages/updater/src/checker.ts +2 -2
- package/packages/updater/src/commands.ts +6 -6
- package/packages/updater/src/index.ts +12 -10
- package/packages/updater/src/installer.ts +5 -2
- package/packages/updater/src/tui/changelog-overlay.ts +2 -2
- package/packages/updater/src/tui/readme-overlay.ts +4 -4
- package/packages/updater/src/tui/settings-overlay.ts +3 -3
- package/packages/updater/src/tui/update-overlay.ts +3 -3
- package/packages/utility/src/commands.ts +2 -2
- package/packages/utility/src/diff/highlighter.ts +1 -1
- package/packages/utility/src/diff/theme.ts +0 -0
- package/packages/utility/src/index.ts +21 -18
- package/packages/web-api/src/engine/dependencies.ts +4 -4
- package/packages/web-api/src/index.ts +1 -2
- package/packages/workflow/commands.ts +1 -1
- package/packages/workflow/index.ts +15 -7
- package/packages/workflow/skills/brainstorm/SKILL.md +20 -6
- package/packages/workflow/skills/debug/SKILL.md +4 -2
- package/packages/workflow/skills/plan/SKILL.md +36 -13
- package/packages/workflow/skills/review-work/SKILL.md +21 -1
- package/packages/workflow/skills/work/SKILL.md +22 -1
- package/packages/compactor/src/store/chunking.ts +0 -126
- package/packages/compactor/src/store/db-base.ts +0 -87
- package/packages/compactor/src/store/index.ts +0 -513
- package/packages/compactor/src/store/unified.ts +0 -109
- package/packages/compactor/src/tools/ctx-fetch-and-index.ts +0 -32
- package/packages/compactor/src/tools/ctx-index.ts +0 -36
- package/packages/compactor/src/tools/ctx-search.ts +0 -19
package/README.md
CHANGED
|
@@ -77,7 +77,7 @@ Coexists triggers enhance behavior when packages are installed together. Workflo
|
|
|
77
77
|
| Workflow | `/unipi:` | brainstorm, plan, work, review-work, consolidate, quick-work, debug, fix |
|
|
78
78
|
| Ralph | `/unipi:ralph` | start, stop, resume, status |
|
|
79
79
|
| Memory | `/unipi:memory-` | process, search, consolidate, forget |
|
|
80
|
-
| Compactor | `/unipi
|
|
80
|
+
| Compactor | `/unipi:` | lossless-compact, compact-stats, compact-settings, compact-preset, session-recall, content-index, content-search |
|
|
81
81
|
| Notify | `/unipi:notify-` | settings, test, set-tg, set-ntfy |
|
|
82
82
|
| MCP | `/unipi:mcp-` | add, settings, sync, status |
|
|
83
83
|
| Web | `/unipi:web-` | settings, cache-clear |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/unipi",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "All-in-one extension suite for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -55,7 +55,8 @@
|
|
|
55
55
|
"node_modules/@pi-unipi/command-enchantment/src/index.ts",
|
|
56
56
|
"node_modules/@pi-unipi/footer/src/index.ts",
|
|
57
57
|
"node_modules/@pi-unipi/updater/src/index.ts",
|
|
58
|
-
"node_modules/@pi-unipi/input-shortcuts/src/index.ts"
|
|
58
|
+
"node_modules/@pi-unipi/input-shortcuts/src/index.ts",
|
|
59
|
+
"node_modules/@pi-unipi/cocoindex/index.ts"
|
|
59
60
|
],
|
|
60
61
|
"skills": [
|
|
61
62
|
"node_modules/@pi-unipi/workflow/skills",
|
|
@@ -69,7 +70,8 @@
|
|
|
69
70
|
"node_modules/@pi-unipi/notify/skills",
|
|
70
71
|
"node_modules/@pi-unipi/milestone/skills",
|
|
71
72
|
"node_modules/@pi-unipi/kanboard/skills",
|
|
72
|
-
"node_modules/@pi-unipi/updater/skills"
|
|
73
|
+
"node_modules/@pi-unipi/updater/skills",
|
|
74
|
+
"node_modules/@pi-unipi/cocoindex/skills"
|
|
73
75
|
]
|
|
74
76
|
},
|
|
75
77
|
"peerDependencies": {
|
|
@@ -97,7 +99,8 @@
|
|
|
97
99
|
"@pi-unipi/workflow": "*",
|
|
98
100
|
"@pi-unipi/footer": "*",
|
|
99
101
|
"@pi-unipi/updater": "*",
|
|
100
|
-
"@pi-unipi/input-shortcuts": "*"
|
|
102
|
+
"@pi-unipi/input-shortcuts": "*",
|
|
103
|
+
"@pi-unipi/cocoindex": "*"
|
|
101
104
|
},
|
|
102
105
|
"devDependencies": {
|
|
103
106
|
"@types/node": "^25.6.0",
|
|
@@ -12,6 +12,8 @@ Ask-user has no user commands. It's an agent tool package — the agent calls it
|
|
|
12
12
|
|
|
13
13
|
All workflow skills detect ask-user and use it for decision gates. Instead of the agent deciding on its own, it presents options and waits for your input. This happens naturally during brainstorm, plan, work, and other skills when the agent faces ambiguity.
|
|
14
14
|
|
|
15
|
+
For workflow handoffs, options can use `action: "new_session"` with a `prefill`. Selecting one opens a launcher where **Compact & run** queues the prefill after compaction (or a short fallback) and **Run directly** queues it immediately. If automatic queuing fails, the prefill is placed in the editor for you to submit manually.
|
|
16
|
+
|
|
15
17
|
The bundled skill guides the agent to use `ask_user` for high-stakes decisions — architecture choices, database selection, naming decisions, anything with lasting impact.
|
|
16
18
|
|
|
17
19
|
## Agent Tool
|
|
@@ -43,6 +45,34 @@ ask_user({
|
|
|
43
45
|
})
|
|
44
46
|
```
|
|
45
47
|
|
|
48
|
+
### `new_session` Handoffs
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
ask_user({
|
|
52
|
+
question: "Continue with implementation?",
|
|
53
|
+
options: [
|
|
54
|
+
{
|
|
55
|
+
label: "Proceed to work",
|
|
56
|
+
value: "work",
|
|
57
|
+
action: "new_session",
|
|
58
|
+
prefill: "/unipi:work specs:2026-05-06-feature-plan.md",
|
|
59
|
+
},
|
|
60
|
+
{ label: "Done for now", value: "done", action: "end_turn" },
|
|
61
|
+
],
|
|
62
|
+
allowFreeform: false,
|
|
63
|
+
})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
When the user chooses a `new_session` option:
|
|
67
|
+
|
|
68
|
+
| Launcher choice | Behavior |
|
|
69
|
+
|-----------------|----------|
|
|
70
|
+
| 🧹 Compact & run | Starts context compaction, returns immediately, then queues/submits the prefill as a follow-up message from the compaction callback or a short fallback timer |
|
|
71
|
+
| ▶ Run directly | Queues/submits the prefill immediately as a follow-up message |
|
|
72
|
+
| ✕ Cancel | Cancels the handoff; no message is queued |
|
|
73
|
+
|
|
74
|
+
The tool result is rendered as `queued compact → ...` or `queued direct → ...`. If automatic delivery fails, ask-user falls back to editor prefill and warns you to press Enter.
|
|
75
|
+
|
|
46
76
|
### Keyboard Controls
|
|
47
77
|
|
|
48
78
|
| Mode | Keys |
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* Uses ctx.ui.custom() callback pattern following question.ts/questionnaire.ts.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { Editor, type EditorTheme, Key, matchesKey, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@mariozechner/pi-tui";
|
|
8
|
+
import { Editor, type EditorTheme, Key, matchesKey, Text, truncateToWidth, type TUI, visibleWidth, wrapTextWithAnsi } from "@mariozechner/pi-tui";
|
|
9
|
+
import type { Theme, AgentToolResult } from "@mariozechner/pi-coding-agent";
|
|
9
10
|
import type { NormalizedOption, AskUserResponse } from "./types.js";
|
|
10
11
|
|
|
11
12
|
/** Result returned by the ask UI */
|
|
@@ -31,9 +32,9 @@ export function renderAskUI(params: {
|
|
|
31
32
|
allowFreeform: boolean;
|
|
32
33
|
timeout?: number;
|
|
33
34
|
}): (
|
|
34
|
-
tui:
|
|
35
|
-
theme:
|
|
36
|
-
kb:
|
|
35
|
+
tui: TUI,
|
|
36
|
+
theme: Theme,
|
|
37
|
+
kb: import("@mariozechner/pi-coding-agent").KeybindingsManager,
|
|
37
38
|
done: (result: AskUIResult | null) => void,
|
|
38
39
|
) => {
|
|
39
40
|
render: (width: number) => string[];
|
|
@@ -446,9 +447,23 @@ export function renderAskUI(params: {
|
|
|
446
447
|
function renderOptions(
|
|
447
448
|
lines: string[],
|
|
448
449
|
add: (s: string) => void,
|
|
449
|
-
theme:
|
|
450
|
+
theme: Theme,
|
|
450
451
|
width: number,
|
|
451
452
|
) {
|
|
453
|
+
const addWrappedOptionLine = (prefix: string, content: string) => {
|
|
454
|
+
const prefixWidth = visibleWidth(prefix);
|
|
455
|
+
const contentWidth = Math.max(1, width - prefixWidth);
|
|
456
|
+
const continuationPrefix = " ".repeat(prefixWidth);
|
|
457
|
+
const wrapped = wrapTextWithAnsi(content, contentWidth);
|
|
458
|
+
for (let lineIndex = 0; lineIndex < wrapped.length; lineIndex++) {
|
|
459
|
+
add((lineIndex === 0 ? prefix : continuationPrefix) + wrapped[lineIndex]);
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
const addWrappedDescription = (description: string) => {
|
|
464
|
+
addWrappedOptionLine(" ", theme.fg("muted", description));
|
|
465
|
+
};
|
|
466
|
+
|
|
452
467
|
for (let i = 0; i < displayOptions.length; i++) {
|
|
453
468
|
const opt = displayOptions[i];
|
|
454
469
|
const isSelected = i === optionIndex;
|
|
@@ -466,11 +481,9 @@ export function renderAskUI(params: {
|
|
|
466
481
|
label = `${opt.label}: "${customText}"`;
|
|
467
482
|
}
|
|
468
483
|
|
|
469
|
-
|
|
470
|
-
prefix +
|
|
471
|
-
|
|
472
|
-
" " +
|
|
473
|
-
theme.fg(isSelected ? "accent" : "text", label),
|
|
484
|
+
addWrappedOptionLine(
|
|
485
|
+
prefix + theme.fg(color, `[${box}]`) + " ",
|
|
486
|
+
theme.fg(isSelected ? "accent" : "text", label),
|
|
474
487
|
);
|
|
475
488
|
|
|
476
489
|
// Show edit indicator if in edit mode for this option
|
|
@@ -492,11 +505,9 @@ export function renderAskUI(params: {
|
|
|
492
505
|
label = `${opt.label}: "${optCustom}"`;
|
|
493
506
|
}
|
|
494
507
|
|
|
495
|
-
|
|
496
|
-
prefix +
|
|
497
|
-
|
|
498
|
-
" " +
|
|
499
|
-
theme.fg(isSelected ? "accent" : "text", label),
|
|
508
|
+
addWrappedOptionLine(
|
|
509
|
+
prefix + theme.fg(color, `[${box}]`) + " ",
|
|
510
|
+
theme.fg(isSelected ? "accent" : "text", label),
|
|
500
511
|
);
|
|
501
512
|
|
|
502
513
|
// Show edit indicator if in edit mode for this option
|
|
@@ -524,11 +535,11 @@ export function renderAskUI(params: {
|
|
|
524
535
|
label += theme.fg("dim", " ↗");
|
|
525
536
|
}
|
|
526
537
|
|
|
527
|
-
|
|
528
|
-
prefix
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
538
|
+
addWrappedOptionLine(
|
|
539
|
+
prefix,
|
|
540
|
+
isSelected
|
|
541
|
+
? theme.fg("accent", label)
|
|
542
|
+
: theme.fg("text", label),
|
|
532
543
|
);
|
|
533
544
|
|
|
534
545
|
// Show edit indicator if in edit mode for this option
|
|
@@ -542,7 +553,7 @@ export function renderAskUI(params: {
|
|
|
542
553
|
|
|
543
554
|
// Description
|
|
544
555
|
if (opt.description) {
|
|
545
|
-
|
|
556
|
+
addWrappedDescription(opt.description);
|
|
546
557
|
}
|
|
547
558
|
}
|
|
548
559
|
}
|
|
@@ -561,19 +572,48 @@ export function renderAskUI(params: {
|
|
|
561
572
|
* Create a renderCall function for the ask_user tool.
|
|
562
573
|
*/
|
|
563
574
|
export function createRenderCall() {
|
|
564
|
-
return (args:
|
|
565
|
-
const question = args.question || "";
|
|
566
|
-
const
|
|
575
|
+
return (args: Record<string, unknown>, theme: Theme, _context: unknown) => {
|
|
576
|
+
const question = (args.question as string) || "";
|
|
577
|
+
const context = (args.context as string | undefined) || "";
|
|
578
|
+
const options = Array.isArray(args.options)
|
|
579
|
+
? (args.options as Array<Record<string, unknown>>)
|
|
580
|
+
: [];
|
|
567
581
|
const mode = args.allowMultiple ? "multi-select" : "single-select";
|
|
582
|
+
const allowFreeform = args.allowFreeform !== false;
|
|
568
583
|
const count = options.length;
|
|
569
584
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
theme.fg("
|
|
585
|
+
const lines: string[] = [];
|
|
586
|
+
lines.push(
|
|
587
|
+
theme.fg("toolTitle", theme.bold("ask_user")) +
|
|
588
|
+
theme.fg("dim", ` (${count} option${count !== 1 ? "s" : ""}, ${mode}${allowFreeform ? ", freeform" : ""})`),
|
|
589
|
+
);
|
|
590
|
+
if (context) {
|
|
591
|
+
lines.push(theme.fg("muted", "Context: ") + theme.fg("text", context));
|
|
592
|
+
}
|
|
593
|
+
lines.push(theme.fg("muted", "Question: ") + theme.fg("text", question));
|
|
594
|
+
|
|
573
595
|
if (count > 0) {
|
|
574
|
-
|
|
596
|
+
lines.push(theme.fg("muted", "Options:"));
|
|
597
|
+
options.forEach((option, index) => {
|
|
598
|
+
const label = String(option.label ?? option.value ?? `Option ${index + 1}`);
|
|
599
|
+
const action = typeof option.action === "string" && option.action !== "select"
|
|
600
|
+
? theme.fg("dim", ` [${option.action}]`)
|
|
601
|
+
: "";
|
|
602
|
+
lines.push(
|
|
603
|
+
theme.fg("dim", ` ${index + 1}. `) +
|
|
604
|
+
theme.fg("text", label) +
|
|
605
|
+
action,
|
|
606
|
+
);
|
|
607
|
+
if (typeof option.description === "string" && option.description.trim()) {
|
|
608
|
+
lines.push(theme.fg("muted", ` ${option.description}`));
|
|
609
|
+
}
|
|
610
|
+
if (typeof option.prefill === "string" && option.prefill.trim()) {
|
|
611
|
+
lines.push(theme.fg("dim", ` prefill: ${option.prefill}`));
|
|
612
|
+
}
|
|
613
|
+
});
|
|
575
614
|
}
|
|
576
|
-
|
|
615
|
+
|
|
616
|
+
return new Text(lines.join("\n"), 0, 0);
|
|
577
617
|
};
|
|
578
618
|
}
|
|
579
619
|
|
|
@@ -581,14 +621,15 @@ export function createRenderCall() {
|
|
|
581
621
|
* Create a renderResult function for the ask_user tool.
|
|
582
622
|
*/
|
|
583
623
|
export function createRenderResult() {
|
|
584
|
-
return (result:
|
|
585
|
-
const details = result.details;
|
|
624
|
+
return (result: AgentToolResult<unknown>, _options: unknown, theme: Theme, _context: unknown) => {
|
|
625
|
+
const details = result.details as Record<string, unknown> | undefined;
|
|
586
626
|
if (!details) {
|
|
587
|
-
const
|
|
588
|
-
|
|
627
|
+
const content = result.content as unknown as Array<Record<string, unknown>> | undefined;
|
|
628
|
+
const text = content?.[0];
|
|
629
|
+
return new Text(text?.type === "text" ? (text.text as string) : "", 0, 0);
|
|
589
630
|
}
|
|
590
631
|
|
|
591
|
-
const response = details
|
|
632
|
+
const response = (details as { response: AskUserResponse }).response;
|
|
592
633
|
if (!response) {
|
|
593
634
|
return new Text(theme.fg("warning", "No response"), 0, 0);
|
|
594
635
|
}
|
|
@@ -639,19 +680,39 @@ export function createRenderResult() {
|
|
|
639
680
|
0,
|
|
640
681
|
);
|
|
641
682
|
case "new_session": {
|
|
642
|
-
const
|
|
643
|
-
if (
|
|
683
|
+
const prefill = response.prefill || "";
|
|
684
|
+
if (response.launchStatus === "editor_prefill") {
|
|
685
|
+
const label = response.launchedWith === "compact"
|
|
686
|
+
? "⚠ compact editor prefill → "
|
|
687
|
+
: "⚠ direct editor prefill → ";
|
|
688
|
+
return new Text(
|
|
689
|
+
theme.fg("warning", label) + theme.fg("accent", prefill),
|
|
690
|
+
0,
|
|
691
|
+
0,
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
if (response.launchStatus === "failed") {
|
|
695
|
+
const label = response.launchedWith === "compact"
|
|
696
|
+
? "handoff failed (compact) → "
|
|
697
|
+
: "handoff failed (direct) → ";
|
|
698
|
+
return new Text(
|
|
699
|
+
theme.fg("error", label) + theme.fg("accent", prefill),
|
|
700
|
+
0,
|
|
701
|
+
0,
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
if (response.launchedWith === "compact") {
|
|
644
705
|
return new Text(
|
|
645
|
-
theme.fg("success", "✓
|
|
646
|
-
theme.fg("accent",
|
|
706
|
+
theme.fg("success", "✓ queued compact → ") +
|
|
707
|
+
theme.fg("accent", prefill),
|
|
647
708
|
0,
|
|
648
709
|
0,
|
|
649
710
|
);
|
|
650
711
|
}
|
|
651
|
-
if (launchedWith === "direct") {
|
|
712
|
+
if (response.launchedWith === "direct") {
|
|
652
713
|
return new Text(
|
|
653
|
-
theme.fg("success", "✓
|
|
654
|
-
theme.fg("accent",
|
|
714
|
+
theme.fg("success", "✓ queued direct → ") +
|
|
715
|
+
theme.fg("accent", prefill),
|
|
655
716
|
0,
|
|
656
717
|
0,
|
|
657
718
|
);
|
|
@@ -659,7 +720,7 @@ export function createRenderResult() {
|
|
|
659
720
|
return new Text(
|
|
660
721
|
theme.fg("success", "✓ ") +
|
|
661
722
|
theme.fg("muted", "new session") +
|
|
662
|
-
(
|
|
723
|
+
(prefill ? theme.fg("accent", `: ${prefill}`) : ""),
|
|
663
724
|
0,
|
|
664
725
|
0,
|
|
665
726
|
);
|
|
@@ -23,7 +23,7 @@ export function registerAskUserCommands(pi: ExtensionAPI): void {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
ctx.ui.custom(
|
|
26
|
-
(tui
|
|
26
|
+
(tui, _theme, _keybindings, done) => {
|
|
27
27
|
const overlay = new AskUserSettingsOverlay();
|
|
28
28
|
overlay.onClose = () => done(undefined);
|
|
29
29
|
return {
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/ask-user — new_session handoff helpers
|
|
3
|
+
*
|
|
4
|
+
* Queues launcher prefill messages without waiting for LLM follow-up.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
8
|
+
import type { SessionLaunchReason, SessionLaunchStatus } from "./types.js";
|
|
9
|
+
|
|
10
|
+
/** Compact handoff fallback timer. Keeps the launcher from stalling if callbacks wait for the tool turn to finish. */
|
|
11
|
+
export const COMPACT_HANDOFF_FALLBACK_MS = 1500;
|
|
12
|
+
|
|
13
|
+
export interface HandoffResult {
|
|
14
|
+
status: SessionLaunchStatus;
|
|
15
|
+
reason: SessionLaunchReason;
|
|
16
|
+
prefill?: string;
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface NormalizedPrefill {
|
|
21
|
+
ok: true;
|
|
22
|
+
prefill: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface InvalidPrefill {
|
|
26
|
+
ok: false;
|
|
27
|
+
reason: "empty-prefill";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizePrefill(prefill: string | undefined): NormalizedPrefill | InvalidPrefill {
|
|
31
|
+
const trimmed = (prefill ?? "").trim();
|
|
32
|
+
if (!trimmed) {
|
|
33
|
+
return { ok: false, reason: "empty-prefill" };
|
|
34
|
+
}
|
|
35
|
+
return { ok: true, prefill: trimmed };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function errorMessage(error: unknown): string {
|
|
39
|
+
return error instanceof Error ? error.message : String(error);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function notify(ctx: ExtensionContext, message: string, level: "info" | "warning" | "error"): void {
|
|
43
|
+
try {
|
|
44
|
+
ctx.ui.notify(message, level);
|
|
45
|
+
} catch {
|
|
46
|
+
// Notifications are best-effort only; never let status UI break delivery.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function fallbackToEditor(
|
|
51
|
+
ctx: ExtensionContext,
|
|
52
|
+
prefill: string,
|
|
53
|
+
reason: SessionLaunchReason,
|
|
54
|
+
error: unknown,
|
|
55
|
+
): HandoffResult {
|
|
56
|
+
try {
|
|
57
|
+
ctx.ui.setEditorText(prefill);
|
|
58
|
+
notify(ctx, "Automatic handoff failed. The command was placed in the editor; press Enter to run it.", "warning");
|
|
59
|
+
return {
|
|
60
|
+
status: "editor_prefill",
|
|
61
|
+
reason,
|
|
62
|
+
prefill,
|
|
63
|
+
error: errorMessage(error),
|
|
64
|
+
};
|
|
65
|
+
} catch (editorError) {
|
|
66
|
+
notify(ctx, `Automatic handoff failed and editor fallback failed: ${errorMessage(editorError)}`, "error");
|
|
67
|
+
return {
|
|
68
|
+
status: "failed",
|
|
69
|
+
reason,
|
|
70
|
+
prefill,
|
|
71
|
+
error: `${errorMessage(error)}; editor fallback: ${errorMessage(editorError)}`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function deliverFollowUpMessage(
|
|
77
|
+
pi: ExtensionAPI,
|
|
78
|
+
ctx: ExtensionContext,
|
|
79
|
+
prefill: string,
|
|
80
|
+
reason: SessionLaunchReason,
|
|
81
|
+
): HandoffResult {
|
|
82
|
+
try {
|
|
83
|
+
pi.sendUserMessage(prefill, { deliverAs: "followUp" });
|
|
84
|
+
return { status: "queued", reason, prefill };
|
|
85
|
+
} catch (error) {
|
|
86
|
+
return fallbackToEditor(ctx, prefill, reason, error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function queueDirectHandoff(
|
|
91
|
+
pi: ExtensionAPI,
|
|
92
|
+
ctx: ExtensionContext,
|
|
93
|
+
prefill: string | undefined,
|
|
94
|
+
): HandoffResult {
|
|
95
|
+
const normalized = normalizePrefill(prefill);
|
|
96
|
+
if (!normalized.ok) {
|
|
97
|
+
return { status: "cancelled", reason: normalized.reason };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return deliverFollowUpMessage(pi, ctx, normalized.prefill, "direct");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function queueCompactHandoff(params: {
|
|
104
|
+
pi: ExtensionAPI;
|
|
105
|
+
ctx: ExtensionContext;
|
|
106
|
+
prefill: string | undefined;
|
|
107
|
+
customInstructions: string;
|
|
108
|
+
}): HandoffResult {
|
|
109
|
+
const { pi, ctx, customInstructions } = params;
|
|
110
|
+
const normalized = normalizePrefill(params.prefill);
|
|
111
|
+
if (!normalized.ok) {
|
|
112
|
+
return { status: "cancelled", reason: normalized.reason };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const { prefill } = normalized;
|
|
116
|
+
let delivered = false;
|
|
117
|
+
let fallbackTimer: ReturnType<typeof setTimeout> | undefined;
|
|
118
|
+
|
|
119
|
+
const clearFallbackTimer = () => {
|
|
120
|
+
if (fallbackTimer) {
|
|
121
|
+
clearTimeout(fallbackTimer);
|
|
122
|
+
fallbackTimer = undefined;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const deliverOnce = (reason: SessionLaunchReason): HandoffResult | undefined => {
|
|
127
|
+
if (delivered) {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
delivered = true;
|
|
131
|
+
clearFallbackTimer();
|
|
132
|
+
if (reason === "fallback-timeout") {
|
|
133
|
+
notify(ctx, "Compaction is still running; queued the selected handoff command via fallback.", "info");
|
|
134
|
+
}
|
|
135
|
+
return deliverFollowUpMessage(pi, ctx, prefill, reason);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
fallbackTimer = setTimeout(() => {
|
|
139
|
+
deliverOnce("fallback-timeout");
|
|
140
|
+
}, COMPACT_HANDOFF_FALLBACK_MS);
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
ctx.compact({
|
|
144
|
+
customInstructions,
|
|
145
|
+
onComplete: () => {
|
|
146
|
+
deliverOnce("compacted");
|
|
147
|
+
},
|
|
148
|
+
onError: (error) => {
|
|
149
|
+
notify(ctx, `Compaction reported an error; continuing handoff anyway: ${errorMessage(error)}`, "warning");
|
|
150
|
+
deliverOnce("compaction-error");
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
} catch (error) {
|
|
154
|
+
notify(ctx, `Could not start compaction; running selected handoff directly: ${errorMessage(error)}`, "warning");
|
|
155
|
+
return deliverOnce("compact-start-failed") ?? {
|
|
156
|
+
status: "queued",
|
|
157
|
+
reason: "compact-start-failed",
|
|
158
|
+
prefill,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return { status: "scheduled", reason: "compact-started", prefill };
|
|
163
|
+
}
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* Offers Compact & run, Run directly, or Cancel.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { Key, matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
8
|
+
import { Key, matchesKey, truncateToWidth, type TUI, visibleWidth } from "@mariozechner/pi-tui";
|
|
9
|
+
import type { Theme, KeybindingsManager } from "@mariozechner/pi-coding-agent";
|
|
9
10
|
import type { SessionLauncherResult } from "./types.js";
|
|
10
11
|
|
|
11
12
|
/** Launcher option definition */
|
|
@@ -30,9 +31,9 @@ const OPTIONS: LauncherOption[] = [
|
|
|
30
31
|
export function renderLauncherUI(params: {
|
|
31
32
|
prefill: string;
|
|
32
33
|
}): (
|
|
33
|
-
tui:
|
|
34
|
-
theme:
|
|
35
|
-
kb:
|
|
34
|
+
tui: TUI,
|
|
35
|
+
theme: Theme,
|
|
36
|
+
kb: KeybindingsManager,
|
|
36
37
|
done: (result: SessionLauncherResult | null) => void,
|
|
37
38
|
) => {
|
|
38
39
|
render: (width: number) => string[];
|
|
@@ -56,7 +56,7 @@ Use the `ask_user` tool to collect structured input from the user.
|
|
|
56
56
|
| `"select"` | Normal selection (default). Returns immediately. |
|
|
57
57
|
| `"input"` | Enters text input mode. Returns `combined` response with selection + text. |
|
|
58
58
|
| `"end_turn"` | Signals end of agent turn. Returns `end_turn` response kind. |
|
|
59
|
-
| `"new_session"` | Starts a
|
|
59
|
+
| `"new_session"` | Starts a handoff. Returns `new_session` response kind with optional `prefill`. Shows a launcher overlay offering **Compact & run** (compact first, then queue/submit the prefill) or **Run directly** (queue/submit immediately). The current LLM follow-up is aborted after a successful queue or editor fallback. |
|
|
60
60
|
|
|
61
61
|
## Examples
|
|
62
62
|
|
|
@@ -149,7 +149,7 @@ ask_user({
|
|
|
149
149
|
- "Looks good" returns immediately with selection
|
|
150
150
|
- "I want changes" enters text input mode for the user to explain
|
|
151
151
|
- "Done for now" signals the agent to end its turn
|
|
152
|
-
- "Start fresh"
|
|
152
|
+
- "Start fresh" opens the launcher; **Compact & run** or **Run directly** queues the prefill message automatically
|
|
153
153
|
|
|
154
154
|
## Session Launcher
|
|
155
155
|
|
|
@@ -157,8 +157,8 @@ When a user selects a `new_session` option, a secondary launcher overlay appears
|
|
|
157
157
|
|
|
158
158
|
| Choice | Behavior |
|
|
159
159
|
|--------|----------|
|
|
160
|
-
| 🧹 Compact & run |
|
|
161
|
-
| ▶ Run directly |
|
|
162
|
-
| ✕ Cancel | Cancels the session launch |
|
|
160
|
+
| 🧹 Compact & run | Starts `ctx.compact()` without waiting in the tool spinner, then queues/submits the prefill as a follow-up message after compaction or a short fallback timer |
|
|
161
|
+
| ▶ Run directly | Queues/submits the prefill immediately as a follow-up message, without compaction |
|
|
162
|
+
| ✕ Cancel | Cancels the session launch; no prefill is queued |
|
|
163
163
|
|
|
164
|
-
This two-step flow lets the user manage context window usage before starting a new task.
|
|
164
|
+
The prefill can be a slash command (for example `/unipi:work specs:...`) or any non-empty message. If automatic delivery fails, ask_user places the prefill in the editor and warns the user to press Enter. This two-step flow lets the user manage context window usage before starting a new task while avoiding unnecessary LLM follow-up in the old session.
|