@pi-archimedes/notify 2.3.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 +5 -4
- package/package.json +5 -5
- package/src/default-export.test.ts +27 -0
- package/src/index.ts +28 -25
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
|
-
- **
|
|
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,17 +27,18 @@ pi install npm:pi-archimedes
|
|
|
27
27
|
|
|
28
28
|
## Usage
|
|
29
29
|
|
|
30
|
-
When the agent
|
|
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
|
|
|
34
34
|
| Setting | Type | Default | Description |
|
|
35
35
|
|---------|------|---------|-------------|
|
|
36
|
-
| `enabled` | bool | `true` | Enable desktop notifications |
|
|
37
36
|
| `notifyOnAgentEnd` | bool | `true` | Notify when agent finishes a task |
|
|
38
37
|
| `notifyOnQuestion` | bool | `true` | Notify when a question needs your answer |
|
|
39
38
|
| `delayMs` | number | `30000` | Milliseconds to wait before sending notification (default 30 seconds) |
|
|
40
39
|
|
|
40
|
+
On/off is managed by the suite: toggle via `/plugins` (`archimedes.notify.enabled`, default on).
|
|
41
|
+
|
|
41
42
|
Settings are stored in `~/.pi/agent/settings.json` under the `archimedes.notify` namespace.
|
|
42
43
|
|
|
43
44
|
## Terminal compatibility
|
|
@@ -53,6 +54,6 @@ Settings are stored in `~/.pi/agent/settings.json` under the `archimedes.notify`
|
|
|
53
54
|
|
|
54
55
|
## Integration
|
|
55
56
|
|
|
56
|
-
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
|
|
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.
|
|
57
58
|
|
|
58
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.
|
|
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.
|
|
17
|
+
"@pi-archimedes/core": "2.5.0"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
|
-
"@earendil-works/pi-coding-agent": ">=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.
|
|
25
|
-
"@earendil-works/pi-tui": "^0.84.
|
|
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": {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import defaultExport from "./index.js";
|
|
3
|
+
|
|
4
|
+
describe("notify default export (standalone factory)", () => {
|
|
5
|
+
it("is a function that registers the notify handlers (registerNotify subscribes its own session events)", () => {
|
|
6
|
+
expect(typeof defaultExport).toBe("function");
|
|
7
|
+
|
|
8
|
+
const on = vi.fn();
|
|
9
|
+
const pi = { on } as any;
|
|
10
|
+
|
|
11
|
+
defaultExport(pi);
|
|
12
|
+
|
|
13
|
+
const events = on.mock.calls.map((c: Array<unknown>) => c[0]);
|
|
14
|
+
expect(events).toEqual(
|
|
15
|
+
expect.arrayContaining([
|
|
16
|
+
"agent_settled",
|
|
17
|
+
"ui_prompt_end",
|
|
18
|
+
"ui_prompt_start",
|
|
19
|
+
"input",
|
|
20
|
+
"before_agent_start",
|
|
21
|
+
"agent_start",
|
|
22
|
+
"session_start",
|
|
23
|
+
"session_shutdown",
|
|
24
|
+
]),
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -1,28 +1,27 @@
|
|
|
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
|
-
|
|
11
|
-
|
|
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
|
|
|
15
14
|
// ── Config ──────────────────────────────────────────────────────────────────
|
|
16
15
|
|
|
17
16
|
export interface NotifyConfig {
|
|
18
|
-
enabled
|
|
17
|
+
// suite-managed by meta's plugin gate (archimedes.notify.enabled — see ADR 0012); notify never reads this
|
|
18
|
+
enabled?: boolean;
|
|
19
19
|
notifyOnAgentEnd: boolean;
|
|
20
20
|
notifyOnQuestion: boolean;
|
|
21
21
|
delayMs: number;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export const DEFAULT_NOTIFY_CONFIG: NotifyConfig = {
|
|
25
|
-
enabled: true,
|
|
26
25
|
notifyOnAgentEnd: true,
|
|
27
26
|
notifyOnQuestion: true,
|
|
28
27
|
delayMs: 30_000,
|
|
@@ -153,15 +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 (!config.
|
|
155
|
+
if (trigger === TRIGGER.AGENT_SETTLED && !config.notifyOnAgentEnd) {
|
|
157
156
|
return;
|
|
158
157
|
}
|
|
159
158
|
|
|
160
|
-
if (trigger === TRIGGER.
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (trigger === TRIGGER.ASK_REQUEST && !config.notifyOnQuestion) {
|
|
159
|
+
if (trigger === TRIGGER.UI_PROMPT && !config.notifyOnQuestion) {
|
|
165
160
|
return;
|
|
166
161
|
}
|
|
167
162
|
|
|
@@ -178,7 +173,7 @@ export function scheduleNotify(trigger: TriggerType): void {
|
|
|
178
173
|
|
|
179
174
|
/** Fire the actual notification based on the pending trigger. */
|
|
180
175
|
function fireNotification(trigger: TriggerType | null): void {
|
|
181
|
-
if (trigger === TRIGGER.
|
|
176
|
+
if (trigger === TRIGGER.AGENT_SETTLED) {
|
|
182
177
|
notify("Pi", "Task complete — waiting for input");
|
|
183
178
|
} else {
|
|
184
179
|
notify("Pi", "A question needs your answer");
|
|
@@ -189,15 +184,22 @@ function fireNotification(trigger: TriggerType | null): void {
|
|
|
189
184
|
|
|
190
185
|
/** Register the notify extension with the Pi agent. */
|
|
191
186
|
export function registerNotify(pi: ExtensionAPI): void {
|
|
192
|
-
pi.on("
|
|
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));
|
|
193
191
|
pi.on("input", () => cancelPending());
|
|
194
192
|
pi.on("before_agent_start", () => cancelPending());
|
|
195
193
|
pi.on("agent_start", () => cancelPending());
|
|
196
|
-
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
)
|
|
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
|
+
});
|
|
201
203
|
|
|
202
204
|
// Listen for raw terminal keystrokes — cancel on any key press
|
|
203
205
|
let unsubTerminalInput: (() => void) | null = null;
|
|
@@ -213,18 +215,19 @@ export function registerNotify(pi: ExtensionAPI): void {
|
|
|
213
215
|
});
|
|
214
216
|
}
|
|
215
217
|
|
|
218
|
+
// ── Default export (for standalone pi.extensions loading) ─────────────────
|
|
219
|
+
|
|
220
|
+
// registerNotify subscribes its own session_start/session_shutdown (and
|
|
221
|
+
// agent/input) handlers internally, so the default factory just registers it.
|
|
222
|
+
export default function (pi: ExtensionAPI): void {
|
|
223
|
+
registerNotify(pi);
|
|
224
|
+
}
|
|
225
|
+
|
|
216
226
|
// ── Settings UI ─────────────────────────────────────────────────────────────
|
|
217
227
|
|
|
218
228
|
/** Build settings UI items for the notify package. */
|
|
219
229
|
export function getNotifySettingsItems(config: NotifyConfig): SettingItem[] {
|
|
220
230
|
return [
|
|
221
|
-
{
|
|
222
|
-
id: "enabled",
|
|
223
|
-
label: "Notifications",
|
|
224
|
-
description: "Enable desktop notifications",
|
|
225
|
-
currentValue: config.enabled ? "On" : "Off",
|
|
226
|
-
values: ["On", "Off"],
|
|
227
|
-
},
|
|
228
231
|
{
|
|
229
232
|
id: "notifyOnAgentEnd",
|
|
230
233
|
label: "Notify on task complete",
|