@vs4vijay/piverse 0.1.0 → 0.6.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.
@@ -0,0 +1,178 @@
1
+ # @vs4vijay/pi-queue
2
+
3
+ Queue messages to run after the current Pi agent turn settles.
4
+
5
+ ## Install
6
+
7
+ Install from npm, then add it to your pi config:
8
+
9
+ ```
10
+ pi install npm:@vs4vijay/pi-queue
11
+ ```
12
+
13
+ In your pi config, reference the package as an extension:
14
+
15
+ ```json
16
+ {
17
+ "pi": {
18
+ "extensions": ["@vs4vijay/pi-queue"]
19
+ }
20
+ }
21
+ ```
22
+
23
+ Or, when developing in this repo, reference the extension path directly:
24
+
25
+ ```json
26
+ {
27
+ "pi": {
28
+ "extensions": ["./extensions/pi-queue/src/index.ts"]
29
+ }
30
+ }
31
+ ```
32
+
33
+ ## Commands
34
+
35
+ All commands are available as both `/queue-*` and `/q-*`.
36
+
37
+ | Command | Alias | Description |
38
+ |---------|-------|-------------|
39
+ | `/queue <message>` | `/q <message>` | Add a message to the queue |
40
+ | `/queue` | `/q` | Show pending messages with index numbers |
41
+ | `/queue-clear` | `/q-clear` | Clear all queued messages |
42
+ | `/queue-pause` | `/q-pause` | Pause auto-firing (queue stays intact) |
43
+ | `/queue-resume` | `/q-resume` | Resume auto-firing and fire next immediately |
44
+ | `/queue-flush` | `/q-flush` | Drain all remaining messages, one per turn, ignoring pause / error stop |
45
+ | `/queue-rm <index>` | `/q-rm <index>` | Remove item at position (1-based) |
46
+ | `/queue-next <msg>` | `/q-next <msg>` | Insert message at front (fires next) |
47
+ | `/queue-file <path>` | `/q-file <path>` | Queue lines from a file (skips `#` comments) |
48
+
49
+ ## How It Works
50
+
51
+ Queued messages are intentionally delivered **after** the current turn settles. The extension listens for the `agent_settled` event and only then fires the next message as a new user message — so follow-ups you queue mid-run never interrupt the turn in progress. They execute cleanly afterwards, one at a time.
52
+
53
+ 1. You `/q` one or more messages while the agent is working (or idle).
54
+ 2. The extension listens for the `agent_settled` event — fired when the agent fully settles.
55
+ 3. On settle, the next queued message fires as a new user message via `pi.sendUserMessage()`.
56
+ 4. Messages execute one at a time, in FIFO order.
57
+ 5. Slash commands (messages starting with `/`) are dispatched through the command system automatically.
58
+
59
+ ## Example usage
60
+
61
+ Queue follow-ups while the agent is working:
62
+
63
+ ```
64
+ > /queue refactor the auth module next
65
+ Queued #1: "refactor the auth module next"
66
+
67
+ > /queue run the test suite
68
+ Queued #2: "run the test suite"
69
+
70
+ > /queue
71
+ Queued (2):
72
+ 1. refactor the auth module next
73
+ 2. run the test suite
74
+ ```
75
+
76
+ Once the current turn settles, the queued messages fire one at a time as new user messages:
77
+
78
+ ```
79
+ → Queue: running 1/2 # "refactor the auth module next" is sent
80
+ → Queue: running 2/2 # "run the test suite" is sent
81
+ ```
82
+
83
+ Changed your mind? Clear the queue instead:
84
+
85
+ ```
86
+ > /queue-clear
87
+ Cleared 2 queued message(s)
88
+ ```
89
+
90
+ With nothing queued:
91
+
92
+ ```
93
+ > /queue
94
+ Queue is empty. /queue <message> to add.
95
+ ```
96
+
97
+ If the queue is paused and empty:
98
+
99
+ ```
100
+ > /queue-pause
101
+ Queue paused.
102
+
103
+ > /queue
104
+ Queue is empty and paused. /queue <message> to add.
105
+ ```
106
+
107
+ ## Features
108
+
109
+ ### Stop on Error
110
+
111
+ If a tool error occurs during a turn, the queue auto-pauses and notifies you. Use `/q-resume` to continue, or `/q-rm` to skip the problematic next item.
112
+
113
+ ### Progress
114
+
115
+ During a drain sequence, you'll see: `Queue: running 2/5` — showing which item is firing out of the total batch.
116
+
117
+ ### Queue from File
118
+
119
+ Load a batch of messages from a file:
120
+
121
+ ```
122
+ # tasks.txt
123
+ add error handling to the auth module
124
+ write tests for it
125
+ # this line is skipped
126
+ update the README
127
+ ```
128
+
129
+ ```
130
+ /q-file tasks.txt
131
+ → Queued 3 item(s) from tasks.txt
132
+ ```
133
+
134
+ ### Flush
135
+
136
+ `/q-flush` drains **all** remaining queued messages back-to-back, one per turn — overriding both a manual pause and the stop-on-error behavior. Useful when you want the whole backlog fired off at once regardless of a mid-batch error.
137
+
138
+ ```
139
+ > /q-flush
140
+ Flush armed — firing all 3 remaining, one per turn.
141
+ → Queue: running 1/3
142
+ → Queue: running 2/3
143
+ → Queue: running 3/3
144
+ ```
145
+
146
+ Running `/q-flush` on an empty queue reports `Queue already empty.`
147
+
148
+ ## Persistence
149
+
150
+ Queue state and pause flag persist across `/reload` and session restarts via `session_shutdown`/`session_start` hooks.
151
+
152
+ ## Example
153
+
154
+ ```
155
+ > /q add error handling to the auth module
156
+ Queued #1: "add error handling to the auth module"
157
+
158
+ > /q write tests for it
159
+ Queued #2: "write tests for it"
160
+
161
+ > /q
162
+ Queued (2):
163
+ 1. add error handling to the auth module
164
+ 2. write tests for it
165
+
166
+ > /q-next fix the lint errors first
167
+ Inserted at #1: "fix the lint errors first" (3 total)
168
+
169
+ > /q-pause
170
+ Queue paused.
171
+
172
+ > /q-rm 3
173
+ Removed #3: "write tests for it" (2 remaining)
174
+
175
+ > /q-resume
176
+ Queue resumed. Firing next (2 remaining).
177
+ → Queue: running 1/2
178
+ ```
@@ -1,9 +1,19 @@
1
1
  {
2
2
  "name": "@vs4vijay/pi-queue",
3
- "version": "0.1.0",
3
+ "version": "0.6.0",
4
4
  "description": "Queue messages to run after the current Pi session turn settles",
5
5
  "type": "module",
6
6
  "keywords": ["pi-package"],
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/vs4vijay/piverse.git"
11
+ },
12
+ "author": "vs4vijay",
13
+ "homepage": "https://github.com/vs4vijay/piverse",
14
+ "bugs": {
15
+ "url": "https://github.com/vs4vijay/piverse/issues"
16
+ },
7
17
  "pi": {
8
18
  "extensions": ["./src/index.ts"]
9
19
  },
@@ -12,5 +22,8 @@
12
22
  },
13
23
  "peerDependencies": {
14
24
  "@earendil-works/pi-coding-agent": "*"
25
+ },
26
+ "devDependencies": {
27
+ "typescript": "^5.0.0"
15
28
  }
16
29
  }
@@ -0,0 +1,79 @@
1
+ # pi-queue — Feature Plan
2
+
3
+ ## Current State
4
+
5
+ FIFO message queue that fires after each agent turn settles. Commands: `/queue <msg>`, `/queue` (show), `/queue-clear`, `/queue-pause`, `/queue-resume`, `/queue-flush`, `/queue-rm`, `/queue-next`, `/queue-file`. Persists across session restarts. Auto-pauses on tool error; flushes override both pause and error stop.
6
+
7
+ All planned features below are implemented.
8
+
9
+ ---
10
+
11
+ ## Planned Features
12
+
13
+ ### 1. Alias: `/q`
14
+
15
+ - Register `/q` as an alias for `/queue` (same handler).
16
+ - Similarly `/q-pause`, `/q-resume`, `/q-rm`, `/q-next`, `/q-file`, `/q-clear`.
17
+ - Shorter to type during fast workflows.
18
+ - **Done.**
19
+
20
+ ### 2. Pause / Resume
21
+
22
+ - `/queue-pause` — stops auto-firing on `agent_settled`. Queue stays intact.
23
+ - `/queue-resume` — re-enables auto-firing and immediately fires next if agent is idle.
24
+ - Show paused state in `/queue` output (e.g., `"Queued (3, PAUSED)"`); when paused and empty, show a combined empty+paused message.
25
+ - **Done.**
26
+
27
+ ### 3. Remove / Priority Insert
28
+
29
+ - `/queue-rm <index>` — remove item at position (1-based).
30
+ - `/queue-next <message>` — insert at position 1 (fire next).
31
+ - **Done.**
32
+
33
+ ### 4. Stop on Error
34
+
35
+ - If the agent's previous turn produced an error (tool failure, error event), auto-pause the queue.
36
+ - Notify: `"Queue paused — previous turn had errors. /queue-resume to continue."`.
37
+ - Error detection: watch `tool_result` events for `isError`.
38
+ - **Done.**
39
+
40
+ ### 5. Progress Notification
41
+
42
+ - On each `agent_settled` drain, notify: `"Queue: running 2/5"`.
43
+ - Track total at start of drain batch vs remaining.
44
+ - Reset counter when queue empties or new items are added after drain completes.
45
+ - **Done.**
46
+
47
+ ### 6. Queue from File
48
+
49
+ - `/queue-file <path>` — read file, each non-empty line becomes a queued message.
50
+ - Skip lines starting with `#` (comments).
51
+ - Report count added.
52
+ - **Done.**
53
+
54
+ ### 7. Flush
55
+
56
+ - `/queue-flush` — arm a drain that fires **all** remaining messages, one per `agent_settled`, ignoring both the manual pause and the stop-on-error pause, until the queue is empty.
57
+ - Repeatedly sending `pi.sendUserMessage` one per turn keeps the extension's "fire after settle, one at a time" model intact and lets each message complete cleanly.
58
+ - **Done.**
59
+
60
+ ---
61
+
62
+ ## Implementation Order
63
+
64
+ | Phase | Features | Reason |
65
+ |-------|----------|--------|
66
+ | 1 | Alias, Pause/Resume, Remove, Queue-next | Steer a running queue |
67
+ | 2 | Stop on Error, Progress | Safety + visibility |
68
+ | 3 | Queue from File | Batch input |
69
+ | 4 | Flush | Drain the whole backlog on demand |
70
+
71
+ ---
72
+
73
+ ## Notes
74
+
75
+ - All commands register via `pi.registerCommand`. Each command gets both `/queue-*` and `/q-*` variants.
76
+ - State additions (pause flag, progress counter) persist via existing `session_shutdown`/`session_start` pattern. The transient `flushing` flag resets on empty and on session start.
77
+ - The queue sends messages as-is — shell commands ("run npm test") work because the agent interprets them like any user message.
78
+ - Slash commands (e.g., `/review src/index.ts`, `/commit`) are supported: when a queued message starts with `/`, pass `{ expandPromptTemplates: true }` to `pi.sendUserMessage()` so it routes through the command dispatcher instead of going to the agent as plain text.
79
+ - Handlers type their context as `ExtensionCommandContext` from `@earendil-works/pi-coding-agent`.
@@ -1,52 +1,160 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
1
+ import { readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
4
 
3
5
  export default function (pi: ExtensionAPI) {
4
6
  let queue: string[] = [];
7
+ let paused = false;
8
+ let lastTurnHadError = false;
9
+ let drainTotal = 0;
10
+ let flushing = false;
5
11
 
6
- pi.registerCommand("queue", {
7
- description: "Queue a message to send after the current turn settles. No args = show queue.",
8
- handler: async (args, ctx) => {
9
- if (!args.trim()) {
10
- if (queue.length === 0) {
11
- ctx.ui.notify("Queue is empty. /queue <message> to add.", "info");
12
- } else {
13
- const display = queue.map((m, i) => `${i + 1}. ${m}`).join("\n");
14
- ctx.ui.notify(`Queued (${queue.length}):\n${display}`, "info");
15
- }
16
- return;
12
+ // Register command under both /queue-* and /q-* names
13
+ function cmd(name: string, description: string, handler: (args: string, ctx: ExtensionCommandContext) => Promise<void>) {
14
+ const opts = { description, handler };
15
+ pi.registerCommand(`queue${name}`, opts);
16
+ pi.registerCommand(`q${name}`, opts);
17
+ }
18
+
19
+ cmd("", "Queue a message to send after the current turn settles. No args = show queue.", async (args, ctx) => {
20
+ if (!args.trim()) {
21
+ if (queue.length === 0) {
22
+ ctx.ui.notify(
23
+ paused
24
+ ? "Queue is empty and paused. /queue <message> to add."
25
+ : "Queue is empty. /queue <message> to add.",
26
+ "info"
27
+ );
28
+ } else {
29
+ const label = paused ? `Queued (${queue.length}, PAUSED)` : `Queued (${queue.length})`;
30
+ const display = queue.map((m, i) => `${i + 1}. ${m}`).join("\n");
31
+ ctx.ui.notify(`${label}:\n${display}`, "info");
17
32
  }
18
- queue.push(args.trim());
19
- ctx.ui.notify(`Queued #${queue.length}: "${args.trim()}"`, "info");
20
- },
33
+ return;
34
+ }
35
+ queue.push(args.trim());
36
+ ctx.ui.notify(`Queued #${queue.length}: "${args.trim()}"`, "info");
37
+ });
38
+
39
+ cmd("-clear", "Clear all queued messages", async (_args, ctx) => {
40
+ const count = queue.length;
41
+ queue = [];
42
+ ctx.ui.notify(count ? `Cleared ${count} queued message(s)` : "Queue already empty", "info");
43
+ });
44
+
45
+ cmd("-pause", "Pause queue auto-firing", async (_args, ctx) => {
46
+ paused = true;
47
+ ctx.ui.notify("Queue paused.", "info");
48
+ });
49
+
50
+ cmd("-resume", "Resume queue auto-firing", async (_args, ctx) => {
51
+ paused = false;
52
+ if (queue.length > 0) {
53
+ ctx.ui.notify(`Queue resumed. Firing next (${queue.length} remaining).`, "info");
54
+ const next = queue.shift()!;
55
+ const opts = next.startsWith("/") ? { expandPromptTemplates: true } : {};
56
+ pi.sendUserMessage(next, opts);
57
+ } else {
58
+ ctx.ui.notify("Queue resumed (empty).", "info");
59
+ }
60
+ });
61
+
62
+ cmd("-flush", "Drain all remaining queued messages, one per turn, ignoring pause/error stop", async (_args, ctx) => {
63
+ if (queue.length === 0) {
64
+ ctx.ui.notify("Queue already empty.", "info");
65
+ return;
66
+ }
67
+ flushing = true;
68
+ paused = false;
69
+ ctx.ui.notify(`Flush armed — firing all ${queue.length} remaining, one per turn.`, "info");
70
+ });
71
+
72
+ cmd("-rm", "Remove a queued item by index (1-based)", async (args, ctx) => {
73
+ const idx = parseInt(args.trim(), 10);
74
+ if (isNaN(idx) || idx < 1 || idx > queue.length) {
75
+ ctx.ui.notify(`Invalid index. Use 1-${queue.length}.`, "warning");
76
+ return;
77
+ }
78
+ const [removed] = queue.splice(idx - 1, 1);
79
+ ctx.ui.notify(`Removed #${idx}: "${removed}" (${queue.length} remaining)`, "info");
80
+ });
81
+
82
+ cmd("-next", "Insert a message at the front of the queue (fires next)", async (args, ctx) => {
83
+ const msg = args.trim();
84
+ if (!msg) {
85
+ ctx.ui.notify("Usage: /queue-next <message>", "warning");
86
+ return;
87
+ }
88
+ queue.unshift(msg);
89
+ ctx.ui.notify(`Inserted at #1: "${msg}" (${queue.length} total)`, "info");
90
+ });
91
+
92
+ cmd("-file", "Queue messages from a file (one per line, # comments skipped)", async (args, ctx) => {
93
+ const filePath = args.trim();
94
+ if (!filePath) {
95
+ ctx.ui.notify("Usage: /queue-file <path>", "warning");
96
+ return;
97
+ }
98
+ const resolved = resolve(process.cwd(), filePath);
99
+ let content: string;
100
+ try {
101
+ content = readFileSync(resolved, "utf-8");
102
+ } catch {
103
+ ctx.ui.notify(`Cannot read file: ${resolved}`, "error");
104
+ return;
105
+ }
106
+ const lines = content.split("\n").filter((l) => l.trim() && !l.trim().startsWith("#"));
107
+ for (const line of lines) queue.push(line.trim());
108
+ ctx.ui.notify(`Queued ${lines.length} item(s) from ${filePath}`, "info");
21
109
  });
22
110
 
23
- pi.registerCommand("queue-clear", {
24
- description: "Clear all queued messages",
25
- handler: async (_args, ctx) => {
26
- const count = queue.length;
27
- queue = [];
28
- ctx.ui.notify(count ? `Cleared ${count} queued message(s)` : "Queue already empty", "info");
29
- },
111
+ pi.on("tool_result", async (event) => {
112
+ if (event.isError) lastTurnHadError = true;
30
113
  });
31
114
 
32
- pi.on("agent_settled", async () => {
33
- if (queue.length === 0) return;
115
+ pi.on("agent_settled", async (_event, ctx) => {
116
+ if (queue.length === 0) {
117
+ drainTotal = 0;
118
+ flushing = false;
119
+ return;
120
+ }
121
+ if (!flushing) {
122
+ if (lastTurnHadError) {
123
+ paused = true;
124
+ lastTurnHadError = false;
125
+ ctx.ui.notify("Queue paused — previous turn had errors. /queue-resume to continue.", "warning");
126
+ return;
127
+ }
128
+ if (paused) return;
129
+ }
130
+ lastTurnHadError = false;
131
+
132
+ // Track progress
133
+ if (drainTotal === 0) drainTotal = queue.length;
134
+ const current = drainTotal - queue.length + 1;
135
+ ctx.ui.notify(`Queue: running ${current}/${drainTotal}`, "info");
136
+
34
137
  const next = queue.shift()!;
35
- pi.sendUserMessage(next);
138
+ const opts = next.startsWith("/") ? { expandPromptTemplates: true } : {};
139
+ pi.sendUserMessage(next, opts);
36
140
  });
37
141
 
38
142
  pi.on("session_start", async (_event, ctx) => {
39
143
  queue = [];
144
+ paused = false;
145
+ flushing = false;
40
146
  for (const entry of ctx.sessionManager.getEntries()) {
41
147
  if (entry.type === "custom" && entry.customType === "piverse-queue-state") {
42
- queue = (entry.data as { queue: string[] }).queue ?? [];
148
+ const data = entry.data as { queue: string[]; paused?: boolean };
149
+ queue = data.queue ?? [];
150
+ paused = data.paused ?? false;
43
151
  }
44
152
  }
45
153
  });
46
154
 
47
155
  pi.on("session_shutdown", async () => {
48
- if (queue.length > 0) {
49
- pi.appendEntry("piverse-queue-state", { queue });
156
+ if (queue.length > 0 || paused) {
157
+ pi.appendEntry("piverse-queue-state", { queue, paused });
50
158
  }
51
159
  });
52
160
  }
package/package.json CHANGED
@@ -1,17 +1,33 @@
1
1
  {
2
2
  "name": "@vs4vijay/piverse",
3
- "version": "0.1.0",
4
- "description": "A multi-extension ecosystem for the Pi coding agent",
5
- "keywords": ["pi-package"],
3
+ "version": "0.6.0",
4
+ "description": "A multi-extension ecosystem for the Pi coding agent — message queueing (/queue) and project notes (/notes) extensions",
5
+ "keywords": [
6
+ "pi-package"
7
+ ],
8
+ "license": "MIT",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/vs4vijay/piverse.git"
12
+ },
13
+ "author": "vs4vijay",
14
+ "homepage": "https://github.com/vs4vijay/piverse",
15
+ "bugs": {
16
+ "url": "https://github.com/vs4vijay/piverse/issues"
17
+ },
6
18
  "type": "module",
7
19
  "pi": {
8
- "extensions": ["./extensions/pi-queue/src/index.ts"]
20
+ "extensions": [
21
+ "./extensions/pi-queue/src/index.ts",
22
+ "./extensions/pi-notes/src/index.ts"
23
+ ]
9
24
  },
10
25
  "scripts": {
11
26
  "typecheck": "tsc --noEmit"
12
27
  },
13
28
  "peerDependencies": {
14
- "@earendil-works/pi-coding-agent": "*",
29
+ "@earendil-works/pi-coding-agent": "^0.84.2",
30
+ "@earendil-works/pi-tui": "^0.84.2",
15
31
  "typebox": "*"
16
32
  },
17
33
  "devDependencies": {