@pi-unipi/unipi 0.1.17 → 0.1.18
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 +17 -17
- package/package.json +1 -1
- package/packages/ask-user/ask-ui.ts +19 -1
- package/packages/ask-user/launcher-ui.ts +142 -0
- package/packages/ask-user/skills/ask-user/SKILL.md +13 -1
- package/packages/ask-user/tools.ts +53 -1
- package/packages/ask-user/types.ts +6 -0
- package/packages/autocomplete/src/__tests__/provider.sorting.test.ts +423 -0
- package/packages/autocomplete/src/provider.ts +16 -3
- package/packages/autocomplete/src/sorting.ts +81 -0
- package/packages/compactor/src/index.ts +34 -15
- package/packages/compactor/src/info-screen.ts +3 -3
- package/packages/compactor/src/session/analytics.ts +8 -1
- package/packages/compactor/src/tools/ctx-stats.ts +26 -2
- package/packages/core/constants.ts +1 -0
- package/packages/core/utils.ts +30 -0
- package/packages/footer/src/commands.ts +36 -121
- package/packages/footer/src/config.ts +4 -0
- package/packages/footer/src/help.ts +160 -0
- package/packages/footer/src/index.ts +25 -1
- package/packages/footer/src/presets.ts +40 -31
- package/packages/footer/src/rendering/icons.ts +38 -20
- package/packages/footer/src/rendering/renderer.ts +195 -76
- package/packages/footer/src/rendering/theme.ts +56 -29
- package/packages/footer/src/segments/compactor.ts +21 -10
- package/packages/footer/src/segments/core.ts +122 -14
- package/packages/footer/src/segments/kanboard.ts +24 -8
- package/packages/footer/src/segments/mcp.ts +25 -8
- package/packages/footer/src/segments/memory.ts +8 -4
- package/packages/footer/src/segments/notify.ts +16 -5
- package/packages/footer/src/segments/ralph.ts +24 -7
- package/packages/footer/src/segments/status-ext.ts +1 -1
- package/packages/footer/src/segments/workflow.ts +39 -17
- package/packages/footer/src/tps-tracker.ts +204 -0
- package/packages/footer/src/tui/settings-tui.ts +228 -57
- package/packages/footer/src/types.ts +51 -12
- package/packages/updater/src/checker.ts +4 -4
- package/packages/updater/src/installer.ts +4 -7
- package/packages/updater/src/readme.ts +7 -8
- package/packages/updater/src/tui/changelog-overlay.ts +4 -3
package/README.md
CHANGED
|
@@ -9,39 +9,39 @@ pi install npm:@pi-unipi/unipi
|
|
|
9
9
|
|
|
10
10
|
## What You Get
|
|
11
11
|
|
|
12
|
-
**Workflow** — 20 commands that take ideas to shipped code. Brainstorm, plan, execute in worktrees, review, consolidate. The agent follows skill files step by step.
|
|
12
|
+
**[Workflow](./packages/workflow/README.md)** — 20 commands that take ideas to shipped code. Brainstorm, plan, execute in worktrees, review, consolidate. The agent follows skill files step by step.
|
|
13
13
|
|
|
14
|
-
**Ralph** — Long-running loops that persist across sessions. Start a task, iterate through checklist items, resume after crashes. Progress tracked, state saved.
|
|
14
|
+
**[Ralph](./packages/ralph/README.md)** — Long-running loops that persist across sessions. Start a task, iterate through checklist items, resume after crashes. Progress tracked, state saved.
|
|
15
15
|
|
|
16
|
-
**Memory** — SQLite + vector search stores facts, preferences, and decisions. Project-scoped and global. The agent remembers what you told it last week.
|
|
16
|
+
**[Memory](./packages/memory/README.md)** — SQLite + vector search stores facts, preferences, and decisions. Project-scoped and global. The agent remembers what you told it last week.
|
|
17
17
|
|
|
18
|
-
**Compactor** — Zero-LLM context engine. 6-stage pipeline hits 95%+ token reduction at zero API cost. Session continuity, sandbox execution, FTS5 search.
|
|
18
|
+
**[Compactor](./packages/compactor/README.md)** — Zero-LLM context engine. 6-stage pipeline hits 95%+ token reduction at zero API cost. Session continuity, sandbox execution, FTS5 search.
|
|
19
19
|
|
|
20
|
-
**Subagents** — Parallel execution with file locking. Spawn background agents to research, fix, or build while the main agent keeps going.
|
|
20
|
+
**[Subagents](./packages/subagents/README.md)** — Parallel execution with file locking. Spawn background agents to research, fix, or build while the main agent keeps going.
|
|
21
21
|
|
|
22
|
-
**Web API** — Web search, page reading, content summarization. Smart-fetch engine with browser-grade TLS fingerprinting — free, no API key. Paid providers as fallbacks.
|
|
22
|
+
**[Web API](./packages/web-api/README.md)** — Web search, page reading, content summarization. Smart-fetch engine with browser-grade TLS fingerprinting — free, no API key. Paid providers as fallbacks.
|
|
23
23
|
|
|
24
|
-
**MCP** — Browse 7,800+ MCP servers, add them interactively. Tools from servers register automatically as Pi tools.
|
|
24
|
+
**[MCP](./packages/mcp/README.md)** — Browse 7,800+ MCP servers, add them interactively. Tools from servers register automatically as Pi tools.
|
|
25
25
|
|
|
26
|
-
**Notify** — Push notifications to native OS, Gotify, Telegram, or ntfy. Per-event platform routing. Configure once, get alerts everywhere.
|
|
26
|
+
**[Notify](./packages/notify/README.md)** — Push notifications to native OS, Gotify, Telegram, or ntfy. Per-event platform routing. Configure once, get alerts everywhere.
|
|
27
27
|
|
|
28
|
-
**Footer** — Persistent status bar showing live stats from every package. Responsive layout, presets, per-segment toggling.
|
|
28
|
+
**[Footer](./packages/footer/README.md)** — Persistent status bar showing live stats from every package. Responsive layout, presets, per-segment toggling.
|
|
29
29
|
|
|
30
|
-
**BTW** — Side conversations that run in parallel. Ask questions without interrupting the main agent.
|
|
30
|
+
**[BTW](./packages/btw/README.md)** — Side conversations that run in parallel. Ask questions without interrupting the main agent.
|
|
31
31
|
|
|
32
|
-
**Ask User** — Structured input for decision gates. Single-select, multi-select, freeform. The agent asks instead of guessing.
|
|
32
|
+
**[Ask User](./packages/ask-user/README.md)** — Structured input for decision gates. Single-select, multi-select, freeform. The agent asks instead of guessing.
|
|
33
33
|
|
|
34
|
-
**Milestone** — Track project goals across workflow cycles. MILESTONES.md stays in sync with specs, plans, and completed work.
|
|
34
|
+
**[Milestone](./packages/milestone/README.md)** — Track project goals across workflow cycles. MILESTONES.md stays in sync with specs, plans, and completed work.
|
|
35
35
|
|
|
36
|
-
**Kanboard** — Web UI and TUI overlay for kanban boards. Parses all workflow documents into cards with progress indicators.
|
|
36
|
+
**[Kanboard](./packages/kanboard/README.md)** — Web UI and TUI overlay for kanban boards. Parses all workflow documents into cards with progress indicators.
|
|
37
37
|
|
|
38
|
-
**Info Screen** — Dashboard overlay showing module status, tools, and custom data groups.
|
|
38
|
+
**[Info Screen](./packages/info-screen/README.md)** — Dashboard overlay showing module status, tools, and custom data groups.
|
|
39
39
|
|
|
40
|
-
**Utility** — Environment info, diagnostics, cleanup, name badge, and Shiki-powered diff rendering.
|
|
40
|
+
**[Utility](./packages/utility/README.md)** — Environment info, diagnostics, cleanup, name badge, and Shiki-powered diff rendering.
|
|
41
41
|
|
|
42
|
-
**Updater** — Checks npm for new versions on session start. Changelog browser and readme browser in TUI overlays.
|
|
42
|
+
**[Updater](./packages/updater/README.md)** — Checks npm for new versions on session start. Changelog browser and readme browser in TUI overlays.
|
|
43
43
|
|
|
44
|
-
**Input Shortcuts** — Keyboard shortcuts via vim-style chord overlay. Stash/restore, undo/redo, clipboard, thinking toggle.
|
|
44
|
+
**[Input Shortcuts](./packages/input-shortcuts/README.md)** — Keyboard shortcuts via vim-style chord overlay. Stash/restore, undo/redo, clipboard, thinking toggle.
|
|
45
45
|
|
|
46
46
|
## Architecture
|
|
47
47
|
|
package/package.json
CHANGED
|
@@ -638,7 +638,24 @@ export function createRenderResult() {
|
|
|
638
638
|
0,
|
|
639
639
|
0,
|
|
640
640
|
);
|
|
641
|
-
case "new_session":
|
|
641
|
+
case "new_session": {
|
|
642
|
+
const launchedWith = (response as any).launchedWith;
|
|
643
|
+
if (launchedWith === "compact") {
|
|
644
|
+
return new Text(
|
|
645
|
+
theme.fg("success", "✓ compacted → ") +
|
|
646
|
+
theme.fg("accent", response.prefill || ""),
|
|
647
|
+
0,
|
|
648
|
+
0,
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
if (launchedWith === "direct") {
|
|
652
|
+
return new Text(
|
|
653
|
+
theme.fg("success", "✓ running → ") +
|
|
654
|
+
theme.fg("accent", response.prefill || ""),
|
|
655
|
+
0,
|
|
656
|
+
0,
|
|
657
|
+
);
|
|
658
|
+
}
|
|
642
659
|
return new Text(
|
|
643
660
|
theme.fg("success", "✓ ") +
|
|
644
661
|
theme.fg("muted", "new session") +
|
|
@@ -646,6 +663,7 @@ export function createRenderResult() {
|
|
|
646
663
|
0,
|
|
647
664
|
0,
|
|
648
665
|
);
|
|
666
|
+
}
|
|
649
667
|
default:
|
|
650
668
|
return new Text(
|
|
651
669
|
theme.fg("text", JSON.stringify(response)),
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/ask-user — Session Launcher TUI
|
|
3
|
+
*
|
|
4
|
+
* Secondary overlay shown when user selects a new_session option.
|
|
5
|
+
* Offers Compact & run, Run directly, or Cancel.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Key, matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
9
|
+
import type { SessionLauncherResult } from "./types.js";
|
|
10
|
+
|
|
11
|
+
/** Launcher option definition */
|
|
12
|
+
interface LauncherOption {
|
|
13
|
+
label: string;
|
|
14
|
+
icon: string;
|
|
15
|
+
action: SessionLauncherResult["action"];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const OPTIONS: LauncherOption[] = [
|
|
19
|
+
{ label: "Compact & run", icon: "🧹", action: "compact" },
|
|
20
|
+
{ label: "Run directly", icon: "▶", action: "direct" },
|
|
21
|
+
{ label: "Cancel", icon: "✕", action: "cancel" },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Render the session launcher UI.
|
|
26
|
+
*
|
|
27
|
+
* Simple single-select picker with 3 fixed options.
|
|
28
|
+
* No editor, no timeout, no multi-select.
|
|
29
|
+
*/
|
|
30
|
+
export function renderLauncherUI(params: {
|
|
31
|
+
prefill: string;
|
|
32
|
+
}): (
|
|
33
|
+
tui: any,
|
|
34
|
+
theme: any,
|
|
35
|
+
kb: any,
|
|
36
|
+
done: (result: SessionLauncherResult | null) => void,
|
|
37
|
+
) => {
|
|
38
|
+
render: (width: number) => string[];
|
|
39
|
+
invalidate: () => void;
|
|
40
|
+
handleInput: (data: string) => void;
|
|
41
|
+
} {
|
|
42
|
+
return (_tui, theme, _kb, done) => {
|
|
43
|
+
const { prefill } = params;
|
|
44
|
+
|
|
45
|
+
// State
|
|
46
|
+
let optionIndex = 0;
|
|
47
|
+
let cachedLines: string[] | undefined;
|
|
48
|
+
|
|
49
|
+
function refresh() {
|
|
50
|
+
cachedLines = undefined;
|
|
51
|
+
_tui.requestRender();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function handleInput(data: string) {
|
|
55
|
+
// Navigation
|
|
56
|
+
if (matchesKey(data, Key.up)) {
|
|
57
|
+
optionIndex = Math.max(0, optionIndex - 1);
|
|
58
|
+
refresh();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (matchesKey(data, Key.down)) {
|
|
62
|
+
optionIndex = Math.min(OPTIONS.length - 1, optionIndex + 1);
|
|
63
|
+
refresh();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Enter: select
|
|
68
|
+
if (matchesKey(data, Key.enter)) {
|
|
69
|
+
const opt = OPTIONS[optionIndex];
|
|
70
|
+
done({ action: opt.action, prefill });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Escape: cancel
|
|
75
|
+
if (matchesKey(data, Key.escape)) {
|
|
76
|
+
done(null);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function render(width: number): string[] {
|
|
82
|
+
if (cachedLines) return cachedLines;
|
|
83
|
+
|
|
84
|
+
const lines: string[] = [];
|
|
85
|
+
const innerWidth = Math.max(40, width - 2);
|
|
86
|
+
const border = (s: string) => theme.fg("accent", s);
|
|
87
|
+
|
|
88
|
+
function padVisible(content: string, targetWidth: number): string {
|
|
89
|
+
const vw = visibleWidth(content);
|
|
90
|
+
const pad = Math.max(0, targetWidth - vw);
|
|
91
|
+
return content + " ".repeat(pad);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const add = (s: string) =>
|
|
95
|
+
lines.push(
|
|
96
|
+
border("│") +
|
|
97
|
+
padVisible(truncateToWidth(s, innerWidth), innerWidth) +
|
|
98
|
+
border("│"),
|
|
99
|
+
);
|
|
100
|
+
const addEmpty = () =>
|
|
101
|
+
lines.push(border("│") + " ".repeat(innerWidth) + border("│"));
|
|
102
|
+
|
|
103
|
+
// Top border
|
|
104
|
+
lines.push(border(`╭${"─".repeat(innerWidth)}╮`));
|
|
105
|
+
|
|
106
|
+
// Header: show prefill command (truncated)
|
|
107
|
+
const headerPrefix = " 🚀 ";
|
|
108
|
+
const maxPrefillWidth = innerWidth - headerPrefix.length - 1;
|
|
109
|
+
const truncatedPrefill = truncateToWidth(prefill || "(no command)", maxPrefillWidth);
|
|
110
|
+
add(theme.fg("accent", headerPrefix) + theme.fg("text", truncatedPrefill));
|
|
111
|
+
addEmpty();
|
|
112
|
+
|
|
113
|
+
// Options
|
|
114
|
+
for (let i = 0; i < OPTIONS.length; i++) {
|
|
115
|
+
const opt = OPTIONS[i];
|
|
116
|
+
const isSelected = i === optionIndex;
|
|
117
|
+
const prefix = isSelected ? theme.fg("accent", "> ") : " ";
|
|
118
|
+
const label = `${opt.icon} ${opt.label}`;
|
|
119
|
+
const color = isSelected ? "accent" : "text";
|
|
120
|
+
add(prefix + theme.fg(color, label));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Footer hint
|
|
124
|
+
addEmpty();
|
|
125
|
+
add(theme.fg("dim", " ↑↓ navigate • Enter select • Esc cancel"));
|
|
126
|
+
|
|
127
|
+
// Bottom border
|
|
128
|
+
lines.push(border(`╰${"─".repeat(innerWidth)}╯`));
|
|
129
|
+
|
|
130
|
+
cachedLines = lines;
|
|
131
|
+
return lines;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
render,
|
|
136
|
+
invalidate: () => {
|
|
137
|
+
cachedLines = undefined;
|
|
138
|
+
},
|
|
139
|
+
handleInput,
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -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 new session. Returns `new_session` response kind with optional `prefill`. |
|
|
59
|
+
| `"new_session"` | Starts a new session. Returns `new_session` response kind with optional `prefill`. Shows a launcher overlay offering **Compact & run** (compacts context first) or **Run directly**. |
|
|
60
60
|
|
|
61
61
|
## Examples
|
|
62
62
|
|
|
@@ -150,3 +150,15 @@ ask_user({
|
|
|
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
152
|
- "Start fresh" starts a new session with the prefill message
|
|
153
|
+
|
|
154
|
+
## Session Launcher
|
|
155
|
+
|
|
156
|
+
When a user selects a `new_session` option, a secondary launcher overlay appears with three choices:
|
|
157
|
+
|
|
158
|
+
| Choice | Behavior |
|
|
159
|
+
|--------|----------|
|
|
160
|
+
| 🧹 Compact & run | Compacts current context (via `ctx.compact()`), then returns the prefill command to the LLM |
|
|
161
|
+
| ▶ Run directly | Returns the prefill command to the LLM without compaction |
|
|
162
|
+
| ✕ Cancel | Cancels the session launch |
|
|
163
|
+
|
|
164
|
+
This two-step flow lets the user manage context window usage before starting a new task.
|
|
@@ -11,8 +11,9 @@ import {
|
|
|
11
11
|
UNIPI_EVENTS,
|
|
12
12
|
emitEvent,
|
|
13
13
|
} from "@pi-unipi/core";
|
|
14
|
-
import type { NormalizedOption, AskUserResponse } from "./types.js";
|
|
14
|
+
import type { NormalizedOption, AskUserResponse, SessionLauncherResult } from "./types.js";
|
|
15
15
|
import { renderAskUI, createRenderCall, createRenderResult } from "./ask-ui.js";
|
|
16
|
+
import { renderLauncherUI } from "./launcher-ui.js";
|
|
16
17
|
import { getAskUserSettings } from "./config.js";
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -331,6 +332,57 @@ export function registerAskUserTools(pi: ExtensionAPI): void {
|
|
|
331
332
|
contentText = "No response";
|
|
332
333
|
}
|
|
333
334
|
|
|
335
|
+
// Session launcher intercept: when user selects new_session, offer compact/direct/cancel
|
|
336
|
+
if (response.kind === "new_session") {
|
|
337
|
+
const prefill = response.prefill || "";
|
|
338
|
+
const launcherResult = await ctx.ui.custom<SessionLauncherResult | null>(
|
|
339
|
+
renderLauncherUI({ prefill }),
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
if (!launcherResult || launcherResult.action === "cancel") {
|
|
343
|
+
return {
|
|
344
|
+
content: [{ type: "text", text: "User cancelled the session launch" }],
|
|
345
|
+
details: {
|
|
346
|
+
question,
|
|
347
|
+
options: normalizedOptions.map((o) => o.label),
|
|
348
|
+
response: {
|
|
349
|
+
kind: "cancelled",
|
|
350
|
+
comment: "Session launcher cancelled",
|
|
351
|
+
} as AskUserResponse,
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (launcherResult.action === "compact") {
|
|
357
|
+
try {
|
|
358
|
+
await new Promise<void>((resolve, reject) => {
|
|
359
|
+
ctx.compact({
|
|
360
|
+
customInstructions: `Preparing for new task. Summarize previous work concisely, preserving only what's essential for: ${prefill}`,
|
|
361
|
+
onComplete: () => resolve(),
|
|
362
|
+
onError: (err) => reject(err),
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
} catch (err) {
|
|
366
|
+
// Compaction failure shouldn't block the session launch — continue anyway
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const actionLabel = launcherResult.action === "compact" ? "compacted" : "running";
|
|
371
|
+
contentText = `User chose to proceed (${actionLabel}): ${prefill}`;
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
content: [{ type: "text", text: contentText }],
|
|
375
|
+
details: {
|
|
376
|
+
question,
|
|
377
|
+
options: normalizedOptions.map((o) => o.label),
|
|
378
|
+
response: {
|
|
379
|
+
...response,
|
|
380
|
+
launchedWith: launcherResult.action,
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
334
386
|
return {
|
|
335
387
|
content: [{ type: "text", text: contentText }],
|
|
336
388
|
details: {
|
|
@@ -54,6 +54,12 @@ export interface AskUserResponse {
|
|
|
54
54
|
comment?: string;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/** Result from the session launcher UI */
|
|
58
|
+
export interface SessionLauncherResult {
|
|
59
|
+
action: "compact" | "direct" | "cancel";
|
|
60
|
+
prefill: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
57
63
|
/** Normalized option with resolved value */
|
|
58
64
|
export interface NormalizedOption {
|
|
59
65
|
label: string;
|