@kaiserlich-dev/pi-queue-picker 1.0.0 → 1.0.3

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
@@ -23,6 +23,8 @@ When the agent is idle, Enter submits normally. When the agent is busy:
23
23
 
24
24
  The picker remembers your last chosen mode as the default.
25
25
 
26
+ Slash commands (for example `/model`, `/settings`, `/skill:name`, or `/prompt-template`) bypass the picker while busy so interactive command UIs still open normally.
27
+
26
28
  ### Editing queued messages
27
29
 
28
30
  Queued messages stay in an editable buffer until the agent finishes. To edit them:
@@ -82,6 +84,13 @@ pi install git:github.com/kaiserlich-dev/pi-queue-picker
82
84
 
83
85
  Then restart pi or run `/reload`.
84
86
 
87
+ ## Development
88
+
89
+ ```bash
90
+ # Run locally without installing
91
+ pi -e ./extensions/queue-picker.ts
92
+ ```
93
+
85
94
  ## Compatibility
86
95
 
87
96
  Works alongside other extensions that customize the editor (e.g. `pi-powerline-footer`). Uses the `input` event API instead of replacing the editor component.
@@ -45,6 +45,22 @@ function isLimitedTerminal(): boolean {
45
45
  return false;
46
46
  }
47
47
 
48
+ /**
49
+ * Commands like /model, /settings, /skill:name should bypass the delivery picker.
50
+ * They are interactive control flow, not chat content.
51
+ */
52
+ export function shouldBypassPicker(text: string): boolean {
53
+ const trimmed = text.trim();
54
+ if (!trimmed.startsWith("/")) return false;
55
+
56
+ const firstToken = trimmed.split(/\s+/, 1)[0];
57
+ if (firstToken === "/") return true;
58
+
59
+ // Commands are one slash-prefixed token without extra slashes, e.g. /model, /skill:name
60
+ if (firstToken.slice(1).includes("/")) return false;
61
+ return /^\/[a-z0-9:_-]+$/i.test(firstToken);
62
+ }
63
+
48
64
  const PICKER_WIDTH = 72;
49
65
  const BOX_WIDTH = 76;
50
66
 
@@ -56,25 +72,23 @@ export default function (pi: ExtensionAPI) {
56
72
 
57
73
  // --- Helpers ---
58
74
 
59
- function sendToPi(text: string, isIdle: boolean, mode: PickerMode) {
60
- if (isIdle) {
61
- pi.sendUserMessage(text);
62
- } else {
63
- pi.sendUserMessage(text, { deliverAs: mode });
64
- }
75
+ function sendToPi(text: string, mode: PickerMode) {
76
+ // Always specify deliverAs for safety — even at agent_end another
77
+ // extension handler may have already started a new turn.
78
+ pi.sendUserMessage(text, { deliverAs: mode });
65
79
  }
66
80
 
67
- function flushOneQueuedMessage(isIdle: boolean) {
81
+ function flushOneQueuedMessage() {
68
82
  const next = shiftNext(buffer);
69
83
  if (!next) return;
70
- sendToPi(next.text, isIdle, next.mode);
84
+ sendToPi(next.text, next.mode);
71
85
  updateWidget(uiRef, buffer);
72
86
  }
73
87
 
74
88
  function flushOneSteerWhileBusy() {
75
89
  const steerMsg = shiftNextSteer(buffer);
76
90
  if (!steerMsg) return;
77
- sendToPi(steerMsg.text, false, "steer");
91
+ sendToPi(steerMsg.text, "steer");
78
92
  updateWidget(uiRef, buffer);
79
93
  }
80
94
 
@@ -142,7 +156,7 @@ export default function (pi: ExtensionAPI) {
142
156
  updateWidget(uiRef, buffer);
143
157
 
144
158
  if (ctx.isIdle() && buffer.length > 0) {
145
- flushOneQueuedMessage(true);
159
+ flushOneQueuedMessage();
146
160
  } else if (!ctx.isIdle()) {
147
161
  flushOneSteerWhileBusy();
148
162
  }
@@ -155,14 +169,9 @@ export default function (pi: ExtensionAPI) {
155
169
  clearBuffer();
156
170
  });
157
171
 
158
- pi.on("session_switch", (_event, ctx) => {
159
- uiRef = ctx.ui;
160
- clearBuffer();
161
- });
162
-
163
- pi.on("agent_end", async (_event, ctx) => {
172
+ pi.on("agent_end", async (_event, _ctx) => {
164
173
  if (editingQueue || buffer.length === 0) return;
165
- flushOneQueuedMessage(ctx.isIdle());
174
+ flushOneQueuedMessage();
166
175
  });
167
176
 
168
177
  pi.on("input", async (event, ctx) => {
@@ -174,6 +183,10 @@ export default function (pi: ExtensionAPI) {
174
183
  return { action: "continue" as const };
175
184
  }
176
185
 
186
+ if (shouldBypassPicker(event.text)) {
187
+ return { action: "continue" as const };
188
+ }
189
+
177
190
  if (isLimitedTerminal()) {
178
191
  return { action: "continue" as const };
179
192
  }
@@ -224,7 +237,7 @@ export default function (pi: ExtensionAPI) {
224
237
  lastMode = mode;
225
238
 
226
239
  if (mode === "steer") {
227
- pi.sendUserMessage(event.text, { deliverAs: "steer" });
240
+ sendToPi(event.text, "steer");
228
241
  ctx.ui.notify(`Steer: ${event.text}`, "info");
229
242
  } else {
230
243
  addMessage(buffer, {
@@ -239,7 +252,7 @@ export default function (pi: ExtensionAPI) {
239
252
  );
240
253
 
241
254
  if (ctx.isIdle()) {
242
- flushOneQueuedMessage(true);
255
+ flushOneQueuedMessage();
243
256
  }
244
257
  }
245
258
 
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@kaiserlich-dev/pi-queue-picker",
3
- "version": "1.0.0",
3
+ "version": "1.0.3",
4
4
  "description": "Pick between steering and follow-up when queuing messages in pi",
5
5
  "keywords": [
6
- "pi-package"
6
+ "pi-package",
7
+ "pi-coding-agent",
8
+ "queue",
9
+ "message-buffer"
7
10
  ],
8
11
  "license": "MIT",
9
12
  "repository": {
@@ -26,8 +29,8 @@
26
29
  "image": "https://raw.githubusercontent.com/kaiserlich-dev/pi-queue-picker/main/assets/preview.png"
27
30
  },
28
31
  "peerDependencies": {
29
- "@mariozechner/pi-coding-agent": "*",
30
- "@mariozechner/pi-tui": "*"
32
+ "@mariozechner/pi-coding-agent": "^0.65.0",
33
+ "@mariozechner/pi-tui": "^0.65.0"
31
34
  },
32
35
  "scripts": {
33
36
  "publish:pi": "bash ./scripts/publish.sh",