@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.
Files changed (40) hide show
  1. package/README.md +17 -17
  2. package/package.json +1 -1
  3. package/packages/ask-user/ask-ui.ts +19 -1
  4. package/packages/ask-user/launcher-ui.ts +142 -0
  5. package/packages/ask-user/skills/ask-user/SKILL.md +13 -1
  6. package/packages/ask-user/tools.ts +53 -1
  7. package/packages/ask-user/types.ts +6 -0
  8. package/packages/autocomplete/src/__tests__/provider.sorting.test.ts +423 -0
  9. package/packages/autocomplete/src/provider.ts +16 -3
  10. package/packages/autocomplete/src/sorting.ts +81 -0
  11. package/packages/compactor/src/index.ts +34 -15
  12. package/packages/compactor/src/info-screen.ts +3 -3
  13. package/packages/compactor/src/session/analytics.ts +8 -1
  14. package/packages/compactor/src/tools/ctx-stats.ts +26 -2
  15. package/packages/core/constants.ts +1 -0
  16. package/packages/core/utils.ts +30 -0
  17. package/packages/footer/src/commands.ts +36 -121
  18. package/packages/footer/src/config.ts +4 -0
  19. package/packages/footer/src/help.ts +160 -0
  20. package/packages/footer/src/index.ts +25 -1
  21. package/packages/footer/src/presets.ts +40 -31
  22. package/packages/footer/src/rendering/icons.ts +38 -20
  23. package/packages/footer/src/rendering/renderer.ts +195 -76
  24. package/packages/footer/src/rendering/theme.ts +56 -29
  25. package/packages/footer/src/segments/compactor.ts +21 -10
  26. package/packages/footer/src/segments/core.ts +122 -14
  27. package/packages/footer/src/segments/kanboard.ts +24 -8
  28. package/packages/footer/src/segments/mcp.ts +25 -8
  29. package/packages/footer/src/segments/memory.ts +8 -4
  30. package/packages/footer/src/segments/notify.ts +16 -5
  31. package/packages/footer/src/segments/ralph.ts +24 -7
  32. package/packages/footer/src/segments/status-ext.ts +1 -1
  33. package/packages/footer/src/segments/workflow.ts +39 -17
  34. package/packages/footer/src/tps-tracker.ts +204 -0
  35. package/packages/footer/src/tui/settings-tui.ts +228 -57
  36. package/packages/footer/src/types.ts +51 -12
  37. package/packages/updater/src/checker.ts +4 -4
  38. package/packages/updater/src/installer.ts +4 -7
  39. package/packages/updater/src/readme.ts +7 -8
  40. 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/unipi",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "description": "All-in-one extension suite for Pi coding agent",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -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;