@pi-unipi/unipi 2.20.1 → 2.20.3

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 CHANGED
@@ -6,6 +6,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [2.20.3] — 2026-09-17
10
+
11
+ ### Fixed
12
+
13
+ - `background-tasks`: **distinguish persistent dev servers from finite jobs in `bg_run` prompt guidelines.** The prompt guidance previously grouped dev servers and test suites under the same "do not set `triggerOnCompletion:false`" rule. For persistent servers and watchers (e.g. `npm run dev`, `vite`), `triggerOnCompletion: true` caused the UI to show an indefinite "waiting on 1 bg task" spinner and caused `SidekickRuntime` in Fusion to hang waiting for `openBgTasks` to reach zero. `BgRunParams` and `promptGuidelines` in `packages/background-tasks/src/tools.ts` now explicitly instruct agents to set `triggerOnCompletion: false` for long-running servers/daemons so neither the harness nor sidekicks wait indefinitely for exit.
14
+
15
+ ## [2.20.2] — 2026-09-17
16
+
17
+ ### Fixed
18
+
19
+ - `core`, `background-tasks`, `fusion`: **panes waiting on an auto-resume wake read `working` in herdr, not `idle`.** When the lead's turn ended while background work will re-invoke the agent — a bg task with wake-on-completion, or a Fusion sidekick handoff — herdr's sidebar flipped to `idle` the moment `agent_settled` fired, even as the session's own wake line said "agent resumes automatically when done". New key-scoped claim helper `setHerdrWorking()` in `@pi-unipi/core` emits `herdr:working {active,label}` exactly once per claim transition (the same refcounted event protocol `herdr:blocked` uses); `background-tasks` holds the claim while its bg-wake line is installed and `fusion` holds it while the sidekick wake line is up. Consumed by a one-listener patch to herdr's pi integration (held-working refcount in `desiredState()`, `blocked` still outranks it) — herdr enforces one status authority per pane and ignores second-source `pane.report_agent` reports, so the event protocol is the only channel. Until herdr upstreams the protocol, the patched integration file is local (`herdr integration install pi` reverts it; pristine v8 backup at `~/.pi/agent/herdr-agent-state.v8.orig.bak`).
20
+
9
21
  ## [2.20.1] — 2026-09-17
10
22
 
11
23
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/unipi",
3
- "version": "2.20.1",
3
+ "version": "2.20.3",
4
4
  "description": "All-in-one extension suite for Pi coding agent",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -84,29 +84,29 @@
84
84
  "typebox": "^1.1.38"
85
85
  },
86
86
  "dependencies": {
87
- "@pi-unipi/ask-user": "2.20.1",
88
- "@pi-unipi/background-tasks": "2.20.1",
89
- "@pi-unipi/btw": "2.20.1",
90
- "@pi-unipi/command-enchantment": "2.20.1",
91
- "@pi-unipi/compactor": "2.20.1",
92
- "@pi-unipi/core": "2.20.1",
93
- "@pi-unipi/footer": "2.20.1",
94
- "@pi-unipi/image": "2.20.1",
95
- "@pi-unipi/fusion": "2.20.1",
96
- "@pi-unipi/info-screen": "2.20.1",
97
- "@pi-unipi/input-shortcuts": "2.20.1",
98
- "@pi-unipi/kanboard": "2.20.1",
99
- "@pi-unipi/mcp": "2.20.1",
100
- "@pi-unipi/memory": "2.20.1",
101
- "@pi-unipi/milestone": "2.20.1",
102
- "@pi-unipi/notify": "2.20.1",
103
- "@pi-unipi/ralph": "2.20.1",
104
- "@pi-unipi/subagents": "2.20.1",
105
- "@pi-unipi/trajectory": "2.20.1",
106
- "@pi-unipi/updater": "2.20.1",
107
- "@pi-unipi/utility": "2.20.1",
108
- "@pi-unipi/web-api": "2.20.1",
109
- "@pi-unipi/workflow": "2.20.1"
87
+ "@pi-unipi/ask-user": "2.20.3",
88
+ "@pi-unipi/background-tasks": "2.20.3",
89
+ "@pi-unipi/btw": "2.20.3",
90
+ "@pi-unipi/command-enchantment": "2.20.3",
91
+ "@pi-unipi/compactor": "2.20.3",
92
+ "@pi-unipi/core": "2.20.3",
93
+ "@pi-unipi/footer": "2.20.3",
94
+ "@pi-unipi/image": "2.20.3",
95
+ "@pi-unipi/fusion": "2.20.3",
96
+ "@pi-unipi/info-screen": "2.20.3",
97
+ "@pi-unipi/input-shortcuts": "2.20.3",
98
+ "@pi-unipi/kanboard": "2.20.3",
99
+ "@pi-unipi/mcp": "2.20.3",
100
+ "@pi-unipi/memory": "2.20.3",
101
+ "@pi-unipi/milestone": "2.20.3",
102
+ "@pi-unipi/notify": "2.20.3",
103
+ "@pi-unipi/ralph": "2.20.3",
104
+ "@pi-unipi/subagents": "2.20.3",
105
+ "@pi-unipi/trajectory": "2.20.3",
106
+ "@pi-unipi/updater": "2.20.3",
107
+ "@pi-unipi/utility": "2.20.3",
108
+ "@pi-unipi/web-api": "2.20.3",
109
+ "@pi-unipi/workflow": "2.20.3"
110
110
  },
111
111
  "devDependencies": {
112
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.20.1",
3
+ "version": "2.20.3",
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.20.1"
43
+ "@pi-unipi/core": "2.20.3"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@earendil-works/pi-coding-agent": "^0.84.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/command-enchantment",
3
- "version": "2.20.1",
3
+ "version": "2.20.3",
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/background-tasks",
3
- "version": "2.20.1",
3
+ "version": "2.20.3",
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.84.0",
13
- "@pi-unipi/core": "2.20.1",
13
+ "@pi-unipi/core": "2.20.3",
14
14
  "turndown": "^7.2.4"
15
15
  },
16
16
  "devDependencies": {
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
13
- import { createSpinnerLine } from "@pi-unipi/core";
13
+ import { createSpinnerLine, setHerdrWorking } from "@pi-unipi/core";
14
14
  import { loadBackgroundTasksConfig } from "./config.js";
15
15
  import { BackgroundTaskRegistry } from "./registry.js";
16
16
  import {
@@ -134,9 +134,21 @@ export default function backgroundTasksExtension(pi: ExtensionAPI): void {
134
134
  { placement: "aboveEditor" },
135
135
  );
136
136
  wakeLineInstalled = true;
137
+ // The pane is not done: a running task will re-invoke the agent. Keep
138
+ // herdr at `working` instead of `idle` for exactly as long as the wake
139
+ // line is up (one claim transition per widget install, not per tick).
140
+ const wakeCount = registry
141
+ .allTasks()
142
+ .filter((task) => task.status === "running" && task.triggerOnCompletion).length;
143
+ setHerdrWorking(
144
+ pi,
145
+ "bg-wake",
146
+ `${wakeCount === 1 ? "1 bg task" : `${String(wakeCount)} bg tasks`} will resume agent`,
147
+ );
137
148
  } else if (!wantWakeLine && wakeLineInstalled) {
138
149
  target.ui.setWidget("background-tasks", undefined);
139
150
  wakeLineInstalled = false;
151
+ setHerdrWorking(pi, "bg-wake", null);
140
152
  }
141
153
  if (running.length === 0 && unseenFinishedCount === 0) {
142
154
  target.ui.setStatus("background-tasks", undefined);
@@ -52,13 +52,13 @@ export const BgRunParams = Type.Object({
52
52
  notifyOnCompletion: Type.Optional(
53
53
  Type.Boolean({
54
54
  description:
55
- "Deliver a durable terminal notification when the task finishes. Default true. Do not disable unless opting out of completion handling.",
55
+ "Deliver a durable terminal notification when the task finishes. Default true for finite tasks. Set false or pair with triggerOnCompletion:false for persistent servers/daemons/watchers.",
56
56
  }),
57
57
  ),
58
58
  triggerOnCompletion: Type.Optional(
59
59
  Type.Boolean({
60
60
  description:
61
- "Start a follow-up agent turn on terminal notification. Default true for bg_run. Requires notifyOnCompletion.",
61
+ "Start a follow-up agent turn on terminal notification. Default true for finite tasks (tests, builds, exports). MUST set false for persistent background services/servers (e.g. dev servers, watchers) so the harness and subagents do not wait indefinitely for the server to finish. Requires notifyOnCompletion.",
62
62
  }),
63
63
  ),
64
64
  });
@@ -185,14 +185,14 @@ export function registerToolsAndCommands(options: RegisterSurfaceOptions): void
185
185
  promptSnippet:
186
186
  "Start a named long-running shell command; default terminal notification wakes a follow-up turn, so yield instead of polling",
187
187
  promptGuidelines: [
188
- "Use bg_run instead of bash for commands expected to run for a long time, such as test suites, dev servers, watchers, or builds.",
188
+ "Use bg_run instead of bash for commands expected to run in the background (test suites, builds, dev servers, watchers).",
189
+ "For FINITE tasks (tests, builds, exports, scripts): leave triggerOnCompletion:true and notifyOnCompletion:true (defaults) to automatically wake the agent when finished.",
190
+ "For PERSISTENT servers/daemons/watchers (e.g. 'npm run dev', vite, live dev servers, background listeners): MUST set triggerOnCompletion:false (and optionally notifyOnCompletion:false) so the agent and sidekick do not wait for the server to exit.",
189
191
  "Set isAgent:true only when the background task launches an LLM/agent process; false for scripts, tests, dev servers, sleeps.",
190
192
  "Always set name to a concise 2-6 word human-readable label; do not use the raw command as the name.",
191
- "bg_run returns immediately. With notifyOnCompletion:true and triggerOnCompletion:true (both defaults), completed, failed, or killed terminal state is delivered as <background-task-notification> and automatically starts a follow-up agent turn.",
192
- "After a default bg_run launch, continue only independent useful work that does not merely wait for the task; otherwise briefly acknowledge it if useful, then end the current turn. Do not call sleep, bg_status, or bg_logs merely to wait; the terminal notification will wake you.",
193
+ "bg_run returns immediately. For finite tasks with default wake-up, continue only independent useful work that does not merely wait for the task; otherwise end the current turn. Do not call sleep, bg_status, or bg_logs merely to wait; the terminal notification will wake you.",
193
194
  "A running result is not an instruction to poll again.",
194
195
  "Treat <background-task-notification> as durable terminal truth. Do not call bg_status to reconfirm it.",
195
- "Do not set notifyOnCompletion:false or triggerOnCompletion:false unless intentionally opting out.",
196
196
  ],
197
197
  parameters: BgRunParams,
198
198
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/btw",
3
- "version": "2.20.1",
3
+ "version": "2.20.3",
4
4
  "description": "A pi extension for parallel side conversations with /unipi:btw — part of the Unipi suite",
5
5
  "type": "module",
6
6
  "main": "extensions/btw.ts",
@@ -37,7 +37,7 @@
37
37
  "@earendil-works/pi-tui": "^0.84.0"
38
38
  },
39
39
  "dependencies": {
40
- "@pi-unipi/core": "2.20.1"
40
+ "@pi-unipi/core": "2.20.3"
41
41
  },
42
42
  "pi": {
43
43
  "extensions": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/compactor",
3
- "version": "2.20.1",
3
+ "version": "2.20.3",
4
4
  "description": "Context engine for Pi — zero-LLM compaction, session continuity, sandbox execution, and tool display optimization",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -34,8 +34,8 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
- "@pi-unipi/core": "2.20.1",
38
- "@pi-unipi/info-screen": "2.20.1",
37
+ "@pi-unipi/core": "2.20.3",
38
+ "@pi-unipi/info-screen": "2.20.3",
39
39
  "@earendil-works/pi-agent-core": "^0.84.0"
40
40
  },
41
41
  "peerDependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/core",
3
- "version": "2.20.1",
3
+ "version": "2.20.3",
4
4
  "description": "Shared utilities, event types, and constants for Unipi extension suite",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -304,6 +304,42 @@ export async function withHerdrBlocked<T>(
304
304
  }
305
305
  }
306
306
 
307
+ /**
308
+ * Claim herdr `working` state for a long-lived "the agent will auto-resume" wait
309
+ * (bg task wake, fusion sidekick handoff) — the states where pi has already
310
+ * settled but background work holds a pending wake.
311
+ *
312
+ * Key-scoped and level-based, not a wrapper: each key claims exactly once and
313
+ * emits `herdr:working` {active:true,label} on claim, {active:false,label} on
314
+ * clear — the same refcounted protocol `herdr:blocked` uses, so the herdr pi
315
+ * integration can keep the pane `working` after agent_settled.
316
+ *
317
+ * - label !== null && no previous claim → emit active
318
+ * - label !== null && same label → no emit (still claimed)
319
+ * - label !== null && different label → emit inactive(previous) then active
320
+ * - label === null && claimed → emit inactive, drop claim
321
+ * - label === null && not claimed → no emit
322
+ */
323
+ const herdrWorkingClaims = new Map<string, string>();
324
+
325
+ export function setHerdrWorking(
326
+ pi: { events: { emit: (name: string, payload: unknown) => void } },
327
+ key: string,
328
+ label: string | null,
329
+ ): void {
330
+ const previous = herdrWorkingClaims.get(key);
331
+ if (label === null) {
332
+ if (previous === undefined) return;
333
+ herdrWorkingClaims.delete(key);
334
+ emitEvent(pi, "herdr:working", { active: false, label: previous });
335
+ return;
336
+ }
337
+ if (previous === label) return;
338
+ if (previous !== undefined) emitEvent(pi, "herdr:working", { active: false, label: previous });
339
+ herdrWorkingClaims.set(key, label);
340
+ emitEvent(pi, "herdr:working", { active: true, label });
341
+ }
342
+
307
343
  /** Format a token count for display (e.g. 1234 → "1.2k", 1500000 → "1.5M"). */
308
344
  export function formatTokens(n: number): string {
309
345
  if (n < 1000) return String(n);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/footer",
3
- "version": "2.20.1",
3
+ "version": "2.20.3",
4
4
  "description": "Persistent status bar for Unipi — subscribes to UNIPI_EVENTS and renders key stats from all unipi packages",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -32,8 +32,8 @@
32
32
  "access": "public"
33
33
  },
34
34
  "dependencies": {
35
- "@pi-unipi/core": "2.20.1",
36
- "@pi-unipi/background-tasks": "2.20.1"
35
+ "@pi-unipi/core": "2.20.3",
36
+ "@pi-unipi/background-tasks": "2.20.3"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@earendil-works/pi-coding-agent": "^0.84.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/fusion",
3
- "version": "2.20.1",
3
+ "version": "2.20.3",
4
4
  "description": "Devin-style model picker, fusion presets (lead + sidekick), and Local Fusion runtime for UniPi",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -32,8 +32,8 @@
32
32
  "access": "public"
33
33
  },
34
34
  "dependencies": {
35
- "@pi-unipi/core": "2.20.1",
36
- "@pi-unipi/subagents": "2.20.1"
35
+ "@pi-unipi/core": "2.20.3",
36
+ "@pi-unipi/subagents": "2.20.3"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@earendil-works/pi-ai": "^0.84.0",
@@ -16,7 +16,7 @@
16
16
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
17
17
  import type { Api, Model } from "@earendil-works/pi-ai";
18
18
  import type { AutocompleteProvider, AutocompleteSuggestions } from "@earendil-works/pi-tui";
19
- import { createSpinnerLine, setSharedFusionStatus, UNIPI_PREFIX } from "@pi-unipi/core";
19
+ import { createSpinnerLine, setHerdrWorking, setSharedFusionStatus, UNIPI_PREFIX } from "@pi-unipi/core";
20
20
  import { homedir } from "node:os";
21
21
  import { join } from "node:path";
22
22
  import {
@@ -85,6 +85,8 @@ export interface SidekickWakeLine {
85
85
  export function createSidekickWakeLine(deps: {
86
86
  isBusy: () => boolean;
87
87
  progress: () => { toolCalls: number; startedAt: number } | undefined;
88
+ /** Claim hook: called with a label when the line goes up, null when it comes down. */
89
+ onHeld?: (label: string | null) => void;
88
90
  }): SidekickWakeLine {
89
91
  let installed = false;
90
92
 
@@ -102,14 +104,17 @@ export function createSidekickWakeLine(deps: {
102
104
  { placement: "aboveEditor" },
103
105
  );
104
106
  installed = true;
107
+ deps.onHeld?.("sidekick working — resumes automatically");
105
108
  } else if (!want && installed) {
106
109
  ctx.ui.setWidget(SIDEKICK_WAKE_WIDGET_KEY, undefined);
107
110
  installed = false;
111
+ deps.onHeld?.(null);
108
112
  }
109
113
  },
110
114
  clear(ctx) {
111
115
  if (!installed) return;
112
116
  installed = false;
117
+ deps.onHeld?.(null);
113
118
  if (!removable(ctx)) return;
114
119
  ctx.ui.setWidget(SIDEKICK_WAKE_WIDGET_KEY, undefined);
115
120
  },
@@ -190,6 +195,10 @@ export default function fusionExtension(pi: ExtensionAPI): void {
190
195
  const wakeLine = createSidekickWakeLine({
191
196
  isBusy: () => runtime?.isBusy() === true,
192
197
  progress: () => runtime?.progress(),
198
+ // While the wake line is up the pane must read `working` in herdr (the
199
+ // sidekick will re-invoke the lead), not `idle` — same claim the bg-tasks
200
+ // wake line makes.
201
+ onHeld: (label) => setHerdrWorking(pi, "fusion-sidekick", label),
193
202
  });
194
203
 
195
204
  function identity(ctx: ExtensionContext): FusionIdentity {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/image",
3
- "version": "2.20.1",
3
+ "version": "2.20.3",
4
4
  "description": "Image generation and image recognition tools for the Pi coding agent",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -34,7 +34,7 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
- "@pi-unipi/core": "2.20.1"
37
+ "@pi-unipi/core": "2.20.3"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@earendil-works/pi-ai": "^0.84.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/info-screen",
3
- "version": "2.20.1",
3
+ "version": "2.20.3",
4
4
  "description": "Dashboard and module registry for Unipi — configurable info overlay with tabbed groups",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -33,7 +33,7 @@
33
33
  "access": "public"
34
34
  },
35
35
  "dependencies": {
36
- "@pi-unipi/core": "2.20.1"
36
+ "@pi-unipi/core": "2.20.3"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@earendil-works/pi-coding-agent": "^0.84.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/input-shortcuts",
3
- "version": "2.20.1",
3
+ "version": "2.20.3",
4
4
  "description": "Keyboard shortcuts for stash/restore, undo/redo, clipboard, and thinking toggle — chord-based overlay system",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -33,7 +33,7 @@
33
33
  "access": "public"
34
34
  },
35
35
  "dependencies": {
36
- "@pi-unipi/core": "2.20.1"
36
+ "@pi-unipi/core": "2.20.3"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@earendil-works/pi-coding-agent": "^0.84.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/kanboard",
3
- "version": "2.20.1",
3
+ "version": "2.20.3",
4
4
  "description": "Visualization layer for unipi workflow — HTTP server with htmx/Alpine.js UI, modular parsers, TUI overlay, and kanban board",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -39,7 +39,7 @@
39
39
  "access": "public"
40
40
  },
41
41
  "dependencies": {
42
- "@pi-unipi/core": "2.20.1"
42
+ "@pi-unipi/core": "2.20.3"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "@earendil-works/pi-coding-agent": "^0.84.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/mcp",
3
- "version": "2.20.1",
3
+ "version": "2.20.3",
4
4
  "description": "MCP server management extension for Pi coding agent — browse, add, configure, and use MCP servers",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -30,7 +30,7 @@
30
30
  "test": "npx tsx --test tests/**/*.test.ts"
31
31
  },
32
32
  "dependencies": {
33
- "@pi-unipi/core": "2.20.1"
33
+ "@pi-unipi/core": "2.20.3"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "@earendil-works/pi-coding-agent": "^0.84.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/memory",
3
- "version": "2.20.1",
3
+ "version": "2.20.3",
4
4
  "description": "Persistent cross-session memory with MemPalace backend (auto-installed) and SQLite fallback for Pi coding agent",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -39,8 +39,8 @@
39
39
  "README.md"
40
40
  ],
41
41
  "dependencies": {
42
- "@pi-unipi/core": "2.20.1",
43
- "@pi-unipi/info-screen": "2.20.1",
42
+ "@pi-unipi/core": "2.20.3",
43
+ "@pi-unipi/info-screen": "2.20.3",
44
44
  "js-yaml": "^4.1.0"
45
45
  },
46
46
  "peerDependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/milestone",
3
- "version": "2.20.1",
3
+ "version": "2.20.3",
4
4
  "description": "Lifecycle layer for project-level goals — MILESTONES.md tracking, session hooks, auto-sync",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -32,7 +32,7 @@
32
32
  "access": "public"
33
33
  },
34
34
  "dependencies": {
35
- "@pi-unipi/core": "2.20.1"
35
+ "@pi-unipi/core": "2.20.3"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@earendil-works/pi-coding-agent": "^0.84.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/notify",
3
- "version": "2.20.1",
3
+ "version": "2.20.3",
4
4
  "description": "Cross-platform notification extension for Pi — native OS, Gotify, and Telegram notifications for agent lifecycle events",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -34,7 +34,7 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
- "@pi-unipi/core": "2.20.1",
37
+ "@pi-unipi/core": "2.20.3",
38
38
  "node-notifier": "^10.0.1"
39
39
  },
40
40
  "peerDependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/ralph",
3
- "version": "2.20.1",
3
+ "version": "2.20.3",
4
4
  "description": "Long-running iterative development loops for Pi coding agent",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -31,8 +31,8 @@
31
31
  "test": "npx tsx --test reminder.test.ts"
32
32
  },
33
33
  "dependencies": {
34
- "@pi-unipi/core": "2.20.1",
35
- "@pi-unipi/info-screen": "2.20.1"
34
+ "@pi-unipi/core": "2.20.3",
35
+ "@pi-unipi/info-screen": "2.20.3"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@earendil-works/pi-ai": "^0.84.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/subagents",
3
- "version": "2.20.1",
3
+ "version": "2.20.3",
4
4
  "description": "Subagents for UniPi — parallel execution, file locking, workflow integration",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -9,7 +9,7 @@
9
9
  "test": "npx tsx --test src/__tests__/*.test.ts"
10
10
  },
11
11
  "dependencies": {
12
- "@pi-unipi/core": "2.20.1",
12
+ "@pi-unipi/core": "2.20.3",
13
13
  "@earendil-works/pi-agent-core": "^0.84.0",
14
14
  "acorn": "8.18.0"
15
15
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/trajectory",
3
- "version": "2.20.1",
3
+ "version": "2.20.3",
4
4
  "description": "Live UniPi trajectory inspector for Pi sessions",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -624,6 +624,19 @@ async function withHerdrBlocked(pi, label, fn) {
624
624
  event(false);
625
625
  }
626
626
  }
627
+ function setHerdrWorking(pi, key, label) {
628
+ const previous = herdrWorkingClaims.get(key);
629
+ if (label === null) {
630
+ if (previous === void 0) return;
631
+ herdrWorkingClaims.delete(key);
632
+ emitEvent(pi, "herdr:working", { active: false, label: previous });
633
+ return;
634
+ }
635
+ if (previous === label) return;
636
+ if (previous !== void 0) emitEvent(pi, "herdr:working", { active: false, label: previous });
637
+ herdrWorkingClaims.set(key, label);
638
+ emitEvent(pi, "herdr:working", { active: true, label });
639
+ }
627
640
  function formatTokens(n) {
628
641
  if (n < 1e3) return String(n);
629
642
  if (n < 1e4) return `${(n / 1e3).toFixed(1)}k`;
@@ -652,11 +665,12 @@ function compareVersions(a, b) {
652
665
  function isNewerVersion(latest, current) {
653
666
  return compareVersions(latest, current) > 0;
654
667
  }
655
- var cachedPiVersion;
668
+ var cachedPiVersion, herdrWorkingClaims;
656
669
  var init_utils = __esm({
657
670
  "packages/core/utils.ts"() {
658
671
  "use strict";
659
672
  cachedPiVersion = null;
673
+ herdrWorkingClaims = /* @__PURE__ */ new Map();
660
674
  }
661
675
  });
662
676
 
@@ -1102,6 +1116,7 @@ __export(core_exports, {
1102
1116
  safeRepeat: () => safeRepeat,
1103
1117
  safeRepeatCount: () => safeRepeatCount,
1104
1118
  sanitize: () => sanitize,
1119
+ setHerdrWorking: () => setHerdrWorking,
1105
1120
  setSharedFusionStatus: () => setSharedFusionStatus,
1106
1121
  shouldRenderBorder: () => shouldRenderBorder,
1107
1122
  tryDelete: () => tryDelete,
@@ -22189,12 +22204,12 @@ var BgRunParams = Type4.Object({
22189
22204
  ),
22190
22205
  notifyOnCompletion: Type4.Optional(
22191
22206
  Type4.Boolean({
22192
- description: "Deliver a durable terminal notification when the task finishes. Default true. Do not disable unless opting out of completion handling."
22207
+ description: "Deliver a durable terminal notification when the task finishes. Default true for finite tasks. Set false or pair with triggerOnCompletion:false for persistent servers/daemons/watchers."
22193
22208
  })
22194
22209
  ),
22195
22210
  triggerOnCompletion: Type4.Optional(
22196
22211
  Type4.Boolean({
22197
- description: "Start a follow-up agent turn on terminal notification. Default true for bg_run. Requires notifyOnCompletion."
22212
+ description: "Start a follow-up agent turn on terminal notification. Default true for finite tasks (tests, builds, exports). MUST set false for persistent background services/servers (e.g. dev servers, watchers) so the harness and subagents do not wait indefinitely for the server to finish. Requires notifyOnCompletion."
22198
22213
  })
22199
22214
  )
22200
22215
  });
@@ -22284,14 +22299,14 @@ Command: ${task.command}`,
22284
22299
  description: `Start a named long-running shell command in the background and return immediately with a task ID and output path. By default, terminal state is delivered automatically as <background-task-notification> and starts a follow-up agent turn; do not sleep or poll merely to wait. Output is written under the OS temp root and model-visible logs are bounded.`,
22285
22300
  promptSnippet: "Start a named long-running shell command; default terminal notification wakes a follow-up turn, so yield instead of polling",
22286
22301
  promptGuidelines: [
22287
- "Use bg_run instead of bash for commands expected to run for a long time, such as test suites, dev servers, watchers, or builds.",
22302
+ "Use bg_run instead of bash for commands expected to run in the background (test suites, builds, dev servers, watchers).",
22303
+ "For FINITE tasks (tests, builds, exports, scripts): leave triggerOnCompletion:true and notifyOnCompletion:true (defaults) to automatically wake the agent when finished.",
22304
+ "For PERSISTENT servers/daemons/watchers (e.g. 'npm run dev', vite, live dev servers, background listeners): MUST set triggerOnCompletion:false (and optionally notifyOnCompletion:false) so the agent and sidekick do not wait for the server to exit.",
22288
22305
  "Set isAgent:true only when the background task launches an LLM/agent process; false for scripts, tests, dev servers, sleeps.",
22289
22306
  "Always set name to a concise 2-6 word human-readable label; do not use the raw command as the name.",
22290
- "bg_run returns immediately. With notifyOnCompletion:true and triggerOnCompletion:true (both defaults), completed, failed, or killed terminal state is delivered as <background-task-notification> and automatically starts a follow-up agent turn.",
22291
- "After a default bg_run launch, continue only independent useful work that does not merely wait for the task; otherwise briefly acknowledge it if useful, then end the current turn. Do not call sleep, bg_status, or bg_logs merely to wait; the terminal notification will wake you.",
22307
+ "bg_run returns immediately. For finite tasks with default wake-up, continue only independent useful work that does not merely wait for the task; otherwise end the current turn. Do not call sleep, bg_status, or bg_logs merely to wait; the terminal notification will wake you.",
22292
22308
  "A running result is not an instruction to poll again.",
22293
- "Treat <background-task-notification> as durable terminal truth. Do not call bg_status to reconfirm it.",
22294
- "Do not set notifyOnCompletion:false or triggerOnCompletion:false unless intentionally opting out."
22309
+ "Treat <background-task-notification> as durable terminal truth. Do not call bg_status to reconfirm it."
22295
22310
  ],
22296
22311
  parameters: BgRunParams,
22297
22312
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
@@ -25680,9 +25695,16 @@ function backgroundTasksExtension(pi) {
25680
25695
  { placement: "aboveEditor" }
25681
25696
  );
25682
25697
  wakeLineInstalled = true;
25698
+ const wakeCount = registry3.allTasks().filter((task) => task.status === "running" && task.triggerOnCompletion).length;
25699
+ setHerdrWorking(
25700
+ pi,
25701
+ "bg-wake",
25702
+ `${wakeCount === 1 ? "1 bg task" : `${String(wakeCount)} bg tasks`} will resume agent`
25703
+ );
25683
25704
  } else if (!wantWakeLine && wakeLineInstalled) {
25684
25705
  target.ui.setWidget("background-tasks", void 0);
25685
25706
  wakeLineInstalled = false;
25707
+ setHerdrWorking(pi, "bg-wake", null);
25686
25708
  }
25687
25709
  if (running.length === 0 && unseenFinishedCount === 0) {
25688
25710
  target.ui.setStatus("background-tasks", void 0);
@@ -52125,14 +52147,17 @@ function createSidekickWakeLine(deps) {
52125
52147
  { placement: "aboveEditor" }
52126
52148
  );
52127
52149
  installed = true;
52150
+ deps.onHeld?.("sidekick working \u2014 resumes automatically");
52128
52151
  } else if (!want && installed) {
52129
52152
  ctx.ui.setWidget(SIDEKICK_WAKE_WIDGET_KEY, void 0);
52130
52153
  installed = false;
52154
+ deps.onHeld?.(null);
52131
52155
  }
52132
52156
  },
52133
52157
  clear(ctx) {
52134
52158
  if (!installed) return;
52135
52159
  installed = false;
52160
+ deps.onHeld?.(null);
52136
52161
  if (!removable(ctx)) return;
52137
52162
  ctx.ui.setWidget(SIDEKICK_WAKE_WIDGET_KEY, void 0);
52138
52163
  }
@@ -52198,7 +52223,11 @@ function fusionExtension(pi) {
52198
52223
  let bashStreak = 0;
52199
52224
  const wakeLine = createSidekickWakeLine({
52200
52225
  isBusy: () => runtime?.isBusy() === true,
52201
- progress: () => runtime?.progress()
52226
+ progress: () => runtime?.progress(),
52227
+ // While the wake line is up the pane must read `working` in herdr (the
52228
+ // sidekick will re-invoke the lead), not `idle` — same claim the bg-tasks
52229
+ // wake line makes.
52230
+ onHeld: (label) => setHerdrWorking(pi, "fusion-sidekick", label)
52202
52231
  });
52203
52232
  function identity(ctx) {
52204
52233
  const reg = registryOf(ctx);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/updater",
3
- "version": "2.20.1",
3
+ "version": "2.20.3",
4
4
  "description": "Auto-updater, changelog browser, and readme browser for Unipi — checks npm registry, renders CHANGELOG.md and README.md files in TUI overlays",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -34,7 +34,7 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
- "@pi-unipi/core": "2.20.1"
37
+ "@pi-unipi/core": "2.20.3"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@earendil-works/pi-coding-agent": "^0.84.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/utility",
3
- "version": "2.20.1",
3
+ "version": "2.20.3",
4
4
  "description": "Utility commands and tools for Pi coding agent — lifecycle, diagnostics, cache, analytics, display, batch execution",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -36,7 +36,7 @@
36
36
  "access": "public"
37
37
  },
38
38
  "dependencies": {
39
- "@pi-unipi/core": "2.20.1"
39
+ "@pi-unipi/core": "2.20.3"
40
40
  },
41
41
  "devDependencies": {},
42
42
  "peerDependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/web-api",
3
- "version": "2.20.1",
3
+ "version": "2.20.3",
4
4
  "description": "Web search, read, and summarize tools with provider-based backend selection for Pi coding agent",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -31,7 +31,7 @@
31
31
  "README.md"
32
32
  ],
33
33
  "dependencies": {
34
- "@pi-unipi/core": "2.20.1",
34
+ "@pi-unipi/core": "2.20.3",
35
35
  "defuddle": "^0.18.1",
36
36
  "linkedom": "^0.18.12",
37
37
  "wreq-js": "^2.3.0"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/workflow",
3
- "version": "2.20.1",
3
+ "version": "2.20.3",
4
4
  "description": "Structured development workflow commands for Pi coding agent",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -30,7 +30,7 @@
30
30
  "access": "public"
31
31
  },
32
32
  "dependencies": {
33
- "@pi-unipi/core": "2.20.1"
33
+ "@pi-unipi/core": "2.20.3"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "@earendil-works/pi-ai": "^0.84.0",