@pi-unipi/unipi 3.0.0-alpha.21 → 3.0.0-alpha.22

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 (59) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +193 -158
  3. package/package.json +21 -21
  4. package/packages/ask-user/README.md +82 -39
  5. package/packages/ask-user/index.ts +17 -2
  6. package/packages/ask-user/package.json +2 -2
  7. package/packages/ask-user/tools.ts +40 -6
  8. package/packages/autocomplete/README.md +46 -21
  9. package/packages/autocomplete/package.json +1 -1
  10. package/packages/background-tasks/README.md +69 -67
  11. package/packages/background-tasks/package.json +2 -2
  12. package/packages/btw/README.md +59 -13
  13. package/packages/btw/package.json +2 -2
  14. package/packages/compactor/README.md +78 -61
  15. package/packages/compactor/package.json +3 -3
  16. package/packages/core/README.md +98 -94
  17. package/packages/core/package.json +1 -1
  18. package/packages/footer/README.md +73 -116
  19. package/packages/footer/package.json +3 -3
  20. package/packages/fusion/README.md +83 -150
  21. package/packages/fusion/package.json +4 -4
  22. package/packages/info-screen/README.md +81 -69
  23. package/packages/info-screen/package.json +2 -2
  24. package/packages/input-shortcuts/README.md +47 -47
  25. package/packages/input-shortcuts/package.json +2 -2
  26. package/packages/kanboard/README.md +98 -182
  27. package/packages/kanboard/package.json +7 -7
  28. package/packages/long-horizon/README.md +98 -16
  29. package/packages/long-horizon/package.json +2 -2
  30. package/packages/long-horizon/src/gate.ts +9 -0
  31. package/packages/mcp/README.md +74 -62
  32. package/packages/mcp/package.json +2 -2
  33. package/packages/memory/README.md +93 -118
  34. package/packages/memory/package.json +3 -3
  35. package/packages/notify/README.md +85 -120
  36. package/packages/notify/package.json +2 -2
  37. package/packages/skill-registry/README.md +78 -44
  38. package/packages/skill-registry/package.json +2 -2
  39. package/packages/subagents/README.md +77 -31
  40. package/packages/subagents/package.json +3 -3
  41. package/packages/unipi/bundled.js +1210 -1099
  42. package/packages/updater/README.md +53 -55
  43. package/packages/updater/package.json +2 -2
  44. package/packages/utility/README.md +87 -78
  45. package/packages/utility/package.json +2 -2
  46. package/packages/utility/src/index.ts +12 -4
  47. package/packages/utility/src/render/delegated.ts +3 -0
  48. package/packages/utility/src/render/harness.ts +11 -2
  49. package/packages/utility/src/render/images.ts +51 -0
  50. package/packages/utility/src/render/reply-bg.ts +30 -2
  51. package/packages/utility/src/render/simple.ts +3 -0
  52. package/packages/utility/src/render/spacing.ts +13 -1
  53. package/packages/utility/src/settings.ts +2 -2
  54. package/packages/watchdog/README.md +66 -41
  55. package/packages/watchdog/package.json +3 -3
  56. package/packages/web-api/README.md +78 -126
  57. package/packages/web-api/package.json +2 -2
  58. package/packages/workflow/README.md +80 -103
  59. package/packages/workflow/package.json +2 -2
@@ -1,72 +1,115 @@
1
- # @pi-unipi/ask-user
1
+ # Ask User
2
2
 
3
- The `ask_user` tool (off by default — enable it in `/unipi:settings` → Ask User): when the agent needs a decision from you, it asks 1–4 multiple-choice questions in one dialog and waits. Modelled on Devin's question UI.
3
+ Let the agent stop and ask you multiple-choice questions when it needs a decision.
4
4
 
5
- ```
5
+ `@pi-unipi/ask-user` · part of [UniPi](../../README.md)
6
+
7
+ ## What it does
8
+
9
+ - Adds the `ask_user` tool. The tool is **off** by default.
10
+ - Shows 1 to 4 questions in one dialog. Each question has 2 to 4 options.
11
+ - Adds an "Other (type your own)" option, so you can type a free answer. You can paste or drop images and files into this answer.
12
+ - Sends a question that you do not answer as "skipped". A skipped question does not stop the dialog.
13
+ - Shows each answer in the transcript as a short tree.
14
+ - Can send a notification while it waits for you, through [Notify](../notify/README.md).
15
+
16
+ ```text
6
17
  ── Planet ✓ · Foods 2 · Last book ─────────────────────────────────────
7
18
  What was the last book you read?
8
19
  Can't remember
9
20
  Don't read books
10
21
  ❭ Other (type your own)
11
22
  └ Dune, cover: [Image #1]
12
- [Image #1] red-cube.png · 570 KB
13
23
  ───────────────────────────────────────────────────────────────────────
14
24
  ↑↓ navigate · ↵ select · ctrl+v image · ←→ switch question · esc cancel
15
25
  ? Not ready to answer, help me out!
16
26
  ```
17
27
 
18
- ## Answering
28
+ ## Quick start
29
+
30
+ UniPi installs this package:
31
+
32
+ ```bash
33
+ pi install npm:@pi-unipi/unipi
34
+ ```
35
+
36
+ To install this package alone:
37
+
38
+ ```bash
39
+ pi install npm:@pi-unipi/ask-user
40
+ ```
41
+
42
+ To turn on the tool:
43
+
44
+ 1. Open `/unipi:settings`.
45
+ 2. Select the **Ask User** group.
46
+ 3. Set **Enable ask_user** to on.
47
+
48
+ When the tool is off, it tells the agent to ask in its reply.
49
+
50
+ ## Answer the questions
19
51
 
20
- | Key | Action |
21
- |-----|--------|
22
- | `↑` `↓` | Move between options |
23
- | `1`–`9` | Pick that option (single choice: picks and moves on; multi-select: toggles) |
24
- | `space` | Toggle (multi-select) |
25
- | `enter` | Select and go to the next question; on the last one, send everything |
26
- | `←` `→`, `tab` | Switch question (in a non-empty "Other" they move the text cursor) |
27
- | type | On "Other", just type — no Enter needed. The option numbers hide so digits are text |
28
- | paste / drop / `ctrl+v` | In "Other": a pasted or dropped image/file path, or a clipboard image (Ctrl+V), becomes `[Image #N]` / `[File #N]`; images are sent to the model with your answer |
29
- | `?` | Not ready to answer: the agent is told you want to clarify first |
30
- | `esc` | Cancel and stop the agent's turn (or send what's answered — see Settings) |
52
+ | Key | What it does |
53
+ |---|---|
54
+ | `↑` / `↓` | Moves between options. |
55
+ | `1`–`9` | Selects that option. In a multi-select question, it toggles the option. |
56
+ | `Space` | Toggles an option in a multi-select question. |
57
+ | `Enter` | Selects and goes to the next question. On the last question, it sends all answers. |
58
+ | `←` / `→`, `Tab`, `Shift+Tab` | Goes to another question. In an "Other" field with text, `←` and `→` move the cursor. |
59
+ | `Ctrl+V` | Pastes an image from the clipboard into the "Other" field. |
60
+ | `?` | Tells the agent that you are not ready to answer and want to talk first. |
61
+ | `Esc` | Stops the agent turn. A setting can make `Esc` send your answers. |
31
62
 
32
- Questions you leave unanswered are sent as **skipped** — skipping never blocks. The header chips show progress: `✓` answered, a number for multi-select picks.
63
+ In the "Other" field, you type text directly. The option numbers disappear, so digits go into the text. A pasted image becomes `[Image #N]`, and a file path becomes `[File #N]`. The tool sends images to the model with your answers.
33
64
 
34
- In the transcript the answers stay as a short tree (`● Asked user 3 questions` / `│ Planet: Mars` / `└ Last book: Dune`). In the **simple** render style the collapsed `• Asked user` row keeps the same answers as compact `header: answer` rows underneath — answering never makes them disappear.
65
+ ## Agent tools
35
66
 
36
- ## For the agent
67
+ | Tool | What it does |
68
+ |---|---|
69
+ | `ask_user` | Asks 1 to 4 questions in one dialog and waits for the answers. |
37
70
 
38
71
  ```ts
39
72
  ask_user({
40
73
  questions: [{
41
- header: "Database", // ≤16 chars, shown as a chip
74
+ header: "Database", // 16 characters or fewer
42
75
  question: "Which database should the service use?",
43
- options: [ // 2–4; "Other" is added automatically
76
+ options: [ // 2–4 options
44
77
  { label: "Postgres", description: "JSON columns, full-text search" },
45
- { label: "SQLite", description: "Zero setup; single writer" },
78
+ { label: "SQLite", description: "No setup, single writer" },
46
79
  ],
47
80
  multi_select: false, // optional
48
- other: true, // optional; false removes "Other"
81
+ other: true, // optional, false removes "Other"
49
82
  }],
50
83
  })
51
84
  ```
52
85
 
53
- The result is `User answered your questions:` followed by a JSON object keyed by question: `{ "selected": [...], "custom_text": "...", "skipped": false }`. Attached images follow as image content.
86
+ The result starts with `User answered your questions:`. A JSON object follows, with one key for each question: `{ "selected": [...], "custom_text": "...", "skipped": false }`. Images follow as image content.
54
87
 
55
- - Tool calls from one message run one at a time, so several `ask_user` calls can never hide each other (they used to: only the last dialog was answerable and the turn hung).
56
- - Options can carry `action: "end_turn"` or `action: "new_session"` with a `prefill` — the workflow skills use these for handoffs; a launcher offers **Compact & run** or **Run directly**.
57
- - The older single-question form (`question`, `context`, `options`, `allowMultiple`, `allowFreeform`) is still accepted; `timeout` is ignored — you answer in your own time.
58
- - Inside a subagent the tool fails with instructions to report the question to the lead instead.
88
+ Other facts:
89
+
90
+ - An option can have `action: "end_turn"`. This option ends the turn.
91
+ - An option can have `action: "new_session"` and a `prefill` message. You then select **Compact & run** or **Run directly**. Workflow skills use this to start the next step.
92
+ - The tool runs one dialog at a time. Thus several calls in one message cannot hide each other.
93
+ - The tool also accepts the older single-question form: `question`, `context`, `options`, `allowMultiple`, `allowFreeform`. It ignores `timeout`.
94
+ - In a subagent, the tool fails. The error tells the subagent to give the question to its lead.
95
+ - With no interactive UI, the tool tells the agent to ask in its reply.
59
96
 
60
97
  ## Settings
61
98
 
62
- `/unipi:settings` → Ask User (changes apply to the next question, no restart):
63
-
64
- | Setting | Default | What it does |
65
- |---------|---------|--------------|
66
- | Enable ask_user | **off** | Give the agent the tool; when off, the agent is told to ask in its reply instead |
67
- | Notify when asked | on | Send a notification while the agent waits for you |
68
- | Questions per call | 4 | How many questions one dialog may hold (1–4; the tool description updates in new sessions) |
69
- | Esc | stop the agent's turn | Or: send what's answered, the rest as skipped |
70
- | Number keys move on | on | In a single-choice question, 1–9 also goes to the next question |
71
- | "Other" choice | agent decides | Or: always / never offer "Other (type your own)", whatever the agent asked for |
72
- | "Not ready" line | on | Show "? Not ready to answer, help me out!" (the `?` key works either way) |
99
+ Open `/unipi:settings` → **Ask User**. A change applies to the next question.
100
+
101
+ | Key | Default | What it does |
102
+ |---|---|---|
103
+ | `enabled` | `false` | Gives the agent the `ask_user` tool. |
104
+ | `notifyOnAsk` | `true` | Sends a notification while the agent waits. |
105
+ | `maxQuestions` | `4` | Maximum questions in one dialog (1–4). The tool description changes in new sessions. |
106
+ | `escape` | `stop` | `stop` ends the agent turn. `send` sends your answers and skips the rest. |
107
+ | `digitAdvance` | `true` | In a single-choice question, a number key also goes to the next question. |
108
+ | `other` | `agent` | `agent` lets the agent decide. `always` and `never` override the agent. |
109
+ | `helpLine` | `true` | Shows the "Not ready to answer" line. The `?` key works in all cases. |
110
+
111
+ ## See also
112
+
113
+ - [Notify](../notify/README.md)
114
+ - [BTW](../btw/README.md)
115
+ - [Tools reference](../../docs/reference/tools.md)
@@ -15,7 +15,8 @@ import {
15
15
  emitEvent,
16
16
  getPackageVersion,
17
17
  } from "@pi-unipi/core";
18
- import { registerAskUserTools } from "./tools.js";
18
+ import { getAskUserSettings } from "./config.js";
19
+ import { isSubagentChild, registerAskUserTools, syncAskUserTool } from "./tools.js";
19
20
 
20
21
  /** Package version */
21
22
  const VERSION = getPackageVersion(dirname(fileURLToPath(import.meta.url)));
@@ -25,8 +26,18 @@ export default function (pi: ExtensionAPI) {
25
26
  // Register tools
26
27
  registerAskUserTools(pi);
27
28
 
29
+ const sync = (cwd?: string) => {
30
+ try {
31
+ const want = getAskUserSettings(cwd).enabled && !isSubagentChild();
32
+ syncAskUserTool(pi, want);
33
+ } catch {
34
+ // tools may not be ready
35
+ }
36
+ };
37
+
28
38
  // Session lifecycle — announce module
29
- pi.on("session_start", async () => {
39
+ pi.on("session_start", async (_event, ctx) => {
40
+ sync(ctx?.cwd);
30
41
  emitEvent(pi, UNIPI_EVENTS.MODULE_READY, {
31
42
  name: MODULES.ASK_USER,
32
43
  version: VERSION,
@@ -34,4 +45,8 @@ export default function (pi: ExtensionAPI) {
34
45
  tools: [ASK_USER_TOOLS.ASK],
35
46
  });
36
47
  });
48
+
49
+ pi.on("before_agent_start", async (_event, ctx) => {
50
+ sync(ctx?.cwd);
51
+ });
37
52
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/ask-user",
3
- "version": "3.0.0-alpha.21",
3
+ "version": "3.0.0-alpha.22",
4
4
  "description": "Structured user input tool for Pi coding agent — single-select, multi-select, freeform",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -40,7 +40,7 @@
40
40
  "access": "public"
41
41
  },
42
42
  "dependencies": {
43
- "@pi-unipi/core": "3.0.0-alpha.21"
43
+ "@pi-unipi/core": "3.0.0-alpha.22"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -28,11 +28,29 @@ export interface AskDetails {
28
28
  questions: AskQuestion[];
29
29
  answers?: QuestionAnswer[];
30
30
  outcome: "answered" | "clarify" | "cancelled" | "action" | "unavailable";
31
+ reason?: "disabled" | "no-ui";
31
32
  /** Legacy single-question fields, kept for older consumers (compactor, notify). */
32
33
  question?: string;
33
34
  attachments?: Array<Pick<Attachment, "id" | "kind" | "path" | "name">>;
34
35
  }
35
36
 
37
+ /**
38
+ * Adds the ask_user tool to pi's active set when enabled, removes it otherwise,
39
+ * touching ONLY `ask_user`, never another module's tools. Skips `setActiveTools`
40
+ * when nothing would change.
41
+ */
42
+ export function syncAskUserTool(
43
+ pi: Pick<ExtensionAPI, "getActiveTools" | "setActiveTools">,
44
+ want: boolean,
45
+ ): void {
46
+ const current = pi.getActiveTools();
47
+ if (current.includes(ASK_USER_TOOLS.ASK) === want) return;
48
+ const set = new Set(current);
49
+ if (want) set.add(ASK_USER_TOOLS.ASK);
50
+ else set.delete(ASK_USER_TOOLS.ASK);
51
+ pi.setActiveTools([...set]);
52
+ }
53
+
36
54
  const OPTION = Type.Object({
37
55
  label: Type.String({ description: "Display text (1–5 words)" }),
38
56
  description: Type.Optional(Type.String({ description: "What this option means or its trade-offs" })),
@@ -51,8 +69,8 @@ const QUESTION = Type.Object({
51
69
  other: Type.Optional(Type.Boolean({ description: "Offer \"Other (type your own)\" (default true)" })),
52
70
  });
53
71
 
54
- function unavailable(questions: AskQuestion[], text: string) {
55
- return { content: [{ type: "text" as const, text }], details: { questions, outcome: "unavailable" } as AskDetails };
72
+ function unavailable(questions: AskQuestion[], text: string, reason?: "disabled" | "no-ui") {
73
+ return { content: [{ type: "text" as const, text }], details: { questions, outcome: "unavailable", reason } as AskDetails };
56
74
  }
57
75
 
58
76
  function imageParts(attachments: readonly Attachment[]) {
@@ -120,8 +138,8 @@ export function registerAskUserTools(pi: ExtensionAPI): void {
120
138
  "ask_user is not available inside a subagent. You cannot talk to the user directly — only your lead can. Do not guess an answer: state the question, the options you considered and your recommendation in your report.",
121
139
  );
122
140
  }
123
- if (!settings.enabled) return unavailable(questions, "ask_user is turned off in settings — ask in your reply instead.");
124
- if (!ctx.hasUI) return unavailable(questions, "No interactive UI (non-interactive mode) — ask in your reply instead.");
141
+ if (!settings.enabled) return unavailable(questions, "ask_user is turned off in settings — ask in your reply instead.", "disabled");
142
+ if (!ctx.hasUI) return unavailable(questions, "No interactive UI (non-interactive mode) — ask in your reply instead.", "no-ui");
125
143
  if (questions.length === 0) throw new Error("ask_user needs at least one question with a question text.");
126
144
 
127
145
  if (settings.notifyOnAsk) {
@@ -186,7 +204,15 @@ export function askRows(details: AskDetails | undefined, theme: Theme): string[]
186
204
  if (details === undefined || questions.length === 0) return [];
187
205
  if (details.outcome === "cancelled") return [` ${theme.fg("error", "Canceled by the user")}`];
188
206
  if (details.outcome === "clarify") return [` ${theme.fg("warning", "Not ready to answer — wants to clarify first")}`];
189
- if (details.outcome === "unavailable") return [` ${theme.fg("dim", "not shown (no interactive UI or turned off)")}`];
207
+ if (details.outcome === "unavailable") {
208
+ const text =
209
+ details.reason === "disabled"
210
+ ? "not shown — ask_user is turned off in settings"
211
+ : details.reason === "no-ui"
212
+ ? "not shown — no interactive UI"
213
+ : "not shown (no interactive UI or turned off)";
214
+ return [` ${theme.fg("dim", text)}`];
215
+ }
190
216
  if (details.outcome === "action") return [` ${theme.fg("muted", "picked an action")}`];
191
217
  return questions.map((q, i) => ` ${theme.fg("muted", `${q.header}: ${answerSummary(q, details.answers?.[i])}`)}`);
192
218
  }
@@ -228,7 +254,15 @@ export function renderAskResult(details: AskDetails | undefined, theme: Theme):
228
254
  const rows: string[] = [];
229
255
  if (details!.outcome === "cancelled") rows.push(t.fg("error", "Canceled by the user"));
230
256
  else if (details!.outcome === "clarify") rows.push(t.fg("warning", "Not ready to answer — wants to clarify first"));
231
- else if (details!.outcome === "unavailable") rows.push(t.fg("dim", "not shown (no interactive UI or turned off)"));
257
+ else if (details!.outcome === "unavailable") {
258
+ const text =
259
+ details!.reason === "disabled"
260
+ ? "not shown — ask_user is turned off in settings"
261
+ : details!.reason === "no-ui"
262
+ ? "not shown — no interactive UI"
263
+ : "not shown (no interactive UI or turned off)";
264
+ rows.push(t.fg("dim", text));
265
+ }
232
266
  else {
233
267
  questions.forEach((q, i) => {
234
268
  const a = details!.answers?.[i];
@@ -1,37 +1,62 @@
1
- # @pi-unipi/command-enchantment
1
+ # Command Enchantment
2
2
 
3
- Enhanced autocomplete for Unipi commands. It wraps Pi's base autocomplete provider and makes `/unipi:*` suggestions easier to scan with package grouping, stable sorting, short descriptions, and package colors.
3
+ Command Enchantment makes `/unipi:*` suggestions in the editor easy to scan, with package tags, colors and a stable order.
4
4
 
5
- ## Commands
5
+ `@pi-unipi/command-enchantment` · part of [UniPi](../../README.md) · source folder `packages/autocomplete`
6
6
 
7
- Command Enchantment has no user commands. It improves the editor autocomplete experience automatically when the package is installed.
7
+ ## What it does
8
8
 
9
- ## What It Does
9
+ - Adds a colored package tag, for example `[workflow]`, to each `/unipi:*` suggestion. The tag replaces the pi source tag.
10
+ - Sorts suggestions by match quality: exact, then prefix, then fuzzy.
11
+ - Puts `/unipi:settings` first when you type `/settings`.
12
+ - Hides `/skill:*` suggestions until you type `/skill:`.
13
+ - Keeps argument suggestions for `/unipi:*` commands, also when you press Tab after a space.
10
14
 
11
- - Groups `/unipi:*` commands by package so workflow, memory, web, footer, and other commands are visually distinct.
12
- - Shows full command values like `unipi:permission` while replacing Pi source tags with concise package tags like `[workflow]`.
13
- - Sorts matches in predictable tiers: exact Unipi matches first, then other Unipi matches, then system commands.
14
- - Preserves dynamic argument completions from command providers (per-command `getArgumentCompletions`).
15
- - Ships an audit test that checks registered Unipi commands are represented in the autocomplete registry and have descriptions.
15
+ ## Quick start
16
16
 
17
- ## Development Checks
18
-
19
- Run the registry audit before releases:
17
+ Command Enchantment ships in `@pi-unipi/unipi`. To install it alone:
20
18
 
21
19
  ```bash
22
- npm --workspace packages/autocomplete test -- src/__tests__/command-registry.audit.test.ts
20
+ pi install npm:@pi-unipi/command-enchantment
23
21
  ```
24
22
 
25
- Run all autocomplete tests:
23
+ 1. Type `/unipi:` in the editor.
24
+ 2. Read the package tag next to each suggestion.
25
+ 3. Type a short name, for example `/plan`. The exact match `/unipi:plan` comes first.
26
+
27
+ The package has no commands and no agent tools.
28
+
29
+ ## Settings
30
+
31
+ Open `/unipi:settings` → Command Enchantment. The namespace is `command-enchantment`.
32
+
33
+ | Key | Default | What it does |
34
+ |---|---|---|
35
+ | `autocompleteEnhanced` | `true` | Turns the enhanced suggestions on or off. Applies at the next session start. |
36
+
37
+ With `false`, the package does not add its provider. Pi suggestions stay as they are.
38
+
39
+ ## How it works
40
+
41
+ At session start, Command Enchantment wraps the pi autocomplete provider. It changes only text that starts with `/`. Other text goes to the pi provider without a change.
42
+
43
+ For a command name, it uses this order:
44
+
45
+ 1. A UniPi command that replaces a pi command with the same name (`settings`).
46
+ 2. An exact match of the full command.
47
+ 3. An exact match of the short name after `unipi:`.
48
+ 4. A prefix match.
49
+ 5. A fuzzy match. A shorter name comes first.
50
+
51
+ In the same tier, pi commands come before UniPi commands. UniPi commands then follow a fixed package order.
52
+
53
+ The command list is in `src/constants.ts`. An audit test checks that each registered `/unipi:*` command is in this list and has a description. Run it before a release:
26
54
 
27
55
  ```bash
28
56
  npm --workspace packages/autocomplete test
29
57
  ```
30
58
 
31
- ## Configuration
32
-
33
- Autocomplete enhancement is enabled by default. The package stores its toggle in the Unipi config and can be disabled by setting `autocompleteEnhanced` to `false`.
34
-
35
- ## License
59
+ ## See also
36
60
 
37
- MIT
61
+ - [Commands reference](../../docs/reference/commands.md)
62
+ - [Settings reference](../../docs/reference/settings.md)
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/command-enchantment",
3
- "version": "3.0.0-alpha.21",
3
+ "version": "3.0.0-alpha.22",
4
4
  "description": "Enhanced TUI autocomplete for /unipi:* commands — colored, sorted, and grouped by package",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -1,76 +1,79 @@
1
- # @pi-unipi/background-tasks
1
+ # Background Tasks
2
2
 
3
- Background tasks for UniPi — durable background shell jobs plus one read-only
4
- delegated background agent, with a footer dock the user can manage while the
5
- agent keeps working. Originally adopted from
6
- [pi-background-tasks](https://github.com/ismailsaleekh/pi-background-tasks);
7
- the Fusion council, attested Pi runs, and Anthropic attribution surfaces were
8
- removed in 2.17.0 so the module does exactly one thing.
3
+ Background Tasks runs long shell commands in the background, so the agent can
4
+ continue to work. When a task finishes, the agent gets a notice.
9
5
 
10
- ## Master toggle
6
+ `@pi-unipi/background-tasks` · part of [UniPi](../../README.md)
11
7
 
12
- One config key disables the entire module — no tools, no commands, no hooks, no UI:
8
+ ## What it does
13
9
 
14
- ```json
15
- // ~/.unipi/config/background-tasks.json (global)
16
- // <workspace>/.unipi/config/background-tasks.json (override; workspace wins)
17
- {
18
- "enabled": true
19
- }
20
- ```
21
-
22
- Open `/unipi:settings` (Background Tasks group) for the master toggle,
23
- defaults, output caps, delegate defaults).
24
-
25
- ## Surfaces
10
+ - Starts a named shell command and returns at once with a task ID and an output file.
11
+ - When the task stops, sends a `<background-task-notification>` message. By
12
+ default, this message starts a new agent turn.
13
+ - Gives the agent bounded log reads, so large output does not fill the context.
14
+ - Stops a task when its output is more than 20 MiB.
15
+ - Shows a start card, a completion card and a task dock.
16
+ - Shows a wait line above the editor while the agent waits for a task.
26
17
 
27
- ### Tools
18
+ ## Quick start
28
19
 
29
- | Tool | Purpose |
30
- | --- | --- |
31
- | `bg_run` | Start a named long-running shell command; terminal notification wakes a follow-up turn by default |
32
- | `bg_status` / `bg_logs` / `bg_kill` | Point-in-time inspection, bounded log reads, stop — never polling primitives |
33
- | `bg_delegate` + `bg_result` | One read-only child Pi agent seeded with a frozen projection of this conversation; hash-verified answer retrieval |
20
+ 1. Install UniPi: `pi install npm:@pi-unipi/unipi`. You can also install this
21
+ package alone: `pi install npm:@pi-unipi/background-tasks`.
22
+ 2. Ask the agent to run a long test suite in the background. The agent calls `bg_run`.
23
+ 3. Push `Shift+Down` to open the task dock.
34
24
 
35
- ### Commands
25
+ ## Commands
36
26
 
37
- `/unipi:bg` (start a shell task), `/unipi:bg-tasks` (open the dock),
38
- `/unipi:settings` (Background Tasks group).
27
+ | Command or key | What it does |
28
+ |---|---|
29
+ | `/unipi:bg [--agent] [--name "Name"] <command>` | Starts a background task. It sends a notice, but does not start a turn. |
30
+ | `/unipi:bg-tasks` | Opens the task manager. |
31
+ | `Shift+Down` | Opens the task dock. |
32
+ | `Ctrl+Alt+C` | Clears the notices of finished tasks. |
39
33
 
40
- Shortcuts: `Shift↓` opens the task manager dock; `Ctrl+Alt+C` clears finished notices.
34
+ ## Agent tools
41
35
 
42
- ## What the user sees
36
+ | Tool | What it does |
37
+ |---|---|
38
+ | `bg_run` | Starts a named shell command. Set `isAgent` to `true` only for a command that starts an LLM agent. |
39
+ | `bg_status` | Shows the state of one task or all tasks at one time. |
40
+ | `bg_logs` | Reads bounded output from a task. |
41
+ | `bg_kill` | Stops a running task by ID. |
43
42
 
44
- - **Launch card** — `bg_run` results render as a tinted card (`● bg started <name> · wakes agent on completion`) so the start of a task is visible in the transcript.
45
- - **Pending-wake line** — while the agent is idle but a task that will wake it is still running, a spinner line sits above the editor: `⠋ waiting on 1 bg task · <name> 12s — agent resumes automatically when done`. Without this the UI looks finished and users assume the turn is over.
46
- - **Completion card** — the terminal notification renders as a tinted card (`✓ bg done <name> · exit 0 · 25s · agent woken`) with the last three output lines. Failed / stopped tasks use the error tint.
47
- - **Dock** (`Shift↓`) — rows show `⏰ wakes agent` for tasks that will resume the agent, and the last output line for running shell tasks.
43
+ Do not call `bg_status` or `bg_logs` in a loop to wait. The notification
44
+ message is the final state of the task.
48
45
 
49
- ## Storage layout (ours — never `.pi/`)
46
+ | `notifyOnCompletion` | `triggerOnCompletion` | Result |
47
+ |---|---|---|
48
+ | `true` | `true` (`bg_run` default) | A notice and a new agent turn. |
49
+ | `true` | `false` (`/unipi:bg` default) | A notice only. |
50
+ | `false` | any | No notice. Check the task yourself. |
50
51
 
51
- - Runtime artifacts (task output/metadata): `$TMPDIR/unipi-bg-tasks/<session>-<pid>-<nonce>/`
52
- - Durable delegate artifacts: `<workspace>/.unipi/delegate/<session>-<pid>/<task-id>/`
53
- - Config: `~/.unipi/config/background-tasks.json` + workspace override
52
+ ## Settings
54
53
 
55
- ## Environment
54
+ Namespace `background-tasks`. Open it with `/unipi:settings`.
56
55
 
57
- `UNIPI_BG_*` prefix (replaces the reference `PI_BG_*`):
58
- `UNIPI_BG_TMP_DIR`, `UNIPI_BG_MAX_OUTPUT_BYTES`, `UNIPI_BG_SHELL`,
59
- `UNIPI_BG_SHELL_PATH`, `UNIPI_BG_DISABLE_PI_TELEMETRY`, `UNIPI_BG_DELEGATE_*`.
56
+ | Key | Default | What it does |
57
+ |---|---|---|
58
+ | `enabled` | `true` | Master switch. When it is `false`, the package registers no tools, commands or UI. |
59
+ | `notifyOnCompletion` | `true` | Sends a notice when a task stops. |
60
+ | `triggerOnCompletion` | `true` | Starts an agent turn when a task stops. |
61
+ | `defaultTimeoutSeconds` | `0` | Stops a task after this time. `0` means no limit. |
62
+ | `maxFinishedTasks` | `30` | Number of finished tasks to keep. |
63
+ | `maxOutputBytes` | `20971520` | Stops and fails a task above this output size. Minimum 1024. |
60
64
 
61
- ## Completion delivery
65
+ | Variable | What it does |
66
+ |---|---|
67
+ | `UNIPI_BG_TMP_DIR` | Folder for task output. The default is `TMPDIR` or `/tmp`. |
68
+ | `UNIPI_BG_MAX_OUTPUT_BYTES` | Default output limit. The `maxOutputBytes` setting wins. |
69
+ | `UNIPI_BG_SHELL`, `UNIPI_BG_SHELL_PATH` | On Windows, sets the shell: `cmd` or `bash`. |
70
+ | `UNIPI_BG_DISABLE_PI_TELEMETRY` | Set to `1` to stop the telemetry wrap of agent commands. |
62
71
 
63
- | `notifyOnCompletion` | `triggerOnCompletion` | Mode |
64
- | --- | --- | --- |
65
- | true (default) | true (bg_run default) | Durable terminal notification + automatic follow-up turn |
66
- | true | false (`/unipi:bg` default) | Notification only |
67
- | false | — | Manual monitoring |
72
+ Task output goes to `<tmp>/unipi-bg-tasks/<session>-<pid>-<nonce>/`.
68
73
 
69
- Treat `<background-task-notification>` as durable terminal truth — do not poll.
74
+ ## For other packages
70
75
 
71
- ## Shared registry (for sibling extensions)
72
-
73
- Other packages can read the live task registry synchronously — no events, no polling:
76
+ Read the live task list without events:
74
77
 
75
78
  ```ts
76
79
  import { getSharedTaskRegistry } from "@pi-unipi/background-tasks";
@@ -79,19 +82,18 @@ const tasks = getSharedTaskRegistry()?.allTasks() ?? [];
79
82
  const running = tasks.filter((t) => t.status === "running").length;
80
83
  ```
81
84
 
82
- The registry is published on `globalThis` under a `Symbol.for` key at extension
83
- init and `session_start`, and cleared on `session_shutdown` (counts are
84
- per-session). Returns `undefined` when the module is disabled — callers must
85
- treat that as "no data", e.g. the footer's glance process line does.
85
+ The function returns `undefined` when the package is off. Treat that as "no data".
86
+
87
+ ## How it works
88
+
89
+ Read [Turn arbiter](../../docs/architecture/turn-arbiter.md) and
90
+ [Harness messages](../../docs/architecture/harness-messages.md).
86
91
 
87
- ## Differences from the reference
92
+ ## See also
88
93
 
89
- - Commands live in the `/unipi:*` namespace; env prefix is `UNIPI_BG_*`.
90
- - Runtime artifacts under the OS temp root (per-registry nonce) and durable
91
- delegate artifacts under workspace `.unipi/` — never `.pi/`.
92
- - The reference's `update-check` footer surface is dropped (unipi's updater
93
- module owns updates).
94
- - Removed: attested Pi runs, the five-slot Fusion council tools, and the
95
- Anthropic attribution provider override.
94
+ - [Subagents](../subagents/README.md)
95
+ - [Tools reference](../../docs/reference/tools.md)
96
+ - [Shortcuts reference](../../docs/reference/shortcuts.md)
96
97
 
97
- ISC-licensed reference: Copyright Ismail <ismailsalikhodjaev@gmail.com>.
98
+ Based on [pi-background-tasks](https://github.com/ismailsaleekh/pi-background-tasks)
99
+ (ISC license, Copyright Ismail).
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/background-tasks",
3
- "version": "3.0.0-alpha.21",
3
+ "version": "3.0.0-alpha.22",
4
4
  "description": "Background tasks for UniPi — durable shell jobs and delegated background agents with a footer dock",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "dependencies": {
12
12
  "@earendil-works/pi-ai": "^0.87.1",
13
- "@pi-unipi/core": "3.0.0-alpha.21",
13
+ "@pi-unipi/core": "3.0.0-alpha.22",
14
14
  "turndown": "^7.2.4"
15
15
  },
16
16
  "devDependencies": {