@pi-archimedes/notify 2.2.0 → 2.4.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
@@ -33,11 +33,12 @@ When the agent finishes a task (`agent_end`) or a question is asked (`ASK_REQUES
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-archimedes/notify",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "type": "module",
5
5
  "keywords": [
6
6
  "pi-package"
@@ -14,13 +14,15 @@
14
14
  ".": "./src/index.ts"
15
15
  },
16
16
  "dependencies": {
17
- "@pi-archimedes/core": "2.2.0"
17
+ "@pi-archimedes/core": "2.4.0"
18
18
  },
19
19
  "peerDependencies": {
20
20
  "@earendil-works/pi-coding-agent": ">=0.1.0",
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
26
  "typescript": "^6.0.0"
25
27
  },
26
28
  "pi": {
@@ -0,0 +1,25 @@
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_end",
17
+ "input",
18
+ "before_agent_start",
19
+ "agent_start",
20
+ "session_start",
21
+ "session_shutdown",
22
+ ]),
23
+ );
24
+ });
25
+ });
package/src/index.ts CHANGED
@@ -4,17 +4,25 @@ import { loadConfig, saveConfig } from "@pi-archimedes/core/settings-io";
4
4
  import { getBus, Events } from "@pi-archimedes/core/bus";
5
5
  import { execFile } from "node:child_process";
6
6
 
7
+ // ── Trigger constants ────────────────────────────────────────────────────────
8
+
9
+ const TRIGGER = {
10
+ AGENT_END: "agent_end",
11
+ ASK_REQUEST: "ask_request",
12
+ } as const;
13
+ type TriggerType = (typeof TRIGGER)[keyof typeof TRIGGER];
14
+
7
15
  // ── Config ──────────────────────────────────────────────────────────────────
8
16
 
9
17
  export interface NotifyConfig {
10
- enabled: boolean;
18
+ // suite-managed by meta's plugin gate (archimedes.notify.enabled — see ADR 0012); notify never reads this
19
+ enabled?: boolean;
11
20
  notifyOnAgentEnd: boolean;
12
21
  notifyOnQuestion: boolean;
13
22
  delayMs: number;
14
23
  }
15
24
 
16
25
  export const DEFAULT_NOTIFY_CONFIG: NotifyConfig = {
17
- enabled: true,
18
26
  notifyOnAgentEnd: true,
19
27
  notifyOnQuestion: true,
20
28
  delayMs: 30_000,
@@ -124,7 +132,7 @@ export function notify(title: string, body: string): void {
124
132
  // ── Circuit breaker state ───────────────────────────────────────────────────
125
133
 
126
134
  let notifyTimer: ReturnType<typeof setTimeout> | null = null;
127
- let pendingTrigger: "agent_end" | "ask_request" | null = null;
135
+ let pendingTrigger: TriggerType | null = null;
128
136
 
129
137
  // ── Circuit breaker logic ───────────────────────────────────────────────────
130
138
 
@@ -138,22 +146,18 @@ export function cancelPending(): void {
138
146
  }
139
147
 
140
148
  /** Schedule a delayed notification (circuit breaker). */
141
- export function scheduleNotify(trigger: "agent_end" | "ask_request"): void {
149
+ export function scheduleNotify(trigger: TriggerType): void {
142
150
  // Cancel any existing timer first
143
151
  cancelPending();
144
152
 
145
153
  // Load config fresh on each trigger
146
154
  const config = loadNotifyConfig();
147
155
 
148
- if (!config.enabled) {
149
- return;
150
- }
151
-
152
- if (trigger === "agent_end" && !config.notifyOnAgentEnd) {
156
+ if (trigger === TRIGGER.AGENT_END && !config.notifyOnAgentEnd) {
153
157
  return;
154
158
  }
155
159
 
156
- if (trigger === "ask_request" && !config.notifyOnQuestion) {
160
+ if (trigger === TRIGGER.ASK_REQUEST && !config.notifyOnQuestion) {
157
161
  return;
158
162
  }
159
163
 
@@ -169,8 +173,8 @@ export function scheduleNotify(trigger: "agent_end" | "ask_request"): void {
169
173
  }
170
174
 
171
175
  /** Fire the actual notification based on the pending trigger. */
172
- function fireNotification(trigger: "agent_end" | "ask_request" | null): void {
173
- if (trigger === "agent_end") {
176
+ function fireNotification(trigger: TriggerType | null): void {
177
+ if (trigger === TRIGGER.AGENT_END) {
174
178
  notify("Pi", "Task complete — waiting for input");
175
179
  } else {
176
180
  notify("Pi", "A question needs your answer");
@@ -181,14 +185,14 @@ function fireNotification(trigger: "agent_end" | "ask_request" | null): void {
181
185
 
182
186
  /** Register the notify extension with the Pi agent. */
183
187
  export function registerNotify(pi: ExtensionAPI): void {
184
- pi.on("agent_end", () => scheduleNotify("agent_end"));
188
+ pi.on("agent_end", () => scheduleNotify(TRIGGER.AGENT_END));
185
189
  pi.on("input", () => cancelPending());
186
190
  pi.on("before_agent_start", () => cancelPending());
187
191
  pi.on("agent_start", () => cancelPending());
188
192
 
189
193
  // Listen for ask requests from the bus (ask package emits this)
190
194
  const unsubAskRequest = getBus().on(Events.ASK_REQUEST, () =>
191
- scheduleNotify("ask_request"),
195
+ scheduleNotify(TRIGGER.ASK_REQUEST),
192
196
  );
193
197
 
194
198
  // Listen for raw terminal keystrokes — cancel on any key press
@@ -205,18 +209,19 @@ export function registerNotify(pi: ExtensionAPI): void {
205
209
  });
206
210
  }
207
211
 
212
+ // ── Default export (for standalone pi.extensions loading) ─────────────────
213
+
214
+ // registerNotify subscribes its own session_start/session_shutdown (and
215
+ // agent/input) handlers internally, so the default factory just registers it.
216
+ export default function (pi: ExtensionAPI): void {
217
+ registerNotify(pi);
218
+ }
219
+
208
220
  // ── Settings UI ─────────────────────────────────────────────────────────────
209
221
 
210
222
  /** Build settings UI items for the notify package. */
211
223
  export function getNotifySettingsItems(config: NotifyConfig): SettingItem[] {
212
224
  return [
213
- {
214
- id: "enabled",
215
- label: "Notifications",
216
- description: "Enable desktop notifications",
217
- currentValue: config.enabled ? "On" : "Off",
218
- values: ["On", "Off"],
219
- },
220
225
  {
221
226
  id: "notifyOnAgentEnd",
222
227
  label: "Notify on task complete",