@pi-unipi/unipi 2.16.0 → 2.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -1
- package/package.json +26 -23
- package/packages/ask-user/package.json +2 -2
- package/packages/autocomplete/package.json +1 -1
- package/packages/autocomplete/src/constants.ts +11 -18
- package/packages/background-tasks/README.md +21 -27
- package/packages/background-tasks/package.json +3 -4
- package/packages/background-tasks/src/cards.ts +76 -0
- package/packages/background-tasks/src/child-process.ts +1 -1
- package/packages/background-tasks/src/config.ts +0 -42
- package/packages/background-tasks/src/context-visible-conversation-v2.ts +1 -1
- package/packages/background-tasks/src/delegate/artifacts.ts +1 -1
- package/packages/background-tasks/src/delegate/hook-contract-evidence.json +18 -0
- package/packages/background-tasks/src/delegate/launch.ts +17 -30
- package/packages/background-tasks/src/delegate/result-package.ts +1 -1
- package/packages/background-tasks/src/delegate/runner.ts +1 -20
- package/packages/background-tasks/src/delegate/seed.ts +1 -1
- package/packages/background-tasks/src/delegate-extension.ts +16 -168
- package/packages/background-tasks/src/fixtures/delegate-context-incident.json +17 -0
- package/packages/background-tasks/src/index.ts +53 -25
- package/packages/background-tasks/src/json-utils.ts +56 -0
- package/packages/background-tasks/src/package-assets.ts +51 -0
- package/packages/background-tasks/src/registry.ts +8 -459
- package/packages/background-tasks/src/task-manager.ts +13 -2
- package/packages/background-tasks/src/tools.ts +4 -189
- package/packages/background-tasks/src/types.ts +17 -70
- package/packages/btw/package.json +2 -2
- package/packages/compactor/package.json +3 -3
- package/packages/core/fusion-status.ts +39 -0
- package/packages/core/index.ts +2 -0
- package/packages/core/package.json +1 -1
- package/packages/core/spinner-line.ts +99 -0
- package/packages/core/tui-overlay.ts +32 -0
- package/packages/footer/package.json +3 -3
- package/packages/footer/src/glance-editor.ts +22 -3
- package/packages/footer/src/index.ts +3 -1
- package/packages/fusion/README.md +131 -0
- package/packages/fusion/package.json +54 -0
- package/packages/fusion/src/index.ts +378 -0
- package/packages/fusion/src/picker.ts +572 -0
- package/packages/fusion/src/preset-editor.ts +194 -0
- package/packages/fusion/src/preset.ts +259 -0
- package/packages/fusion/src/prompts.ts +96 -0
- package/packages/fusion/src/savings.ts +22 -0
- package/packages/fusion/src/sidekick-runtime.ts +301 -0
- package/packages/fusion/src/slider.ts +37 -0
- package/packages/fusion/src/tools.ts +163 -0
- package/packages/image/package.json +2 -2
- package/packages/info-screen/package.json +2 -2
- package/packages/input-shortcuts/package.json +2 -2
- package/packages/kanboard/package.json +2 -2
- package/packages/mcp/package.json +2 -2
- package/packages/memory/package.json +3 -3
- package/packages/milestone/package.json +2 -2
- package/packages/notify/README.md +16 -0
- package/packages/notify/events.ts +3 -1
- package/packages/notify/package.json +2 -2
- package/packages/notify/settings.ts +1 -0
- package/packages/notify/summarize.ts +23 -1
- package/packages/notify/types.ts +9 -0
- package/packages/ralph/package.json +3 -3
- package/packages/subagents/package.json +2 -2
- package/packages/subagents/src/widget.ts +11 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/unipi/bundled.js +4518 -12504
- package/packages/unipi/index.ts +2 -0
- package/packages/updater/package.json +2 -2
- package/packages/utility/package.json +2 -2
- package/packages/web-api/package.json +2 -2
- package/packages/workflow/package.json +2 -2
- package/packages/background-tasks/extensions/anthropic-attribution.ts +0 -1
- package/packages/background-tasks/extensions/fusion-child.ts +0 -1
- package/packages/background-tasks/src/anthropic-attribution-path.ts +0 -21
- package/packages/background-tasks/src/anthropic-attribution.ts +0 -1983
- package/packages/background-tasks/src/attested-pi-run.ts +0 -612
- package/packages/background-tasks/src/fusion/artifacts.ts +0 -967
- package/packages/background-tasks/src/fusion/budget.ts +0 -1162
- package/packages/background-tasks/src/fusion/child-protocol.ts +0 -305
- package/packages/background-tasks/src/fusion/claude-cache.ts +0 -207
- package/packages/background-tasks/src/fusion/clean-context.ts +0 -91
- package/packages/background-tasks/src/fusion/config.ts +0 -449
- package/packages/background-tasks/src/fusion/context.ts +0 -265
- package/packages/background-tasks/src/fusion/evaluation.ts +0 -800
- package/packages/background-tasks/src/fusion/orchestrator.ts +0 -1288
- package/packages/background-tasks/src/fusion/output-contract.ts +0 -34
- package/packages/background-tasks/src/fusion/pi-child.ts +0 -2373
- package/packages/background-tasks/src/fusion/prompts.ts +0 -345
- package/packages/background-tasks/src/fusion/result-package.ts +0 -959
- package/packages/background-tasks/src/fusion/source-policy.ts +0 -257
- package/packages/background-tasks/src/fusion/types.ts +0 -1139
- package/packages/background-tasks/src/fusion/web-fetch.ts +0 -1060
- package/packages/background-tasks/src/fusion/workflows.ts +0 -184
- package/packages/background-tasks/src/fusion-child-extension.ts +0 -1052
- package/packages/background-tasks/src/fusion-extension.ts +0 -1293
- package/packages/background-tasks/src/ui/fusion-model-selector.ts +0 -322
package/CHANGELOG.md
CHANGED
|
@@ -6,7 +6,30 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
-
## [2.
|
|
9
|
+
## [2.17.0] — 2026-09-15
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `fusion`: **Local Fusion runtime — a real lead + sidekick pair.** Picking a Fusion preset now wires up a persistent sidekick subagent, not just a status label, via two new agent tools:
|
|
14
|
+
- `sidekick` — hand off implementation/verification work to a persistent sidekick child process, spawned in pi's RPC mode (`--mode rpc`) with the active pair's sidekick model + effort and its own session file under `~/.unipi/state/fusion/sidekick/<session>.jsonl`. Blocking dispatch is the default (`block:true` waits and returns the report); `block:false` returns immediately and later delivers a `<subagent_completion_notification>` with the report. Calling `sidekick` again while a handoff is running injects the message as an interrupt instead of spawning a second process.
|
|
15
|
+
- `read_subagent` — read a handoff report by `agent_id` (or the latest); `block:true` waits (default 2700s), `block:false` returns a live progress snapshot (tool-call count, recent tools, text tail).
|
|
16
|
+
- The runtime (`sidekick-runtime.ts`) keeps one persistent child per session, streams progress (`◆ sidekick working · N tool calls · 12.3s`), accumulates usage (input/output/cache-read/cache-write tokens + cost), and returns a structured `HandoffReport` (`status`, `text`, `usage`, `toolCalls`, `durationMs`).
|
|
17
|
+
- A delegate-by-default policy (`leadPolicy()` in `prompts.ts`) is appended to the lead's system prompt on every turn while Fusion is active, the sidekick child gets a dedicated role prompt (`sidekickSystemPrompt()`), and a one-time `FIRST_EDIT_NUDGE` reminds the lead to brief the sidekick instead of editing when a change is larger than trivial.
|
|
18
|
+
- `fusion`: **`/unipi:fusion-stats`** command — reports sidekick token usage, estimated savings, handoff count, and runtime liveness.
|
|
19
|
+
- `fusion`: **savings estimation** (`estimateSavings()` in `savings.ts`) — prices sidekick usage at both the sidekick and lead model rates and reports the difference; surfaced in the stats command and the footer.
|
|
20
|
+
- `footer`: **the glance frame now renders the active Fusion pair** in the model slot — `Fusion · <lead> <effort> ◆ <sidekick>` with the lead lit and the sidekick muted, plus `· saved $X.XX` once savings pass half a cent; the separate thinking slot is suppressed while a pair is active.
|
|
21
|
+
- `core`: new `fusion-status` module (`setSharedFusionStatus`/`getSharedFusionStatus`) — a `Symbol.for` shared registry (same pattern as background-tasks) that lets `fusion` publish the active pair and `footer` render it without a hard dependency.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- `fusion`: **now depends on `@pi-unipi/subagents`** — the sidekick child is spawned via `getPiSpawnCommand` from `@pi-unipi/subagents/src/pi-spawn.js`, reusing the subagents package's spawn logic instead of re-implementing it.
|
|
26
|
+
- `fusion`: the picker shows **model badges** (from the preset), decouples effort selection, and renders a **rainbow price slider** (`slider.ts`) placing each model on a log-scaled cost gradient; confirming a Fusion pair now records it as the preset default.
|
|
27
|
+
|
|
28
|
+
## [2.16.1] — 2026-09-11
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- `notify`: **recap no longer silently degrades on thinking models** (GitHub [#36](https://github.com/Neuron-Mr-White/UniPi/issues/36)) — recap summarizes with a 100-token budget, and a thinking model served by llama.cpp/vLLM could burn the entire budget on reasoning tokens and return no visible content, falling back to the raw 100-character truncation. New `recap.disableThinking` config flag (default `false`) sends `chat_template_kwargs: { enable_thinking: false, preserve_thinking: false }` with recap requests so chat-template servers skip reasoning entirely. Opt-in because strict OpenAI-compatible endpoints reject unknown body params; the Anthropic path is unaffected. 5 new tests in `src/__tests__/summarize.test.ts`.
|
|
10
33
|
|
|
11
34
|
### Added
|
|
12
35
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/unipi",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.17.0",
|
|
4
4
|
"description": "All-in-one extension suite for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,7 +35,9 @@
|
|
|
35
35
|
"docs/prefix-cache-architecture.md",
|
|
36
36
|
"packages/*/package.json",
|
|
37
37
|
"packages/*/*.ts",
|
|
38
|
+
"packages/*/tests/**/*.ts",
|
|
38
39
|
"packages/*/src/**/*.ts",
|
|
40
|
+
"packages/*/src/**/*.json",
|
|
39
41
|
"packages/*/extensions/**/*.ts",
|
|
40
42
|
"packages/*/bridge/**/*.py",
|
|
41
43
|
"packages/*/platforms/*",
|
|
@@ -82,28 +84,29 @@
|
|
|
82
84
|
"typebox": "^1.1.38"
|
|
83
85
|
},
|
|
84
86
|
"dependencies": {
|
|
85
|
-
"@pi-unipi/ask-user": "2.
|
|
86
|
-
"@pi-unipi/background-tasks": "2.
|
|
87
|
-
"@pi-unipi/btw": "2.
|
|
88
|
-
"@pi-unipi/command-enchantment": "2.
|
|
89
|
-
"@pi-unipi/compactor": "2.
|
|
90
|
-
"@pi-unipi/core": "2.
|
|
91
|
-
"@pi-unipi/footer": "2.
|
|
92
|
-
"@pi-unipi/image": "2.
|
|
93
|
-
"@pi-unipi/
|
|
94
|
-
"@pi-unipi/
|
|
95
|
-
"@pi-unipi/
|
|
96
|
-
"@pi-unipi/
|
|
97
|
-
"@pi-unipi/
|
|
98
|
-
"@pi-unipi/
|
|
99
|
-
"@pi-unipi/
|
|
100
|
-
"@pi-unipi/
|
|
101
|
-
"@pi-unipi/
|
|
102
|
-
"@pi-unipi/
|
|
103
|
-
"@pi-unipi/
|
|
104
|
-
"@pi-unipi/
|
|
105
|
-
"@pi-unipi/
|
|
106
|
-
"@pi-unipi/
|
|
87
|
+
"@pi-unipi/ask-user": "2.17.0",
|
|
88
|
+
"@pi-unipi/background-tasks": "2.17.0",
|
|
89
|
+
"@pi-unipi/btw": "2.17.0",
|
|
90
|
+
"@pi-unipi/command-enchantment": "2.17.0",
|
|
91
|
+
"@pi-unipi/compactor": "2.17.0",
|
|
92
|
+
"@pi-unipi/core": "2.17.0",
|
|
93
|
+
"@pi-unipi/footer": "2.17.0",
|
|
94
|
+
"@pi-unipi/image": "2.17.0",
|
|
95
|
+
"@pi-unipi/fusion": "2.17.0",
|
|
96
|
+
"@pi-unipi/info-screen": "2.17.0",
|
|
97
|
+
"@pi-unipi/input-shortcuts": "2.17.0",
|
|
98
|
+
"@pi-unipi/kanboard": "2.17.0",
|
|
99
|
+
"@pi-unipi/mcp": "2.17.0",
|
|
100
|
+
"@pi-unipi/memory": "2.17.0",
|
|
101
|
+
"@pi-unipi/milestone": "2.17.0",
|
|
102
|
+
"@pi-unipi/notify": "2.17.0",
|
|
103
|
+
"@pi-unipi/ralph": "2.17.0",
|
|
104
|
+
"@pi-unipi/subagents": "2.17.0",
|
|
105
|
+
"@pi-unipi/trajectory": "2.17.0",
|
|
106
|
+
"@pi-unipi/updater": "2.17.0",
|
|
107
|
+
"@pi-unipi/utility": "2.17.0",
|
|
108
|
+
"@pi-unipi/web-api": "2.17.0",
|
|
109
|
+
"@pi-unipi/workflow": "2.17.0"
|
|
107
110
|
},
|
|
108
111
|
"devDependencies": {
|
|
109
112
|
"@earendil-works/pi-agent-core": "^0.84.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/ask-user",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.17.0",
|
|
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": "2.
|
|
43
|
+
"@pi-unipi/core": "2.17.0"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"@earendil-works/pi-coding-agent": "^0.84.0",
|
|
@@ -37,6 +37,7 @@ export const PACKAGE_ORDER: string[] = [
|
|
|
37
37
|
"image",
|
|
38
38
|
"subagents",
|
|
39
39
|
"background-tasks",
|
|
40
|
+
"fusion",
|
|
40
41
|
];
|
|
41
42
|
|
|
42
43
|
// ─── Package Colors ──────────────────────────────────────────────────
|
|
@@ -62,6 +63,7 @@ export const PACKAGE_COLORS: Record<string, string> = {
|
|
|
62
63
|
image: `${ESC}[35m`, // Magenta
|
|
63
64
|
subagents: `${ESC}[34m`, // Blue
|
|
64
65
|
"background-tasks": `${ESC}[91m`, // Bright Red
|
|
66
|
+
fusion: `${ESC}[96m`, // Bright Cyan
|
|
65
67
|
};
|
|
66
68
|
|
|
67
69
|
// ─── Command Registry ────────────────────────────────────────────────
|
|
@@ -143,17 +145,13 @@ export const COMMAND_REGISTRY: Record<string, string> = {
|
|
|
143
145
|
|
|
144
146
|
// background-tasks (8 commands)
|
|
145
147
|
"unipi:bg": "background-tasks",
|
|
146
|
-
"unipi:bg-clear": "background-tasks",
|
|
147
148
|
"unipi:bg-tasks": "background-tasks",
|
|
148
149
|
"unipi:bg-settings": "background-tasks",
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
"unipi:
|
|
152
|
-
"unipi:
|
|
153
|
-
"unipi:
|
|
154
|
-
"unipi:claude-cache": "background-tasks",
|
|
155
|
-
"unipi:fusion": "background-tasks",
|
|
156
|
-
"unipi:fusion-models": "background-tasks",
|
|
150
|
+
|
|
151
|
+
// fusion (3 commands)
|
|
152
|
+
"unipi:model": "fusion",
|
|
153
|
+
"unipi:fusion-preset": "fusion",
|
|
154
|
+
"unipi:fusion-stats": "fusion",
|
|
157
155
|
|
|
158
156
|
// info (2 commands)
|
|
159
157
|
"unipi:info": "info",
|
|
@@ -316,17 +314,11 @@ export const COMMAND_DESCRIPTIONS: Record<string, string> = {
|
|
|
316
314
|
"unipi:stash-settings": "Open input shortcuts settings — customize keybindings",
|
|
317
315
|
|
|
318
316
|
"unipi:bg": "Start a shell command as a tracked background task",
|
|
319
|
-
"unipi:bg-clear": "Clear finished background task footer notices",
|
|
320
317
|
"unipi:bg-tasks": "Open the background task manager UI",
|
|
321
318
|
"unipi:bg-settings": "Open background-tasks settings (master toggle, defaults)",
|
|
322
|
-
"unipi:
|
|
323
|
-
"unipi:
|
|
324
|
-
"unipi:
|
|
325
|
-
"unipi:kill": "Stop a running background task: /unipi:kill <id>",
|
|
326
|
-
"unipi:logs": "Show bounded output from a background task",
|
|
327
|
-
"unipi:claude-cache": "Show or set Claude cache retention for this session",
|
|
328
|
-
"unipi:fusion": "Run the Fusion reason workflow with a prompt",
|
|
329
|
-
"unipi:fusion-models": "Open the Fusion five-slot model selector",
|
|
319
|
+
"unipi:model": "Pick a model or Fusion lead+sidekick pair (Devin-style picker)",
|
|
320
|
+
"unipi:fusion-preset": "Curate the model preset used by /unipi:model",
|
|
321
|
+
"unipi:fusion-stats": "Estimated Fusion savings (sidekick tokens priced at lead rates)",
|
|
330
322
|
"unipi:subagents-fleet": "Open the subagents fleet view",
|
|
331
323
|
"unipi:subagents-doctor": "Diagnose subagents configuration",
|
|
332
324
|
"unipi:subagents-guide": "Show the subagents usage guide",
|
|
@@ -355,4 +347,5 @@ export const PACKAGE_LABELS: Record<string, string> = {
|
|
|
355
347
|
image: "image",
|
|
356
348
|
subagents: "subagents",
|
|
357
349
|
"background-tasks": "background-tasks",
|
|
350
|
+
fusion: "fusion",
|
|
358
351
|
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# @pi-unipi/background-tasks
|
|
2
2
|
|
|
3
|
-
Background tasks for UniPi —
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Anthropic
|
|
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.
|
|
8
9
|
|
|
9
10
|
## Master toggle
|
|
10
11
|
|
|
@@ -19,37 +20,37 @@ One config key disables the entire module — no tools, no commands, no hooks, n
|
|
|
19
20
|
```
|
|
20
21
|
|
|
21
22
|
Open `/unipi:bg-settings` for the interactive settings overlay (master toggle,
|
|
22
|
-
defaults, output caps, delegate
|
|
23
|
+
defaults, output caps, delegate defaults).
|
|
23
24
|
|
|
24
25
|
## Surfaces
|
|
25
26
|
|
|
26
|
-
### Tools
|
|
27
|
+
### Tools
|
|
27
28
|
|
|
28
29
|
| Tool | Purpose |
|
|
29
30
|
| --- | --- |
|
|
30
31
|
| `bg_run` | Start a named long-running shell command; terminal notification wakes a follow-up turn by default |
|
|
31
32
|
| `bg_status` / `bg_logs` / `bg_kill` | Point-in-time inspection, bounded log reads, stop — never polling primitives |
|
|
32
33
|
| `bg_delegate` + `bg_result` | One read-only child Pi agent seeded with a frozen projection of this conversation; hash-verified answer retrieval |
|
|
33
|
-
| `bg_run_pi_attested` | Evidence-oriented direct Pi spawn with local hashes and route attestation |
|
|
34
|
-
| `fusion_reason` | 3 candidates → blind evaluator → merger on the session projection |
|
|
35
|
-
| `fusion_investigate` / `fusion_research` / `fusion_validate` | Fixed-purpose multi-model workflows on clean-task input (inspect tools / caller-URL research / advisory review) |
|
|
36
34
|
|
|
37
|
-
### Commands
|
|
35
|
+
### Commands
|
|
38
36
|
|
|
39
|
-
`/unipi:bg
|
|
40
|
-
`/unipi:
|
|
41
|
-
`/unipi:bg-settings`, `/unipi:fusion`, `/unipi:fusion-models`,
|
|
42
|
-
`/unipi:claude-cache`.
|
|
37
|
+
`/unipi:bg` (start a shell task), `/unipi:bg-tasks` (open the dock),
|
|
38
|
+
`/unipi:bg-settings`.
|
|
43
39
|
|
|
44
40
|
Shortcuts: `Shift↓` opens the task manager dock; `Ctrl+Alt+C` clears finished notices.
|
|
45
41
|
|
|
42
|
+
## What the user sees
|
|
43
|
+
|
|
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.
|
|
48
|
+
|
|
46
49
|
## Storage layout (ours — never `.pi/`)
|
|
47
50
|
|
|
48
51
|
- Runtime artifacts (task output/metadata): `$TMPDIR/unipi-bg-tasks/<session>-<pid>-<nonce>/`
|
|
49
52
|
- Durable delegate artifacts: `<workspace>/.unipi/delegate/<session>-<pid>/<task-id>/`
|
|
50
|
-
- Durable fusion artifacts: `<workspace>/.unipi/fusion/<session>-<pid>/<run-id>/`
|
|
51
53
|
- Config: `~/.unipi/config/background-tasks.json` + workspace override
|
|
52
|
-
- Fusion model config: five-slot selector persisted through `/unipi:fusion-models`
|
|
53
54
|
|
|
54
55
|
## Environment
|
|
55
56
|
|
|
@@ -67,13 +68,6 @@ Shortcuts: `Shift↓` opens the task manager dock; `Ctrl+Alt+C` clears finished
|
|
|
67
68
|
|
|
68
69
|
Treat `<background-task-notification>` as durable terminal truth — do not poll.
|
|
69
70
|
|
|
70
|
-
## Attribution
|
|
71
|
-
|
|
72
|
-
`extensions/anthropic-attribution.ts` loads first (provider-gated): Claude Code
|
|
73
|
-
subscription OAuth attribution, exact-match system-prompt sanitization, and
|
|
74
|
-
cache-retention policy for Anthropic routes. Duplicate installed copies resolve
|
|
75
|
-
ownership through an EventBus claim; later copies go inert.
|
|
76
|
-
|
|
77
71
|
## Shared registry (for sibling extensions)
|
|
78
72
|
|
|
79
73
|
Other packages can read the live task registry synchronously — no events, no polling:
|
|
@@ -94,10 +88,10 @@ treat that as "no data", e.g. the footer's glance process line does.
|
|
|
94
88
|
|
|
95
89
|
- Commands live in the `/unipi:*` namespace; env prefix is `UNIPI_BG_*`.
|
|
96
90
|
- Runtime artifacts under the OS temp root (per-registry nonce) and durable
|
|
97
|
-
delegate
|
|
91
|
+
delegate artifacts under workspace `.unipi/` — never `.pi/`.
|
|
98
92
|
- The reference's `update-check` footer surface is dropped (unipi's updater
|
|
99
93
|
module owns updates).
|
|
100
|
-
-
|
|
101
|
-
|
|
94
|
+
- Removed: attested Pi runs, the five-slot Fusion council tools, and the
|
|
95
|
+
Anthropic attribution provider override.
|
|
102
96
|
|
|
103
97
|
ISC-licensed reference: Copyright Ismail <ismailsalikhodjaev@gmail.com>.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/background-tasks",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Background tasks for UniPi — durable shell jobs
|
|
3
|
+
"version": "2.17.0",
|
|
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",
|
|
7
7
|
"types": "src/index.ts",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@earendil-works/pi-ai": "^0.84.0",
|
|
13
|
-
"@pi-unipi/core": "2.
|
|
13
|
+
"@pi-unipi/core": "2.17.0",
|
|
14
14
|
"turndown": "^7.2.4"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
],
|
|
32
32
|
"pi": {
|
|
33
33
|
"extensions": [
|
|
34
|
-
"./extensions/anthropic-attribution.ts",
|
|
35
34
|
"./src/index.ts"
|
|
36
35
|
],
|
|
37
36
|
"skills": [],
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/background-tasks — tinted chat cards
|
|
3
|
+
*
|
|
4
|
+
* Background-task launch/completion messages were plain single lines that got
|
|
5
|
+
* lost in the transcript. These helpers render them as padded, background-
|
|
6
|
+
* tinted boxes (same visual language as pi's tool cards) so a user scanning
|
|
7
|
+
* the chat can spot "a bg task started here" / "it finished here" at a glance.
|
|
8
|
+
*/
|
|
9
|
+
import { Box, Text } from "@earendil-works/pi-tui";
|
|
10
|
+
import { formatDuration, taskDisplayName, type BgTaskSnapshot } from "./types.js";
|
|
11
|
+
|
|
12
|
+
type ThemeLike = {
|
|
13
|
+
fg: (color: string, text: string) => string;
|
|
14
|
+
bg: (color: string, text: string) => string;
|
|
15
|
+
bold: (text: string) => string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type CardTone = "pending" | "success" | "error";
|
|
19
|
+
|
|
20
|
+
const BG_FOR_TONE: Record<CardTone, string> = {
|
|
21
|
+
pending: "toolPendingBg",
|
|
22
|
+
success: "toolSuccessBg",
|
|
23
|
+
error: "toolErrorBg",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function toneFor(status: BgTaskSnapshot["status"]): CardTone {
|
|
27
|
+
if (status === "running") return "pending";
|
|
28
|
+
if (status === "completed") return "success";
|
|
29
|
+
return "error";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function statusGlyph(theme: ThemeLike, status: BgTaskSnapshot["status"]): string {
|
|
33
|
+
if (status === "running") return theme.fg("accent", "●");
|
|
34
|
+
if (status === "completed") return theme.fg("success", "✓");
|
|
35
|
+
if (status === "killed") return theme.fg("warning", "■");
|
|
36
|
+
return theme.fg("error", "✗");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function card(theme: ThemeLike, tone: CardTone, lines: string[]): Box {
|
|
40
|
+
const box = new Box(1, 0, (text) => theme.bg(BG_FOR_TONE[tone], text));
|
|
41
|
+
box.addChild(new Text(lines.join("\n"), 0, 0));
|
|
42
|
+
return box;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** `bg_run` result card: "● bg started <name> (id) · will wake agent" */
|
|
46
|
+
export function renderLaunchCard(theme: ThemeLike, task: BgTaskSnapshot): Box {
|
|
47
|
+
const wake = task.triggerOnCompletion
|
|
48
|
+
? theme.fg("dim", " · wakes agent on completion")
|
|
49
|
+
: task.notifyOnCompletion
|
|
50
|
+
? theme.fg("dim", " · notifies on completion")
|
|
51
|
+
: theme.fg("dim", " · silent");
|
|
52
|
+
const head = `${statusGlyph(theme, "running")} ${theme.fg("accent", theme.bold("bg started"))} ${theme.fg("accent", taskDisplayName(task))} ${theme.fg("dim", `(${task.id})`)}${wake}`;
|
|
53
|
+
const cmd = theme.fg("dim", `$ ${task.command.length > 120 ? `${task.command.slice(0, 120)}…` : task.command}`);
|
|
54
|
+
return card(theme, "pending", [head, cmd]);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Completion notification card: "✓ bg done <name> · exit 0 · 25s" + last output lines */
|
|
58
|
+
export function renderCompletionCard(theme: ThemeLike, task: BgTaskSnapshot | undefined): Box {
|
|
59
|
+
if (task === undefined) {
|
|
60
|
+
return card(theme, "success", [`${theme.fg("success", "✓")} ${theme.fg("accent", theme.bold("bg done"))}`]);
|
|
61
|
+
}
|
|
62
|
+
const tone = toneFor(task.status);
|
|
63
|
+
const label =
|
|
64
|
+
task.status === "completed" ? "bg done" : task.status === "killed" ? "bg stopped" : "bg failed";
|
|
65
|
+
const meta: string[] = [];
|
|
66
|
+
if (task.exitCode !== undefined && task.exitCode !== null) meta.push(`exit ${String(task.exitCode)}`);
|
|
67
|
+
if (task.endTime !== undefined) meta.push(formatDuration(task.endTime - task.startTime));
|
|
68
|
+
if (task.triggerOnCompletion) meta.push("agent woken");
|
|
69
|
+
const head = `${statusGlyph(theme, task.status)} ${theme.fg(tone === "error" ? "error" : "success", theme.bold(label))} ${theme.fg("accent", taskDisplayName(task))} ${theme.fg("dim", `(${task.id})`)}${meta.length > 0 ? theme.fg("dim", ` · ${meta.join(" · ")}`) : ""}`;
|
|
70
|
+
const lines = [head];
|
|
71
|
+
if (task.error) lines.push(theme.fg("error", task.error));
|
|
72
|
+
const tail = task.outputTail ?? [];
|
|
73
|
+
for (const line of tail) lines.push(theme.fg("toolOutput", ` ${line}`));
|
|
74
|
+
lines.push(theme.fg("dim", ` ${task.outputPath}`));
|
|
75
|
+
return card(theme, tone, lines);
|
|
76
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @pi-unipi/background-tasks — Shared child-process & context interfaces
|
|
3
3
|
*
|
|
4
|
-
* Consumed by types.ts, registry.ts,
|
|
4
|
+
* Consumed by types.ts, registry.ts, and tests.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { SpawnOptions } from "node:child_process";
|
|
@@ -34,13 +34,6 @@ export interface BackgroundTasksConfig {
|
|
|
34
34
|
maxToolCalls: number;
|
|
35
35
|
timeoutSeconds: number;
|
|
36
36
|
};
|
|
37
|
-
/** Fusion defaults. */
|
|
38
|
-
fusion: {
|
|
39
|
-
/** Five-slot model selections ('' = $current). */
|
|
40
|
-
candidates: [string, string, string];
|
|
41
|
-
evaluator: string;
|
|
42
|
-
merger: string;
|
|
43
|
-
};
|
|
44
37
|
}
|
|
45
38
|
|
|
46
39
|
export const DEFAULT_CONFIG: BackgroundTasksConfig = {
|
|
@@ -57,11 +50,6 @@ export const DEFAULT_CONFIG: BackgroundTasksConfig = {
|
|
|
57
50
|
maxToolCalls: 120,
|
|
58
51
|
timeoutSeconds: 900,
|
|
59
52
|
},
|
|
60
|
-
fusion: {
|
|
61
|
-
candidates: ["", "", ""],
|
|
62
|
-
evaluator: "",
|
|
63
|
-
merger: "",
|
|
64
|
-
},
|
|
65
53
|
};
|
|
66
54
|
|
|
67
55
|
function getGlobalConfigPath(): string {
|
|
@@ -181,28 +169,6 @@ export function validateBackgroundTasksConfig(config: unknown): string[] {
|
|
|
181
169
|
}
|
|
182
170
|
}
|
|
183
171
|
}
|
|
184
|
-
if (c.fusion !== undefined) {
|
|
185
|
-
if (typeof c.fusion !== "object" || c.fusion === null) {
|
|
186
|
-
problems.push("fusion must be an object");
|
|
187
|
-
} else {
|
|
188
|
-
const f = c.fusion as Record<string, unknown>;
|
|
189
|
-
for (const key of ["candidates", "evaluator", "merger"] as const) {
|
|
190
|
-
const value = f[key];
|
|
191
|
-
if (value === undefined) continue;
|
|
192
|
-
if (key === "candidates") {
|
|
193
|
-
if (
|
|
194
|
-
!Array.isArray(value) ||
|
|
195
|
-
value.length !== 3 ||
|
|
196
|
-
value.some((v) => typeof v !== "string")
|
|
197
|
-
) {
|
|
198
|
-
problems.push("fusion.candidates must be an array of exactly 3 strings");
|
|
199
|
-
}
|
|
200
|
-
} else if (typeof value !== "string") {
|
|
201
|
-
problems.push(`fusion.${key} must be a string`);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
172
|
return problems;
|
|
207
173
|
}
|
|
208
174
|
|
|
@@ -272,14 +238,6 @@ export function loadBackgroundTasksConfig(cwd: string): LoadedBackgroundTasksCon
|
|
|
272
238
|
? merged.delegate.autoDeliver
|
|
273
239
|
: "when_small",
|
|
274
240
|
},
|
|
275
|
-
fusion: {
|
|
276
|
-
candidates:
|
|
277
|
-
Array.isArray(merged.fusion?.candidates) && merged.fusion.candidates.length === 3
|
|
278
|
-
? ([...merged.fusion.candidates] as [string, string, string])
|
|
279
|
-
: DEFAULT_CONFIG.fusion.candidates,
|
|
280
|
-
evaluator: typeof merged.fusion?.evaluator === "string" ? merged.fusion.evaluator : "",
|
|
281
|
-
merger: typeof merged.fusion?.merger === "string" ? merged.fusion.merger : "",
|
|
282
|
-
},
|
|
283
241
|
};
|
|
284
242
|
return { config, warnings };
|
|
285
243
|
}
|
|
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { chmod, mkdir, open, readFile, rename, rm } from 'node:fs/promises';
|
|
4
4
|
import { basename, isAbsolute, join, relative, sep } from 'node:path';
|
|
5
|
-
import { canonicalJson } from '../
|
|
5
|
+
import { canonicalJson } from '../json-utils.js';
|
|
6
6
|
import { sanitizePathSegment } from '../types.js';
|
|
7
7
|
import { replaceFileDurable, writeFileDurable } from '../durable-fs.js';
|
|
8
8
|
import {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "pi-background-tasks.delegate-hook-contract.v1",
|
|
3
|
+
"contract_id": "context-measure-abort-v1+tool-result-spill-v1",
|
|
4
|
+
"guarantees": {
|
|
5
|
+
"context_fires_before_every_model_call": true,
|
|
6
|
+
"context_result_messages_reach_provider": true,
|
|
7
|
+
"context_abort_blocks_provider_call": true,
|
|
8
|
+
"context_abort_skips_stream_invocation": false,
|
|
9
|
+
"context_abort_terminates_run": true,
|
|
10
|
+
"context_throw_blocks_provider_call": false,
|
|
11
|
+
"context_throw_isolated_to_throwing_handler": true,
|
|
12
|
+
"tool_result_fires_before_transcript_entry": true,
|
|
13
|
+
"tool_result_replacement_reaches_provider": true,
|
|
14
|
+
"tool_result_replacement_preserves_identity": true,
|
|
15
|
+
"tool_result_chains_in_load_order": true,
|
|
16
|
+
"handlers_run_in_extension_load_order": true
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
|
+
import { packageAssetSearchHint, resolvePackageAsset } from '../package-assets.js';
|
|
3
4
|
import { mkdir } from 'node:fs/promises';
|
|
4
5
|
import { dirname, join, resolve } from 'node:path';
|
|
5
6
|
import { fileURLToPath } from 'node:url';
|
|
@@ -184,15 +185,20 @@ export function resolveDelegateChildExtensionPath(
|
|
|
184
185
|
moduleUrl = import.meta.url,
|
|
185
186
|
pathExists: (path: string) => boolean = existsSync,
|
|
186
187
|
): string {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const candidate =
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
188
|
+
// Prefer the TypeScript guard (pi loads extensions through jiti); fall back
|
|
189
|
+
// to a compiled copy when one exists.
|
|
190
|
+
const candidate =
|
|
191
|
+
resolvePackageAsset('extensions/delegate-child.ts', { moduleUrl, pathExists }) ??
|
|
192
|
+
resolvePackageAsset('extensions/delegate-child.js', { moduleUrl, pathExists });
|
|
193
|
+
if (candidate === undefined) {
|
|
194
|
+
throw new DelegateError(
|
|
195
|
+
`delegate child extension is missing: ${packageAssetSearchHint('extensions/delegate-child.ts')}`,
|
|
196
|
+
{
|
|
197
|
+
code: 'delegate_isolation_unsupported',
|
|
198
|
+
childCreated: false,
|
|
199
|
+
remediation: ['Reinstall the package; the child guard extension is required to spawn.'],
|
|
200
|
+
},
|
|
201
|
+
);
|
|
196
202
|
}
|
|
197
203
|
return candidate;
|
|
198
204
|
}
|
|
@@ -201,7 +207,7 @@ export function resolveDelegateChildExtensionPath(
|
|
|
201
207
|
* Tools the inspect capability permits.
|
|
202
208
|
*
|
|
203
209
|
* v1 is read-only by construction: no shell, no network, no edit/write, no
|
|
204
|
-
* recursive delegation
|
|
210
|
+
* recursive delegation. The list is passed to `--tools`, so it is
|
|
205
211
|
* enforced by the child's tool registry rather than by prompt text.
|
|
206
212
|
*/
|
|
207
213
|
export const DELEGATE_INSPECT_TOOLS: readonly string[] = [
|
|
@@ -223,12 +229,6 @@ export const DELEGATE_FORBIDDEN_TOOLS: readonly string[] = [
|
|
|
223
229
|
'bg_kill',
|
|
224
230
|
'bg_status',
|
|
225
231
|
'bg_logs',
|
|
226
|
-
'bg_run_pi_attested',
|
|
227
|
-
'fusion_brainstorm',
|
|
228
|
-
'fusion_reason',
|
|
229
|
-
'fusion_investigate',
|
|
230
|
-
'fusion_research',
|
|
231
|
-
'fusion_validate',
|
|
232
232
|
];
|
|
233
233
|
|
|
234
234
|
export function delegateToolsFor(capability: DelegateCapability): readonly string[] {
|
|
@@ -256,7 +256,6 @@ export interface DelegateChildArgvInput {
|
|
|
256
256
|
childSessionId: string;
|
|
257
257
|
childSessionDir: string;
|
|
258
258
|
childExtensionPath: string;
|
|
259
|
-
attributionExtensionPath?: string | undefined;
|
|
260
259
|
systemPrompt: string;
|
|
261
260
|
}
|
|
262
261
|
|
|
@@ -282,19 +281,7 @@ export function buildDelegateChildArgv(input: DelegateChildArgvInput): string[]
|
|
|
282
281
|
);
|
|
283
282
|
}
|
|
284
283
|
}
|
|
285
|
-
const extensionPaths =
|
|
286
|
-
input.route.provider === 'anthropic'
|
|
287
|
-
? [
|
|
288
|
-
input.attributionExtensionPath ??
|
|
289
|
-
(() => {
|
|
290
|
-
throw new DelegateError(
|
|
291
|
-
'Anthropic delegate launch requires the package attribution extension',
|
|
292
|
-
{ code: 'delegate_isolation_unsupported', childCreated: false },
|
|
293
|
-
);
|
|
294
|
-
})(),
|
|
295
|
-
input.childExtensionPath,
|
|
296
|
-
]
|
|
297
|
-
: [input.childExtensionPath];
|
|
284
|
+
const extensionPaths = [input.childExtensionPath];
|
|
298
285
|
|
|
299
286
|
return [
|
|
300
287
|
'--mode',
|