opencode-queue 0.6.0 → 0.6.2
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 -1
- package/index.ts +40 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ This plugin adds a real `/queue` slash command that keeps the current run focuse
|
|
|
9
9
|
- Queues normal prompts entered while a session is busy
|
|
10
10
|
- Queues prompts with either `/queue prompt` or `prompt /queue`
|
|
11
11
|
- Queues slash commands with either `/queue /review` or `/review /queue`
|
|
12
|
+
- Queues shell commands with `/queue !ls`
|
|
12
13
|
- Hides queued placeholders from both the UI transcript and the running agent
|
|
13
14
|
- Preserves the selected agent, model, and thinking variant for queued input
|
|
14
15
|
- Replays queued input in order once the session becomes idle
|
|
@@ -39,6 +40,7 @@ While the agent is busy:
|
|
|
39
40
|
/queue continue after the current task finishes
|
|
40
41
|
/queue /review
|
|
41
42
|
/queue /commit
|
|
43
|
+
/queue !ls
|
|
42
44
|
continue after the current task finishes /queue
|
|
43
45
|
/review /queue
|
|
44
46
|
/queue list
|
|
@@ -50,6 +52,7 @@ When the session is idle:
|
|
|
50
52
|
```text
|
|
51
53
|
/queue hello
|
|
52
54
|
/queue /review
|
|
55
|
+
/queue !date
|
|
53
56
|
hello /queue
|
|
54
57
|
/review /queue
|
|
55
58
|
/queue
|
|
@@ -63,9 +66,10 @@ Queued items stay hidden while the current run is still working, then replay aut
|
|
|
63
66
|
- Idle `/queue some text` is treated like a normal prompt with the `/queue` prefix removed.
|
|
64
67
|
- Idle `some text /queue` and `/command /queue` run immediately with the trailing `/queue` removed.
|
|
65
68
|
- Idle `/queue /command` immediately runs the nested command.
|
|
69
|
+
- Idle `/queue !command` immediately runs the shell command as an OpenCode shell block.
|
|
66
70
|
- `/queue` and `/queue list` show the in-memory queue for the current session.
|
|
67
71
|
- `/queue clear` drops all currently queued items for the current session.
|
|
68
|
-
-
|
|
72
|
+
- Native shell-mode suffixes like `!command /queue` are not supported because OpenCode handles leading `!` before plugin command hooks run.
|
|
69
73
|
|
|
70
74
|
## License
|
|
71
75
|
|
package/index.ts
CHANGED
|
@@ -9,18 +9,22 @@ const HANDLED = "__QUEUE_HANDLED__"
|
|
|
9
9
|
type InputPart = TextPartInput | FilePartInput | AgentPartInput | SubtaskPartInput
|
|
10
10
|
type Model = { providerID: string; modelID: string }
|
|
11
11
|
type Meta = { variant?: string; controls?: string[]; fast?: boolean }
|
|
12
|
+
type Run = { agent: string; model?: Model }
|
|
12
13
|
type Info = { agent: string; model: Model } & Meta
|
|
13
14
|
type Msg = { info: { role: string; agent?: string; mode?: string; model?: Model; providerID?: string; modelID?: string } & Meta }
|
|
14
15
|
|
|
15
16
|
type Item =
|
|
16
17
|
| { kind: "prompt"; info: Info; text: string; parts: InputPart[] }
|
|
17
18
|
| { kind: "command"; info: Info; text: string; cmd: string; args: string; files: FilePartInput[] }
|
|
19
|
+
| { kind: "shell"; info: Info; text: string; shell: string }
|
|
18
20
|
|
|
19
21
|
type Op =
|
|
20
22
|
| { kind: "list" }
|
|
21
23
|
| { kind: "clear" }
|
|
24
|
+
| { kind: "invalid"; text: string }
|
|
22
25
|
| { kind: "prompt"; text: string; body: string }
|
|
23
26
|
| { kind: "command"; text: string; cmd: string; args: string }
|
|
27
|
+
| { kind: "shell"; text: string; shell: string }
|
|
24
28
|
|
|
25
29
|
const label = (body: string, files: number) => {
|
|
26
30
|
const text = body.trim() || `${files} attachment${files === 1 ? "" : "s"}`
|
|
@@ -34,6 +38,13 @@ const parse = (body: string, files = 0): Op => {
|
|
|
34
38
|
if (text === "clear") return { kind: "clear" }
|
|
35
39
|
}
|
|
36
40
|
|
|
41
|
+
if (text.startsWith("!")) {
|
|
42
|
+
const shell = text.slice(1).trim()
|
|
43
|
+
if (!shell) return { kind: "invalid", text: "Queue shell command is empty" }
|
|
44
|
+
if (files) return { kind: "invalid", text: "Queued shell commands do not support attachments" }
|
|
45
|
+
return { kind: "shell", text, shell }
|
|
46
|
+
}
|
|
47
|
+
|
|
37
48
|
const match = text.match(CMD)
|
|
38
49
|
if (match) return { kind: "command", text, cmd: match[1], args: match[2] ?? "" }
|
|
39
50
|
return { kind: "prompt", text: label(body, files), body }
|
|
@@ -88,11 +99,21 @@ export const QueuePlugin: Plugin = async ({ client }) => {
|
|
|
88
99
|
return undefined
|
|
89
100
|
}
|
|
90
101
|
|
|
102
|
+
const run = async (sid: string): Promise<Run> => {
|
|
103
|
+
const info = await latest(sid)
|
|
104
|
+
if (info) return info
|
|
105
|
+
console.warn("QueuePlugin shell replay fell back to the build agent because the session has no message context")
|
|
106
|
+
return { agent: "build" }
|
|
107
|
+
}
|
|
108
|
+
|
|
91
109
|
const opts = (info: Info) => ({ agent: info.agent, model: info.model, variant: info.variant, controls: info.controls, fast: info.fast })
|
|
92
110
|
|
|
93
111
|
const prompt = (sid: string, info: Info, parts: InputPart[], noReply?: boolean) => client.session.prompt({ path: { id: sid }, body: { ...opts(info), noReply, parts } as any })
|
|
112
|
+
const shell = (sid: string, command: string, info: Run) => client.session.shell({ path: { id: sid }, body: { agent: info.agent, model: info.model, command } })
|
|
94
113
|
|
|
95
114
|
const replay = async (sid: string, item: Item) => {
|
|
115
|
+
if (item.kind === "shell") return shell(sid, item.shell, item.info)
|
|
116
|
+
|
|
96
117
|
if (item.kind === "command") {
|
|
97
118
|
await prompt(sid, item.info, [{ type: "text", text: item.text }, ...item.files], true)
|
|
98
119
|
await client.session.command({
|
|
@@ -120,7 +141,7 @@ export const QueuePlugin: Plugin = async ({ client }) => {
|
|
|
120
141
|
|
|
121
142
|
flushing.add(sid)
|
|
122
143
|
try {
|
|
123
|
-
|
|
144
|
+
await replay(sid, list.shift()!)
|
|
124
145
|
} catch (error) {
|
|
125
146
|
console.error("QueuePlugin failed to flush queued input", error)
|
|
126
147
|
await toast(`Queue failed: ${error instanceof Error ? error.message : String(error)}`, "error")
|
|
@@ -170,8 +191,14 @@ export const QueuePlugin: Plugin = async ({ client }) => {
|
|
|
170
191
|
const op = parse(body, parts.length)
|
|
171
192
|
|
|
172
193
|
if (op.kind === "list" || op.kind === "clear") return stop(manage(sid, op))
|
|
194
|
+
if (op.kind === "invalid") return stop(op.text, "error")
|
|
173
195
|
|
|
174
196
|
if (!busy.has(sid)) {
|
|
197
|
+
if (op.kind === "shell") {
|
|
198
|
+
await shell(sid, op.shell, await run(sid))
|
|
199
|
+
throw new Error(HANDLED)
|
|
200
|
+
}
|
|
201
|
+
|
|
175
202
|
if (op.kind === "command") {
|
|
176
203
|
await client.session.prompt({ path: { id: sid }, body: { noReply: true, parts: [{ type: "text", text: op.text }, ...parts] } })
|
|
177
204
|
await client.session.command({ path: { id: sid }, body: { command: op.cmd, arguments: op.args, parts } as any })
|
|
@@ -203,8 +230,19 @@ export const QueuePlugin: Plugin = async ({ client }) => {
|
|
|
203
230
|
return
|
|
204
231
|
}
|
|
205
232
|
|
|
233
|
+
if (op.kind === "invalid") {
|
|
234
|
+
hide(output.message.id, text)
|
|
235
|
+
await toast(op.text, "error", 5000)
|
|
236
|
+
return
|
|
237
|
+
}
|
|
238
|
+
|
|
206
239
|
if (!busy.has(sid)) {
|
|
207
240
|
if (op.kind === "command") return
|
|
241
|
+
if (op.kind === "shell") {
|
|
242
|
+
hide(output.message.id, text)
|
|
243
|
+
await shell(sid, op.shell, { agent: output.message.agent, model: output.message.model })
|
|
244
|
+
return
|
|
245
|
+
}
|
|
208
246
|
text.text = body
|
|
209
247
|
return
|
|
210
248
|
}
|
|
@@ -221,7 +259,7 @@ export const QueuePlugin: Plugin = async ({ client }) => {
|
|
|
221
259
|
console.warn("QueuePlugin skipped unexpected part", part.type)
|
|
222
260
|
return []
|
|
223
261
|
})
|
|
224
|
-
const item: Item = op.kind === "command" ? { ...op, info, files: parts } : { ...op, info, parts: inputParts() }
|
|
262
|
+
const item: Item = op.kind === "shell" ? { ...op, info } : op.kind === "command" ? { ...op, info, files: parts } : { ...op, info, parts: inputParts() }
|
|
225
263
|
|
|
226
264
|
queue.set(sid, [...(queue.get(sid) ?? []), item])
|
|
227
265
|
hide(output.message.id, text)
|
package/package.json
CHANGED