@pi-unipi/unipi 3.0.0-alpha.28 → 3.0.0-alpha.29

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,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [3.0.0-alpha.29] — 2026-10-04
10
+
11
+ ### Fixed
12
+ - `notify`: prompts that send no event of their own (third-party `ask_user` tools, the permission prompt, the plan review) can notify through the new **Input Needed** event (`input_needed`, off by default); closing any prompt now stops its reminders. (GitHub #38)
13
+
9
14
  ## [3.0.0-alpha.28] — 2026-10-04
10
15
 
11
16
  ### Changed
package/README.md CHANGED
@@ -249,6 +249,7 @@ The recent releases, in short. The [changelog](CHANGELOG.md) has the details.
249
249
 
250
250
  | Release | What changed for you |
251
251
  |---|---|
252
+ | alpha.29 | Plan mode plans with you: it asks before it submits, blocks only edits outside the plan, and the review shows a summary beside the steps. Notify can alert on any prompt the agent waits on (Input Needed). |
252
253
  | alpha.27 | The footer shows tokens in and out and the session cost under the input. It fits narrow and short windows. Each part of the stats line, the frame badges and the rainbow can be turned off in `/unipi:settings` → Footer. The classic footer is gone. |
253
254
  | alpha.25 | The Unicrab splash at startup. A new `/unipi:info`: this session, the context bucket, compaction savings and `s`/`p`/`g` scope tags. Prompts that UniPi sends for you (goal, summarize, answer) show as a UniPi panel, not as your own message. |
254
255
  | alpha.24 | The `summarize` skill and `/unipi:summarize [focus]`. Long work ends with a short recap: the answer first, then findings, open items and questions. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/unipi",
3
- "version": "3.0.0-alpha.28",
3
+ "version": "3.0.0-alpha.29",
4
4
  "description": "All-in-one extension suite for Pi coding agent",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -83,26 +83,26 @@
83
83
  "typebox": "^1.1.38"
84
84
  },
85
85
  "dependencies": {
86
- "@pi-unipi/ask-user": "3.0.0-alpha.28",
87
- "@pi-unipi/background-tasks": "3.0.0-alpha.28",
88
- "@pi-unipi/btw": "3.0.0-alpha.28",
89
- "@pi-unipi/command-enchantment": "3.0.0-alpha.28",
90
- "@pi-unipi/compactor": "3.0.0-alpha.28",
91
- "@pi-unipi/core": "3.0.0-alpha.28",
92
- "@pi-unipi/footer": "3.0.0-alpha.28",
93
- "@pi-unipi/fusion": "3.0.0-alpha.28",
94
- "@pi-unipi/info-screen": "3.0.0-alpha.28",
95
- "@pi-unipi/input-shortcuts": "3.0.0-alpha.28",
96
- "@pi-unipi/kanboard": "3.0.0-alpha.28",
97
- "@pi-unipi/mcp": "3.0.0-alpha.28",
98
- "@pi-unipi/memory": "3.0.0-alpha.28",
99
- "@pi-unipi/notify": "3.0.0-alpha.28",
100
- "@pi-unipi/skill-registry": "3.0.0-alpha.28",
101
- "@pi-unipi/subagents": "3.0.0-alpha.28",
102
- "@pi-unipi/updater": "3.0.0-alpha.28",
103
- "@pi-unipi/utility": "3.0.0-alpha.28",
104
- "@pi-unipi/web-api": "3.0.0-alpha.28",
105
- "@pi-unipi/workflow": "3.0.0-alpha.28"
86
+ "@pi-unipi/ask-user": "3.0.0-alpha.29",
87
+ "@pi-unipi/background-tasks": "3.0.0-alpha.29",
88
+ "@pi-unipi/btw": "3.0.0-alpha.29",
89
+ "@pi-unipi/command-enchantment": "3.0.0-alpha.29",
90
+ "@pi-unipi/compactor": "3.0.0-alpha.29",
91
+ "@pi-unipi/core": "3.0.0-alpha.29",
92
+ "@pi-unipi/footer": "3.0.0-alpha.29",
93
+ "@pi-unipi/fusion": "3.0.0-alpha.29",
94
+ "@pi-unipi/info-screen": "3.0.0-alpha.29",
95
+ "@pi-unipi/input-shortcuts": "3.0.0-alpha.29",
96
+ "@pi-unipi/kanboard": "3.0.0-alpha.29",
97
+ "@pi-unipi/mcp": "3.0.0-alpha.29",
98
+ "@pi-unipi/memory": "3.0.0-alpha.29",
99
+ "@pi-unipi/notify": "3.0.0-alpha.29",
100
+ "@pi-unipi/skill-registry": "3.0.0-alpha.29",
101
+ "@pi-unipi/subagents": "3.0.0-alpha.29",
102
+ "@pi-unipi/updater": "3.0.0-alpha.29",
103
+ "@pi-unipi/utility": "3.0.0-alpha.29",
104
+ "@pi-unipi/web-api": "3.0.0-alpha.29",
105
+ "@pi-unipi/workflow": "3.0.0-alpha.29"
106
106
  },
107
107
  "devDependencies": {
108
108
  "@earendil-works/pi-agent-core": "^0.87.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/ask-user",
3
- "version": "3.0.0-alpha.28",
3
+ "version": "3.0.0-alpha.29",
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.28"
43
+ "@pi-unipi/core": "3.0.0-alpha.29"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/command-enchantment",
3
- "version": "3.0.0-alpha.28",
3
+ "version": "3.0.0-alpha.29",
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": "3.0.0-alpha.28",
3
+ "version": "3.0.0-alpha.29",
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.28",
13
+ "@pi-unipi/core": "3.0.0-alpha.29",
14
14
  "turndown": "^7.2.4"
15
15
  },
16
16
  "devDependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/btw",
3
- "version": "3.0.0-alpha.28",
3
+ "version": "3.0.0-alpha.29",
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.87.1"
38
38
  },
39
39
  "dependencies": {
40
- "@pi-unipi/core": "3.0.0-alpha.28"
40
+ "@pi-unipi/core": "3.0.0-alpha.29"
41
41
  },
42
42
  "pi": {
43
43
  "extensions": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/compactor",
3
- "version": "3.0.0-alpha.28",
3
+ "version": "3.0.0-alpha.29",
4
4
  "description": "Compaction for Pi — lossless zero-LLM or model summaries, rebuilt from full history, with session recall",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -33,8 +33,8 @@
33
33
  "access": "public"
34
34
  },
35
35
  "dependencies": {
36
- "@pi-unipi/core": "3.0.0-alpha.28",
37
- "@pi-unipi/info-screen": "3.0.0-alpha.28",
36
+ "@pi-unipi/core": "3.0.0-alpha.29",
37
+ "@pi-unipi/info-screen": "3.0.0-alpha.29",
38
38
  "@earendil-works/pi-agent-core": "^0.87.1"
39
39
  },
40
40
  "peerDependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/core",
3
- "version": "3.0.0-alpha.28",
3
+ "version": "3.0.0-alpha.29",
4
4
  "description": "Shared utilities, event types, and constants for Unipi extension suite",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/footer",
3
- "version": "3.0.0-alpha.28",
3
+ "version": "3.0.0-alpha.29",
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": "3.0.0-alpha.28",
36
- "@pi-unipi/background-tasks": "3.0.0-alpha.28"
35
+ "@pi-unipi/core": "3.0.0-alpha.29",
36
+ "@pi-unipi/background-tasks": "3.0.0-alpha.29"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/fusion",
3
- "version": "3.0.0-alpha.28",
3
+ "version": "3.0.0-alpha.29",
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,9 +32,9 @@
32
32
  "access": "public"
33
33
  },
34
34
  "dependencies": {
35
- "@pi-unipi/core": "3.0.0-alpha.28",
36
- "@pi-unipi/subagents": "3.0.0-alpha.28",
37
- "@pi-unipi/utility": "3.0.0-alpha.28"
35
+ "@pi-unipi/core": "3.0.0-alpha.29",
36
+ "@pi-unipi/subagents": "3.0.0-alpha.29",
37
+ "@pi-unipi/utility": "3.0.0-alpha.29"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@earendil-works/pi-ai": "^0.87.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/info-screen",
3
- "version": "3.0.0-alpha.28",
3
+ "version": "3.0.0-alpha.29",
4
4
  "description": "Unicrab startup splash and the /unipi:info dashboard — this session, context, usage, compaction savings",
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": "3.0.0-alpha.28"
36
+ "@pi-unipi/core": "3.0.0-alpha.29"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/input-shortcuts",
3
- "version": "3.0.0-alpha.28",
3
+ "version": "3.0.0-alpha.29",
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": "3.0.0-alpha.28"
36
+ "@pi-unipi/core": "3.0.0-alpha.29"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/kanboard",
3
- "version": "3.0.0-alpha.28",
3
+ "version": "3.0.0-alpha.29",
4
4
  "description": "Kanboard v3 — deferred-work board: CLI/daemon bridge, task runner and skill",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -31,7 +31,7 @@
31
31
  "access": "public"
32
32
  },
33
33
  "dependencies": {
34
- "@pi-unipi/core": "3.0.0-alpha.28"
34
+ "@pi-unipi/core": "3.0.0-alpha.29"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -48,10 +48,10 @@
48
48
  "themes": []
49
49
  },
50
50
  "optionalDependencies": {
51
- "@pi-unipi/kanboard-linux-x64": "3.0.0-alpha.28",
52
- "@pi-unipi/kanboard-linux-arm64": "3.0.0-alpha.28",
53
- "@pi-unipi/kanboard-darwin-x64": "3.0.0-alpha.28",
54
- "@pi-unipi/kanboard-darwin-arm64": "3.0.0-alpha.28",
55
- "@pi-unipi/kanboard-win32-x64": "3.0.0-alpha.28"
51
+ "@pi-unipi/kanboard-linux-x64": "3.0.0-alpha.29",
52
+ "@pi-unipi/kanboard-linux-arm64": "3.0.0-alpha.29",
53
+ "@pi-unipi/kanboard-darwin-x64": "3.0.0-alpha.29",
54
+ "@pi-unipi/kanboard-darwin-arm64": "3.0.0-alpha.29",
55
+ "@pi-unipi/kanboard-win32-x64": "3.0.0-alpha.29"
56
56
  }
57
57
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/long-horizon",
3
- "version": "3.0.0-alpha.28",
3
+ "version": "3.0.0-alpha.29",
4
4
  "description": "Long-horizon execution for Pi — mode-gated /goal /ralph /swarm /graph orchestration with a TypeSafe prompt judge",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -37,7 +37,7 @@
37
37
  "access": "public"
38
38
  },
39
39
  "dependencies": {
40
- "@pi-unipi/core": "3.0.0-alpha.28"
40
+ "@pi-unipi/core": "3.0.0-alpha.29"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/mcp",
3
- "version": "3.0.0-alpha.28",
3
+ "version": "3.0.0-alpha.29",
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": "3.0.0-alpha.28"
33
+ "@pi-unipi/core": "3.0.0-alpha.29"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/memory",
3
- "version": "3.0.0-alpha.28",
3
+ "version": "3.0.0-alpha.29",
4
4
  "description": "Persistent cross-session memory on MemPalace (auto-installed): daemon-routed writes, shared palace with other agents, for Pi coding agent",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -42,8 +42,8 @@
42
42
  "README.md"
43
43
  ],
44
44
  "dependencies": {
45
- "@pi-unipi/core": "3.0.0-alpha.28",
46
- "@pi-unipi/info-screen": "3.0.0-alpha.28",
45
+ "@pi-unipi/core": "3.0.0-alpha.29",
46
+ "@pi-unipi/info-screen": "3.0.0-alpha.29",
47
47
  "js-yaml": "^4.1.0"
48
48
  },
49
49
  "peerDependencies": {
@@ -71,9 +71,12 @@ notify_user({ title: "Build failed", message: "tsc found 12 errors.", priority:
71
71
  | `session_shutdown` | off | The session ends. |
72
72
  | `ask_user_prompt` | off | The agent asks you a question and waits. |
73
73
  | `permission_request` | off | A permission prompt opens. This needs [`@gotgenes/pi-permission-system`](https://www.npmjs.com/package/@gotgenes/pi-permission-system). |
74
+ | `input_needed` | off | The agent waits on any prompt while it runs. Covers prompts that send no event of their own — third-party `ask_user` tools, the permission prompt, the plan review. |
74
75
 
75
76
  `ask_user_prompt` and `permission_request` are blocking events. The agent stops until you answer.
76
77
 
78
+ `input_needed` is the catch-all for the same situation: Pi fires it around every blocking prompt, including ones that emit no event of their own. It only fires while the agent is running — prompts you open yourself while the agent is idle (for example `/unipi:settings`) stay quiet — and a prompt already announced as `ask_user_prompt` or `permission_request` is not announced twice. Closing any prompt stops its reminders.
79
+
77
80
  ## Settings
78
81
 
79
82
  Open `/unipi:settings` → **Notify**. The file is `~/.unipi/config/notify/config.json`. A project file at `.unipi/config/notify/config.json` overrides it.
@@ -104,6 +107,7 @@ A reminder has the text `(still waiting)` in its title and `high` priority. Remi
104
107
  - You press a key.
105
108
  - herdr reports that the agent is not blocked.
106
109
  - The agent starts a new turn.
110
+ - The prompt closes.
107
111
  - The session ends.
108
112
 
109
113
  Only one reminder runs at a time. Silence after input does not apply to blocking events.
@@ -26,6 +26,7 @@ const VALID_PLATFORMS: ReadonlySet<NotifyPlatform> = new Set([
26
26
  const BLOCKING_EVENTS: ReadonlySet<string> = new Set([
27
27
  "ask_user_prompt",
28
28
  "permission_request",
29
+ "input_needed",
29
30
  ]);
30
31
 
31
32
  /** Whether an event type is a human-blocking prompt. */
@@ -51,8 +52,9 @@ export function resetInputActivity(): void {
51
52
  * Empty `platforms` (while enabled) silences all incoming channels, matching
52
53
  * `events.*.platforms: []` → all enabled.
53
54
  *
54
- * Human-blocking events (`ask_user_prompt`, `permission_request`) bypass the
55
- * filter entirely — the keypress that triggered them must not mute them.
55
+ * Human-blocking events (`ask_user_prompt`, `permission_request`,
56
+ * `input_needed`) bypass the filter entirely — the keypress that triggered
57
+ * them must not mute them.
56
58
  */
57
59
  export function filterPlatformsAfterInput(
58
60
  platforms: NotifyPlatform[],
@@ -15,6 +15,7 @@ import { sendTelegramNotification } from "./platforms/telegram.js";
15
15
  import { sendNtfyNotification } from "./platforms/ntfy.js";
16
16
  import { buildAskUserPromptMessage } from "./ask-user-prompt-message.js";
17
17
  import { buildPermissionPromptMessage } from "./permission-prompt-message.js";
18
+ import { buildInputNeededMessage } from "./input-needed-message.js";
18
19
  import { summarizeLastMessage } from "./summarize.js";
19
20
  import { filterPlatformsAfterInput, isBlockingEvent } from "./activity.js";
20
21
 
@@ -75,6 +76,22 @@ const unsubs: Array<() => void> = [];
75
76
  /** Pending re-notify interval for an unanswered blocking prompt. */
76
77
  let renotifyTimer: ReturnType<typeof setInterval> | undefined;
77
78
 
79
+ /**
80
+ * A `ui_prompt_start` within this window of an ask_user/permission alert is
81
+ * the same prompt — pi emits the bus event and the lifecycle event back to
82
+ * back. Exported for tests.
83
+ */
84
+ export const PROMPT_DEDUP_MS = 2000;
85
+
86
+ /** Whether an agent run is in progress — `input_needed` fires only while it is. */
87
+ let agentRunning = false;
88
+
89
+ /** Open blocking UI prompts (`ui_prompt_start`/`ui_prompt_end` pairing). */
90
+ let openPrompts = 0;
91
+
92
+ /** When the last ask_user/permission alert went out, for ui_prompt de-dup. */
93
+ let lastBlockingAlertAt = 0;
94
+
78
95
  /** Cancel any pending re-notify timer. Safe to call at any time. */
79
96
  export function disarmRenotify(): void {
80
97
  const timer = renotifyTimer;
@@ -128,9 +145,33 @@ function armRenotify(
128
145
  renotifyTimer = timer;
129
146
  }
130
147
 
148
+ /**
149
+ * Dispatch a human-blocking prompt alert: high priority plus the reminder
150
+ * loop. Non-`input_needed` alerts also stamp `lastBlockingAlertAt` so the
151
+ * back-to-back `ui_prompt_start` for the same prompt is seen as announced.
152
+ */
153
+ function notifyBlocking(
154
+ pi: ExtensionAPI,
155
+ eventKey: string,
156
+ title: string,
157
+ message: string,
158
+ platforms: NotifyPlatform[],
159
+ config: NotifyConfig,
160
+ cwd: string,
161
+ dispatch: DispatchNotification,
162
+ ): void {
163
+ if (eventKey !== "input_needed") lastBlockingAlertAt = Date.now();
164
+ dispatch(pi, title, message, platforms, eventKey, config, cwd, "high").catch(() => {
165
+ // Silently ignore — background notification failure is non-blocking.
166
+ });
167
+ armRenotify(pi, title, message, platforms, eventKey, config, cwd, dispatch);
168
+ }
169
+
131
170
  /** Unregister all previously registered pi.events.on() listeners. */
132
171
  function unregisterAll(): void {
133
172
  disarmRenotify();
173
+ agentRunning = false;
174
+ openPrompts = 0;
134
175
  for (const unsub of unsubs) {
135
176
  try { unsub(); } catch { /* ignore */ }
136
177
  }
@@ -161,6 +202,7 @@ export const BUILTIN_EVENTS: Record<
161
202
  session_shutdown: { hook: "session_shutdown", label: "Session End" },
162
203
  ask_user_prompt: { hook: UNIPI_EVENTS.ASK_USER_PROMPT, label: "Question Asked" },
163
204
  permission_request: { hook: PERMISSION_UI_PROMPT_EVENT, label: "Permission Request" },
205
+ input_needed: { hook: "ui_prompt_start", label: "Input Needed" },
164
206
  };
165
207
 
166
208
  /**
@@ -184,7 +226,9 @@ export function registerEventListeners(
184
226
  unregisterAll();
185
227
  // Register built-in events (except agent lifecycle notifications which have custom logic)
186
228
  for (const [eventKey, def] of Object.entries(BUILTIN_EVENTS)) {
187
- if (isAgentNotificationEvent(eventKey)) continue; // handled separately below
229
+ // Agent notifications have custom logic; input_needed has its own
230
+ // ui_prompt_start registration below (with agent-running + de-dup guards).
231
+ if (isAgentNotificationEvent(eventKey) || eventKey === "input_needed") continue;
188
232
 
189
233
  const eventConfig = config.events[eventKey];
190
234
  if (!eventConfig?.enabled) continue;
@@ -192,6 +236,12 @@ export function registerEventListeners(
192
236
  const handler = (payload: unknown) => {
193
237
  const title = `Pi — ${def.label}`;
194
238
  const message = buildEventMessage(eventKey, payload);
239
+ // Human-blocking prompts: high priority, reminder loop, and a stamp
240
+ // that keeps the follow-up ui_prompt_start from double-firing.
241
+ if (isBlockingEvent(eventKey)) {
242
+ notifyBlocking(pi, eventKey, title, message, eventConfig.platforms, config, cwd, dispatch);
243
+ return;
244
+ }
195
245
  // Fire-and-forget: don't block the event emitter
196
246
  dispatch(
197
247
  pi,
@@ -205,9 +255,6 @@ export function registerEventListeners(
205
255
  ).catch(() => {
206
256
  // Silently ignore — background notification failure is non-blocking.
207
257
  });
208
- if (isBlockingEvent(eventKey)) {
209
- armRenotify(pi, title, message, eventConfig.platforms, eventKey, config, cwd, dispatch);
210
- }
211
258
  };
212
259
 
213
260
  // Pi lifecycle events are dispatched via ExtensionRunner — must use
@@ -225,14 +272,16 @@ export function registerEventListeners(
225
272
  const askUserConfig = config.events["ask_user_prompt"];
226
273
  if (askUserConfig?.enabled) {
227
274
  unsubs.push(pi.events.on(ASK_USER_PROMPT_EVENT, (payload: unknown) => {
228
- const title = `Pi — ${BUILTIN_EVENTS.ask_user_prompt.label}`;
229
- const message = buildAskUserPromptMessage(payload);
230
- dispatch(pi, title, message, askUserConfig.platforms, "ask_user_prompt", config, cwd, "high").catch(
231
- () => {
232
- // Silently ignore — background notification failure is non-blocking.
233
- }
275
+ notifyBlocking(
276
+ pi,
277
+ "ask_user_prompt",
278
+ `Pi — ${BUILTIN_EVENTS.ask_user_prompt.label}`,
279
+ buildAskUserPromptMessage(payload),
280
+ askUserConfig.platforms,
281
+ config,
282
+ cwd,
283
+ dispatch,
234
284
  );
235
- armRenotify(pi, title, message, askUserConfig.platforms, "ask_user_prompt", config, cwd, dispatch);
236
285
  }));
237
286
  }
238
287
 
@@ -242,11 +291,67 @@ export function registerEventListeners(
242
291
  if ((payload as { active?: unknown } | null)?.active === false) disarmRenotify();
243
292
  }));
244
293
  (pi as any).on("agent_start", () => {
294
+ agentRunning = true;
245
295
  disarmRenotify();
246
296
  });
247
297
 
248
298
  registerAgentNotification(pi, "agent_end", config, cwd, dispatch);
249
299
  registerAgentNotification(pi, "agent_settled", config, cwd, dispatch);
300
+
301
+ // Keep the agent-running flag in sync. Registered after the agent
302
+ // notification handlers above so the first agent_end handler stays the
303
+ // notification path (pi.on handlers fire in registration order).
304
+ (pi as any).on("agent_end", () => {
305
+ agentRunning = false;
306
+ });
307
+
308
+ registerInputNeeded(pi, config, cwd, dispatch);
309
+ }
310
+
311
+ /**
312
+ * pi ≥0.84.4 fires `ui_prompt_start`/`ui_prompt_end` around every blocking
313
+ * user-facing prompt — including ones that emit no bus event of their own
314
+ * (third-party ask_user tools, the permission prompt, the plan review).
315
+ *
316
+ * The start/end pair is registered whenever we are not a subagent child,
317
+ * independent of the `input_needed` flag: the open-prompt counter must stay
318
+ * paired so a prompt closing also disarms ask_user/permission reminders.
319
+ * Only the notification itself respects the flag, and it fires only while
320
+ * the agent is running — a `ui_prompt_start` while idle is the user's own
321
+ * overlay (e.g. /unipi:settings).
322
+ */
323
+ function registerInputNeeded(
324
+ pi: ExtensionAPI,
325
+ config: NotifyConfig,
326
+ cwd: string,
327
+ dispatch: DispatchNotification,
328
+ ): void {
329
+ // Subagent children never own a user-facing prompt.
330
+ if (process.env.UNIPI_SUBAGENT_CHILD === "1") return;
331
+
332
+ (pi as any).on("ui_prompt_start", (payload: unknown) => {
333
+ openPrompts += 1;
334
+ const eventConfig = config.events["input_needed"];
335
+ if (!eventConfig?.enabled) return;
336
+ if (!agentRunning) return;
337
+ // Same prompt already announced as ask_user/permission_request.
338
+ if (Date.now() - lastBlockingAlertAt < PROMPT_DEDUP_MS) return;
339
+ notifyBlocking(
340
+ pi,
341
+ "input_needed",
342
+ `Pi — ${BUILTIN_EVENTS.input_needed.label}`,
343
+ buildInputNeededMessage(payload),
344
+ eventConfig.platforms,
345
+ config,
346
+ cwd,
347
+ dispatch,
348
+ );
349
+ });
350
+
351
+ (pi as any).on("ui_prompt_end", () => {
352
+ openPrompts = Math.max(0, openPrompts - 1);
353
+ if (openPrompts === 0) disarmRenotify();
354
+ });
250
355
  }
251
356
 
252
357
  /** Get all platforms that are currently enabled in config */
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @pi-unipi/notify — Internal helper: build notification message from pi's
3
+ * `ui_prompt_start` lifecycle payload.
4
+ *
5
+ * @internal — not part of the public API. Shared by the event listener and tests.
6
+ */
7
+
8
+ /** Longest prompt title kept verbatim before an ellipsis is appended. */
9
+ export const INPUT_TITLE_MAX_CHARS = 120;
10
+
11
+ function cleanKind(value: unknown): string {
12
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : "prompt";
13
+ }
14
+
15
+ /** Truncate by code points (Array.from) so surrogate pairs are never split. */
16
+ function truncate(text: string, max: number): string {
17
+ const chars = Array.from(text);
18
+ return chars.length <= max ? text : `${chars.slice(0, max).join("")}…`;
19
+ }
20
+
21
+ /** Build a human-readable notification message from a ui_prompt_start payload. */
22
+ export function buildInputNeededMessage(payload: unknown): string {
23
+ const p = (payload ?? {}) as { title?: unknown; kind?: unknown };
24
+ const title = typeof p.title === "string" ? p.title.replace(/\s+/g, " ").trim() : "";
25
+ if (title.length === 0) return `Waiting for your input (${cleanKind(p.kind)})`;
26
+ return `Waiting for your input: ${truncate(title, INPUT_TITLE_MAX_CHARS)}`;
27
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/notify",
3
- "version": "3.0.0-alpha.28",
3
+ "version": "3.0.0-alpha.29",
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": "3.0.0-alpha.28",
37
+ "@pi-unipi/core": "3.0.0-alpha.29",
38
38
  "node-notifier": "^10.0.1"
39
39
  },
40
40
  "peerDependencies": {
@@ -30,6 +30,7 @@ export const DEFAULT_CONFIG: NotifyConfig = {
30
30
  session_shutdown: { enabled: false, platforms: [] },
31
31
  ask_user_prompt: { enabled: false, platforms: [] },
32
32
  permission_request: { enabled: false, platforms: [] },
33
+ input_needed: { enabled: false, platforms: [] },
33
34
  },
34
35
  native: {
35
36
  enabled: true,
@@ -79,6 +80,7 @@ const EVENT_PHRASES: Record<string, string> = {
79
80
  session_shutdown: "the session shuts down.",
80
81
  ask_user_prompt: "the agent asks you a question.",
81
82
  permission_request: "a permission prompt opens.",
83
+ input_needed: "the agent is waiting on any prompt (covers tools that send no event of their own).",
82
84
  };
83
85
 
84
86
  /** One section per known event: enable + platform routing. */
@@ -38,7 +38,8 @@ Help users configure the `@pi-unipi/notify` notification system.
38
38
  "memory_consolidated": { "enabled": false, "platforms": [] },
39
39
  "session_shutdown": { "enabled": false, "platforms": [] },
40
40
  "ask_user_prompt": { "enabled": false, "platforms": [] },
41
- "permission_request": { "enabled": false, "platforms": [] }
41
+ "permission_request": { "enabled": false, "platforms": [] },
42
+ "input_needed": { "enabled": false, "platforms": [] }
42
43
  },
43
44
  "native": {
44
45
  "enabled": true,
@@ -207,10 +208,11 @@ ntfy uses dedicated `ntfy.json` files at both global and project scope, with ful
207
208
  | `session_shutdown` | Off | Session ends |
208
209
  | `ask_user_prompt` | Off | Agent asked a question and is waiting for an answer |
209
210
  | `permission_request` | Off | A permission prompt is about to be shown |
211
+ | `input_needed` | Off | The agent waits on any prompt while it runs (covers tools that send no event of their own) |
210
212
 
211
213
  Each event can override `platforms` — empty array means use `defaultPlatforms`.
212
214
 
213
- `ask_user_prompt` and `permission_request` are **blocking** events: while one is unanswered the agent is parked, so notify re-sends it periodically (see the Re-notify unanswered prompts section under Platforms).
215
+ `ask_user_prompt`, `permission_request` and `input_needed` are **blocking** events: while one is unanswered the agent is parked, so notify re-sends it periodically (see the Re-notify unanswered prompts section under Platforms).
214
216
 
215
217
  ### `permission_request`
216
218
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/skill-registry",
3
- "version": "3.0.0-alpha.28",
3
+ "version": "3.0.0-alpha.29",
4
4
  "description": "Skill registry for pi — per-project skill on/off, a skill vault, and jev-judged skill exposure",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "main": "index.ts",
35
35
  "dependencies": {
36
- "@pi-unipi/core": "3.0.0-alpha.28"
36
+ "@pi-unipi/core": "3.0.0-alpha.29"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/subagents",
3
- "version": "3.0.0-alpha.28",
3
+ "version": "3.0.0-alpha.29",
4
4
  "description": "Subagents for UniPi — Devin-model independent child agents",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -9,8 +9,8 @@
9
9
  "test": "npx tsx --test src/__tests__/*.test.ts"
10
10
  },
11
11
  "dependencies": {
12
- "@pi-unipi/core": "3.0.0-alpha.28",
13
- "@pi-unipi/utility": "3.0.0-alpha.28",
12
+ "@pi-unipi/core": "3.0.0-alpha.29",
13
+ "@pi-unipi/utility": "3.0.0-alpha.29",
14
14
  "@earendil-works/pi-agent-core": "^0.87.1",
15
15
  "acorn": "8.18.0"
16
16
  },
@@ -25336,10 +25336,10 @@ import { dirname as dirname20, join as join37 } from "node:path";
25336
25336
  import { randomUUID as randomUUID6 } from "node:crypto";
25337
25337
  function primaryArg(name, args) {
25338
25338
  if (!args) return "";
25339
- const truncate2 = (v, max) => v.length > max ? `${v.slice(0, Math.max(0, max - 1))}\u2026` : v;
25339
+ const truncate3 = (v, max) => v.length > max ? `${v.slice(0, Math.max(0, max - 1))}\u2026` : v;
25340
25340
  const firstLine3 = (v) => v.split("\n", 1)[0] ?? "";
25341
25341
  const value = name === "bash" ? args.command : name === "read" || name === "edit" || name === "write" ? args.path ?? args.file_path ?? args.filePath : Object.values(args).find((entry) => typeof entry === "string");
25342
- return typeof value === "string" ? truncate2(firstLine3(value), 100) : "";
25342
+ return typeof value === "string" ? truncate3(firstLine3(value), 100) : "";
25343
25343
  }
25344
25344
  var DENY_WITH_NOTE = "Deny with note\u2026";
25345
25345
  var BACKGROUND_DENY_NOTE = "Permission denied for this tool: this agent is running in the background, where tools that would require approval are automatically denied. Do not retry variants of this call; finish what you can without it and say in your report what needs approval.";
@@ -36577,8 +36577,8 @@ function renderMcpSettingsOverlay(params) {
36577
36577
  const selected = i === state2.selectedIndex;
36578
36578
  const statusIcon = server.status === "running" ? "\u25CF" : server.status === "error" ? "\u2717" : server.enabled ? "\u25CB" : "\u25CB";
36579
36579
  const label = `${statusIcon} ${server.enabled ? "" : "disabled \xB7 "}${server.name}`;
36580
- const tools = server.status === "running" && server.toolCount > 0 ? `${server.toolCount} tools` : server.status === "error" && server.error ? truncate2(server.error, 20) : "stopped";
36581
- const value = `${truncate2(server.command, 24)} \xB7 ${tools} \xB7 [${server.source}]`;
36580
+ const tools = server.status === "running" && server.toolCount > 0 ? `${server.toolCount} tools` : server.status === "error" && server.error ? truncate3(server.error, 20) : "stopped";
36581
+ const value = `${truncate3(server.command, 24)} \xB7 ${tools} \xB7 [${server.source}]`;
36582
36582
  body.push(hubExactRow(hubRowColumns({
36583
36583
  inner,
36584
36584
  selected,
@@ -36600,7 +36600,7 @@ function renderMcpSettingsOverlay(params) {
36600
36600
  title: hubBoldText(hubFrameTitle("mcp", [], `\u2014 ${state2.viewScope}`)),
36601
36601
  borderFg: (t) => hubTheme.fg("borderMuted", t)
36602
36602
  });
36603
- function truncate2(text, n) {
36603
+ function truncate3(text, n) {
36604
36604
  return text.length > n ? text.slice(0, n - 1) + "\u2026" : text;
36605
36605
  }
36606
36606
  }
@@ -36879,7 +36879,8 @@ var VALID_PLATFORMS = /* @__PURE__ */ new Set([
36879
36879
  ]);
36880
36880
  var BLOCKING_EVENTS = /* @__PURE__ */ new Set([
36881
36881
  "ask_user_prompt",
36882
- "permission_request"
36882
+ "permission_request",
36883
+ "input_needed"
36883
36884
  ]);
36884
36885
  function isBlockingEvent(eventType) {
36885
36886
  return eventType !== void 0 && BLOCKING_EVENTS.has(eventType);
@@ -36948,7 +36949,8 @@ var DEFAULT_CONFIG5 = {
36948
36949
  memory_consolidated: { enabled: false, platforms: [] },
36949
36950
  session_shutdown: { enabled: false, platforms: [] },
36950
36951
  ask_user_prompt: { enabled: false, platforms: [] },
36951
- permission_request: { enabled: false, platforms: [] }
36952
+ permission_request: { enabled: false, platforms: [] },
36953
+ input_needed: { enabled: false, platforms: [] }
36952
36954
  },
36953
36955
  native: {
36954
36956
  enabled: true,
@@ -36992,7 +36994,8 @@ var EVENT_PHRASES = {
36992
36994
  memory_consolidated: "a memory consolidation completes.",
36993
36995
  session_shutdown: "the session shuts down.",
36994
36996
  ask_user_prompt: "the agent asks you a question.",
36995
- permission_request: "a permission prompt opens."
36997
+ permission_request: "a permission prompt opens.",
36998
+ input_needed: "the agent is waiting on any prompt (covers tools that send no event of their own)."
36996
36999
  };
36997
37000
  var EVENT_SECTIONS = Object.keys(DEFAULT_CONFIG5.events).map((event) => ({
36998
37001
  title: event,
@@ -37558,6 +37561,22 @@ function buildPermissionPromptMessage(payload) {
37558
37561
  return parts.join(" ");
37559
37562
  }
37560
37563
 
37564
+ // packages/notify/input-needed-message.ts
37565
+ var INPUT_TITLE_MAX_CHARS = 120;
37566
+ function cleanKind(value) {
37567
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : "prompt";
37568
+ }
37569
+ function truncate2(text, max) {
37570
+ const chars = Array.from(text);
37571
+ return chars.length <= max ? text : `${chars.slice(0, max).join("")}\u2026`;
37572
+ }
37573
+ function buildInputNeededMessage(payload) {
37574
+ const p = payload ?? {};
37575
+ const title = typeof p.title === "string" ? p.title.replace(/\s+/g, " ").trim() : "";
37576
+ if (title.length === 0) return `Waiting for your input (${cleanKind(p.kind)})`;
37577
+ return `Waiting for your input: ${truncate2(title, INPUT_TITLE_MAX_CHARS)}`;
37578
+ }
37579
+
37561
37580
  // packages/notify/summarize.ts
37562
37581
  var SYSTEM_PROMPT2 = "Summarize this in one concise sentence for a push notification. Reply with ONLY the summary.";
37563
37582
  var MAX_INPUT_CHARS = 2e3;
@@ -37675,6 +37694,10 @@ function defaultEventPriority(eventKey) {
37675
37694
  var sessionCtx = null;
37676
37695
  var unsubs = [];
37677
37696
  var renotifyTimer;
37697
+ var PROMPT_DEDUP_MS = 2e3;
37698
+ var agentRunning = false;
37699
+ var openPrompts = 0;
37700
+ var lastBlockingAlertAt = 0;
37678
37701
  function disarmRenotify() {
37679
37702
  const timer = renotifyTimer;
37680
37703
  renotifyTimer = void 0;
@@ -37707,8 +37730,16 @@ function armRenotify(pi, title, message, platforms, eventType, config, cwd, disp
37707
37730
  timer.unref?.();
37708
37731
  renotifyTimer = timer;
37709
37732
  }
37733
+ function notifyBlocking(pi, eventKey, title, message, platforms, config, cwd, dispatch) {
37734
+ if (eventKey !== "input_needed") lastBlockingAlertAt = Date.now();
37735
+ dispatch(pi, title, message, platforms, eventKey, config, cwd, "high").catch(() => {
37736
+ });
37737
+ armRenotify(pi, title, message, platforms, eventKey, config, cwd, dispatch);
37738
+ }
37710
37739
  function unregisterAll() {
37711
37740
  disarmRenotify();
37741
+ agentRunning = false;
37742
+ openPrompts = 0;
37712
37743
  for (const unsub of unsubs) {
37713
37744
  try {
37714
37745
  unsub();
@@ -37732,18 +37763,23 @@ var BUILTIN_EVENTS = {
37732
37763
  memory_consolidated: { hook: UNIPI_EVENTS.MEMORY_CONSOLIDATED, label: "Memory Saved" },
37733
37764
  session_shutdown: { hook: "session_shutdown", label: "Session End" },
37734
37765
  ask_user_prompt: { hook: UNIPI_EVENTS.ASK_USER_PROMPT, label: "Question Asked" },
37735
- permission_request: { hook: PERMISSION_UI_PROMPT_EVENT, label: "Permission Request" }
37766
+ permission_request: { hook: PERMISSION_UI_PROMPT_EVENT, label: "Permission Request" },
37767
+ input_needed: { hook: "ui_prompt_start", label: "Input Needed" }
37736
37768
  };
37737
37769
  var LIFECYCLE_EVENTS = /* @__PURE__ */ new Set(["agent_end", "agent_settled", "session_shutdown"]);
37738
37770
  function registerEventListeners(pi, config, cwd, dispatch = dispatchNotification) {
37739
37771
  unregisterAll();
37740
37772
  for (const [eventKey, def] of Object.entries(BUILTIN_EVENTS)) {
37741
- if (isAgentNotificationEvent(eventKey)) continue;
37773
+ if (isAgentNotificationEvent(eventKey) || eventKey === "input_needed") continue;
37742
37774
  const eventConfig = config.events[eventKey];
37743
37775
  if (!eventConfig?.enabled) continue;
37744
37776
  const handler = (payload) => {
37745
37777
  const title = `Pi \u2014 ${def.label}`;
37746
37778
  const message = buildEventMessage(eventKey, payload);
37779
+ if (isBlockingEvent(eventKey)) {
37780
+ notifyBlocking(pi, eventKey, title, message, eventConfig.platforms, config, cwd, dispatch);
37781
+ return;
37782
+ }
37747
37783
  dispatch(
37748
37784
  pi,
37749
37785
  title,
@@ -37755,9 +37791,6 @@ function registerEventListeners(pi, config, cwd, dispatch = dispatchNotification
37755
37791
  defaultEventPriority(eventKey)
37756
37792
  ).catch(() => {
37757
37793
  });
37758
- if (isBlockingEvent(eventKey)) {
37759
- armRenotify(pi, title, message, eventConfig.platforms, eventKey, config, cwd, dispatch);
37760
- }
37761
37794
  };
37762
37795
  if (LIFECYCLE_EVENTS.has(eventKey)) {
37763
37796
  pi.on(def.hook, handler);
@@ -37768,23 +37801,55 @@ function registerEventListeners(pi, config, cwd, dispatch = dispatchNotification
37768
37801
  const askUserConfig = config.events["ask_user_prompt"];
37769
37802
  if (askUserConfig?.enabled) {
37770
37803
  unsubs.push(pi.events.on(ASK_USER_PROMPT_EVENT, (payload) => {
37771
- const title = `Pi \u2014 ${BUILTIN_EVENTS.ask_user_prompt.label}`;
37772
- const message = buildAskUserPromptMessage(payload);
37773
- dispatch(pi, title, message, askUserConfig.platforms, "ask_user_prompt", config, cwd, "high").catch(
37774
- () => {
37775
- }
37804
+ notifyBlocking(
37805
+ pi,
37806
+ "ask_user_prompt",
37807
+ `Pi \u2014 ${BUILTIN_EVENTS.ask_user_prompt.label}`,
37808
+ buildAskUserPromptMessage(payload),
37809
+ askUserConfig.platforms,
37810
+ config,
37811
+ cwd,
37812
+ dispatch
37776
37813
  );
37777
- armRenotify(pi, title, message, askUserConfig.platforms, "ask_user_prompt", config, cwd, dispatch);
37778
37814
  }));
37779
37815
  }
37780
37816
  unsubs.push(pi.events.on("herdr:blocked", (payload) => {
37781
37817
  if (payload?.active === false) disarmRenotify();
37782
37818
  }));
37783
37819
  pi.on("agent_start", () => {
37820
+ agentRunning = true;
37784
37821
  disarmRenotify();
37785
37822
  });
37786
37823
  registerAgentNotification(pi, "agent_end", config, cwd, dispatch);
37787
37824
  registerAgentNotification(pi, "agent_settled", config, cwd, dispatch);
37825
+ pi.on("agent_end", () => {
37826
+ agentRunning = false;
37827
+ });
37828
+ registerInputNeeded(pi, config, cwd, dispatch);
37829
+ }
37830
+ function registerInputNeeded(pi, config, cwd, dispatch) {
37831
+ if (process.env.UNIPI_SUBAGENT_CHILD === "1") return;
37832
+ pi.on("ui_prompt_start", (payload) => {
37833
+ openPrompts += 1;
37834
+ const eventConfig = config.events["input_needed"];
37835
+ if (!eventConfig?.enabled) return;
37836
+ if (!agentRunning) return;
37837
+ if (Date.now() - lastBlockingAlertAt < PROMPT_DEDUP_MS) return;
37838
+ notifyBlocking(
37839
+ pi,
37840
+ "input_needed",
37841
+ `Pi \u2014 ${BUILTIN_EVENTS.input_needed.label}`,
37842
+ buildInputNeededMessage(payload),
37843
+ eventConfig.platforms,
37844
+ config,
37845
+ cwd,
37846
+ dispatch
37847
+ );
37848
+ });
37849
+ pi.on("ui_prompt_end", () => {
37850
+ openPrompts = Math.max(0, openPrompts - 1);
37851
+ if (openPrompts === 0) disarmRenotify();
37852
+ });
37788
37853
  }
37789
37854
  function getEnabledPlatforms(config, ntfyEnabled) {
37790
37855
  const enabled = [];
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/updater",
3
- "version": "3.0.0-alpha.28",
3
+ "version": "3.0.0-alpha.29",
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": "3.0.0-alpha.28"
37
+ "@pi-unipi/core": "3.0.0-alpha.29"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/utility",
3
- "version": "3.0.0-alpha.28",
3
+ "version": "3.0.0-alpha.29",
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": "3.0.0-alpha.28"
39
+ "@pi-unipi/core": "3.0.0-alpha.29"
40
40
  },
41
41
  "devDependencies": {},
42
42
  "peerDependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/watchdog",
3
- "version": "3.0.0-alpha.28",
3
+ "version": "3.0.0-alpha.29",
4
4
  "description": "Jev watchdog for long-running tool calls and background tasks — judges stuck work via TypeSafe System One and kills or warns",
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/background-tasks": "3.0.0-alpha.28",
36
- "@pi-unipi/core": "3.0.0-alpha.28"
35
+ "@pi-unipi/background-tasks": "3.0.0-alpha.29",
36
+ "@pi-unipi/core": "3.0.0-alpha.29"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/web-api",
3
- "version": "3.0.0-alpha.28",
3
+ "version": "3.0.0-alpha.29",
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": "3.0.0-alpha.28",
34
+ "@pi-unipi/core": "3.0.0-alpha.29",
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": "3.0.0-alpha.28",
3
+ "version": "3.0.0-alpha.29",
4
4
  "description": "Plan mode and permission modes for the Pi coding agent",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -27,7 +27,7 @@
27
27
  "access": "public"
28
28
  },
29
29
  "dependencies": {
30
- "@pi-unipi/core": "3.0.0-alpha.28"
30
+ "@pi-unipi/core": "3.0.0-alpha.29"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@earendil-works/pi-ai": "^0.87.1",