@pi-archimedes/notify 2.4.0 → 2.5.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/README.md CHANGED
@@ -11,7 +11,7 @@ Get notified when Pi finishes long tasks or needs an answer, without constant po
11
11
  - **Terminal-aware dispatch** — auto-detects your terminal and uses the optimal protocol (OSC 99, OSC 9, OSC 777, or PowerShell toasts)
12
12
  - **tmux passthrough** — all sequences wrapped via DCS for correct rendering inside tmux
13
13
  - **Per-trigger toggles** — independently enable/disable notifications for task completion and unanswered questions
14
- - **Bus-driven** — listens for `agent_end` and `ASK_REQUEST` bus events, so it works with any package that emits them
14
+ - **Pi-native triggers** — keyed on pi's `agent_settled` and `ui_prompt_start` lifecycle events, so task completion works and *any* blocking extension prompt (ask, sudo, mcp OAuth) can hold your attention
15
15
 
16
16
  ## Install
17
17
 
@@ -27,7 +27,7 @@ pi install npm:pi-archimedes
27
27
 
28
28
  ## Usage
29
29
 
30
- When the agent finishes a task (`agent_end`) or a question is asked (`ASK_REQUEST`), a timer starts. If you don't interact for the configured delay, a desktop notification fires. Any keystroke — even just pressing a key without submitting — cancels the timer immediately.
30
+ When the agent's run has settled (`agent_settled`) or an extension opens a blocking prompt (`ui_prompt_start` — ask, sudo, mcp OAuth), a timer starts. If you don't interact for the configured delay, a desktop notification fires. Any keystroke — even just pressing a key without submitting — cancels the timer immediately.
31
31
 
32
32
  ## Settings
33
33
 
@@ -54,6 +54,6 @@ Settings are stored in `~/.pi/agent/settings.json` under the `archimedes.notify`
54
54
 
55
55
  ## Integration
56
56
 
57
- When installed via `pi-archimedes` (the meta package), the notify package is automatically registered and its settings appear in the `/archimedes` settings panel. Standalone installs work independently — any package emitting `agent_end` or `ASK_REQUEST` bus events will trigger notifications.
57
+ When installed via `pi-archimedes` (the meta package), the notify package is automatically registered and its settings appear in the `/archimedes` settings panel. Standalone installs work independently — any blocking extension UI prompt will trigger the question notification.
58
58
 
59
59
  ← Back to [pi-archimedes](../../README.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-archimedes/notify",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "type": "module",
5
5
  "keywords": [
6
6
  "pi-package"
@@ -14,15 +14,15 @@
14
14
  ".": "./src/index.ts"
15
15
  },
16
16
  "dependencies": {
17
- "@pi-archimedes/core": "2.4.0"
17
+ "@pi-archimedes/core": "2.5.0"
18
18
  },
19
19
  "peerDependencies": {
20
- "@earendil-works/pi-coding-agent": ">=0.1.0",
20
+ "@earendil-works/pi-coding-agent": ">=0.84.4",
21
21
  "@earendil-works/pi-tui": ">=0.1.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@earendil-works/pi-coding-agent": "^0.84.2",
25
- "@earendil-works/pi-tui": "^0.84.2",
24
+ "@earendil-works/pi-coding-agent": "^0.84.4",
25
+ "@earendil-works/pi-tui": "^0.84.4",
26
26
  "typescript": "^6.0.0"
27
27
  },
28
28
  "pi": {
@@ -13,7 +13,9 @@ describe("notify default export (standalone factory)", () => {
13
13
  const events = on.mock.calls.map((c: Array<unknown>) => c[0]);
14
14
  expect(events).toEqual(
15
15
  expect.arrayContaining([
16
- "agent_end",
16
+ "agent_settled",
17
+ "ui_prompt_end",
18
+ "ui_prompt_start",
17
19
  "input",
18
20
  "before_agent_start",
19
21
  "agent_start",
package/src/index.ts CHANGED
@@ -1,14 +1,13 @@
1
1
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import type { SettingItem } from "@earendil-works/pi-tui";
3
3
  import { loadConfig, saveConfig } from "@pi-archimedes/core/settings-io";
4
- import { getBus, Events } from "@pi-archimedes/core/bus";
5
4
  import { execFile } from "node:child_process";
6
5
 
7
6
  // ── Trigger constants ────────────────────────────────────────────────────────
8
7
 
9
8
  const TRIGGER = {
10
- AGENT_END: "agent_end",
11
- ASK_REQUEST: "ask_request",
9
+ AGENT_SETTLED: "agent_settled",
10
+ UI_PROMPT: "ui_prompt",
12
11
  } as const;
13
12
  type TriggerType = (typeof TRIGGER)[keyof typeof TRIGGER];
14
13
 
@@ -153,11 +152,11 @@ export function scheduleNotify(trigger: TriggerType): void {
153
152
  // Load config fresh on each trigger
154
153
  const config = loadNotifyConfig();
155
154
 
156
- if (trigger === TRIGGER.AGENT_END && !config.notifyOnAgentEnd) {
155
+ if (trigger === TRIGGER.AGENT_SETTLED && !config.notifyOnAgentEnd) {
157
156
  return;
158
157
  }
159
158
 
160
- if (trigger === TRIGGER.ASK_REQUEST && !config.notifyOnQuestion) {
159
+ if (trigger === TRIGGER.UI_PROMPT && !config.notifyOnQuestion) {
161
160
  return;
162
161
  }
163
162
 
@@ -174,7 +173,7 @@ export function scheduleNotify(trigger: TriggerType): void {
174
173
 
175
174
  /** Fire the actual notification based on the pending trigger. */
176
175
  function fireNotification(trigger: TriggerType | null): void {
177
- if (trigger === TRIGGER.AGENT_END) {
176
+ if (trigger === TRIGGER.AGENT_SETTLED) {
178
177
  notify("Pi", "Task complete — waiting for input");
179
178
  } else {
180
179
  notify("Pi", "A question needs your answer");
@@ -185,15 +184,22 @@ function fireNotification(trigger: TriggerType | null): void {
185
184
 
186
185
  /** Register the notify extension with the Pi agent. */
187
186
  export function registerNotify(pi: ExtensionAPI): void {
188
- pi.on("agent_end", () => scheduleNotify(TRIGGER.AGENT_END));
187
+ pi.on("agent_settled", () => scheduleNotify(TRIGGER.AGENT_SETTLED));
188
+ // Any blocking extension UI prompt (ask, sudo, mcp OAuth) — fires in the
189
+ // parent process for direct and subagent-relayed prompts alike.
190
+ pi.on("ui_prompt_start", (_event) => scheduleNotify(TRIGGER.UI_PROMPT));
189
191
  pi.on("input", () => cancelPending());
190
192
  pi.on("before_agent_start", () => cancelPending());
191
193
  pi.on("agent_start", () => cancelPending());
192
-
193
- // Listen for ask requests from the bus (ask package emits this)
194
- const unsubAskRequest = getBus().on(Events.ASK_REQUEST, () =>
195
- scheduleNotify(TRIGGER.ASK_REQUEST),
196
- );
194
+ // A prompt that closes without terminal input (e.g. the mcp OAuth
195
+ // loader finishing from the browser's `done()`) cancels the question
196
+ // timer so a long-gone prompt does not fire a stale "question needs
197
+ // your answer". Scoped so it never wipes a pending "task complete" timer.
198
+ pi.on("ui_prompt_end", () => {
199
+ if (pendingTrigger === TRIGGER.UI_PROMPT) {
200
+ cancelPending();
201
+ }
202
+ });
197
203
 
198
204
  // Listen for raw terminal keystrokes — cancel on any key press
199
205
  let unsubTerminalInput: (() => void) | null = null;