opencode-queue 0.2.0 → 0.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 +15 -3
- package/index.ts +64 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Queue OpenCode input until the current agent run is actually idle.
|
|
4
4
|
|
|
5
|
-
This plugin adds a `/queue
|
|
5
|
+
This plugin adds a real `/queue` slash command that keeps the current run focused instead of injecting your next message into the still-running loop.
|
|
6
6
|
|
|
7
7
|
## What it does
|
|
8
8
|
|
|
@@ -11,7 +11,9 @@ This plugin adds a `/queue ...` prefix that keeps the current run focused instea
|
|
|
11
11
|
- Hides queued placeholders from both the UI transcript and the running agent
|
|
12
12
|
- Replays queued input in order once the session becomes idle
|
|
13
13
|
- Replays queued commands as a visible `/command` message before executing them
|
|
14
|
+
- Registers `/queue` as a real OpenCode slash command
|
|
14
15
|
- Shows the current queue with `/queue list`
|
|
16
|
+
- Clears the current queue with `/queue clear`
|
|
15
17
|
|
|
16
18
|
## Install
|
|
17
19
|
|
|
@@ -36,6 +38,15 @@ While the agent is busy:
|
|
|
36
38
|
/queue /review
|
|
37
39
|
/queue /commit
|
|
38
40
|
/queue list
|
|
41
|
+
/queue clear
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
When the session is idle:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
/queue hello
|
|
48
|
+
/queue /review
|
|
49
|
+
/queue
|
|
39
50
|
```
|
|
40
51
|
|
|
41
52
|
Queued items stay hidden while the current run is still working, then replay automatically when the session becomes idle.
|
|
@@ -44,8 +55,9 @@ Queued items stay hidden while the current run is still working, then replay aut
|
|
|
44
55
|
|
|
45
56
|
- This is a `/queue` plugin, not a keyboard shortcut plugin. OpenCode plugins cannot currently register custom TUI keybindings.
|
|
46
57
|
- Idle `/queue some text` is treated like a normal prompt with the `/queue` prefix removed.
|
|
47
|
-
- Idle `/queue /command`
|
|
48
|
-
- `/queue list`
|
|
58
|
+
- Idle `/queue /command` immediately runs the nested command.
|
|
59
|
+
- `/queue` and `/queue list` show the in-memory queue for the current session.
|
|
60
|
+
- `/queue clear` drops all currently queued items for the current session.
|
|
49
61
|
|
|
50
62
|
## License
|
|
51
63
|
|
package/index.ts
CHANGED
|
@@ -10,10 +10,11 @@ import type {
|
|
|
10
10
|
|
|
11
11
|
const QUEUE = /^\/queue(?:\s+([\s\S]*))?$/
|
|
12
12
|
const COMMAND = /^\/(\S+)(?:\s+([\s\S]*))?$/
|
|
13
|
+
const HANDLED = "__QUEUE_HANDLED__"
|
|
13
14
|
|
|
14
15
|
type InputPart = TextPartInput | FilePartInput | AgentPartInput | SubtaskPartInput
|
|
15
16
|
type Info = { agent: string; model: { providerID: string; modelID: string } }
|
|
16
|
-
type Item = { info: Info; text: string; parts?: InputPart[]; command?: string; arguments?: string }
|
|
17
|
+
type Item = { info: Info; text: string; parts?: InputPart[]; command?: string; arguments?: string; files?: FilePartInput[] }
|
|
17
18
|
|
|
18
19
|
const label = (body: string, files: number) => {
|
|
19
20
|
const text = body.trim() || `${files} attachment${files === 1 ? "" : "s"}`
|
|
@@ -53,6 +54,12 @@ export const QueuePlugin: Plugin = async ({ client }) => {
|
|
|
53
54
|
return items.map((item, i) => `${i + 1}. ${item.text}`).join("\n")
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
const clear = (sid: string) => {
|
|
58
|
+
const count = queue.get(sid)?.length ?? 0
|
|
59
|
+
queue.delete(sid)
|
|
60
|
+
return count ? `Cleared ${count} queued item${count === 1 ? "" : "s"}` : "Queue is empty"
|
|
61
|
+
}
|
|
62
|
+
|
|
56
63
|
const replay = async (sid: string, item: Item) => {
|
|
57
64
|
if (!item.command || item.arguments === undefined) {
|
|
58
65
|
if (!item.parts?.length) {
|
|
@@ -77,7 +84,7 @@ export const QueuePlugin: Plugin = async ({ client }) => {
|
|
|
77
84
|
agent: item.info.agent,
|
|
78
85
|
model: item.info.model,
|
|
79
86
|
noReply: true,
|
|
80
|
-
parts: [{ type: "text", text: item.text }],
|
|
87
|
+
parts: [{ type: "text", text: item.text }, ...(item.files ?? [])],
|
|
81
88
|
},
|
|
82
89
|
})
|
|
83
90
|
|
|
@@ -88,7 +95,8 @@ export const QueuePlugin: Plugin = async ({ client }) => {
|
|
|
88
95
|
model: `${item.info.model.providerID}/${item.info.model.modelID}`,
|
|
89
96
|
command: item.command,
|
|
90
97
|
arguments: item.arguments,
|
|
91
|
-
|
|
98
|
+
parts: item.files,
|
|
99
|
+
} as any,
|
|
92
100
|
})
|
|
93
101
|
}
|
|
94
102
|
|
|
@@ -118,6 +126,13 @@ export const QueuePlugin: Plugin = async ({ client }) => {
|
|
|
118
126
|
}
|
|
119
127
|
|
|
120
128
|
return {
|
|
129
|
+
config: async (cfg) => {
|
|
130
|
+
cfg.command ??= {}
|
|
131
|
+
cfg.command.queue = {
|
|
132
|
+
template: "",
|
|
133
|
+
description: "Queue input until the session is idle",
|
|
134
|
+
}
|
|
135
|
+
},
|
|
121
136
|
event: async ({ event }) => {
|
|
122
137
|
if (event.type !== "session.status") return
|
|
123
138
|
|
|
@@ -130,6 +145,47 @@ export const QueuePlugin: Plugin = async ({ client }) => {
|
|
|
130
145
|
busy.delete(sid)
|
|
131
146
|
await flush(sid)
|
|
132
147
|
},
|
|
148
|
+
"command.execute.before": async (input, output) => {
|
|
149
|
+
if (input.command !== "queue") return
|
|
150
|
+
|
|
151
|
+
const body = input.arguments ?? ""
|
|
152
|
+
const text = body.trim()
|
|
153
|
+
const files = output.parts.filter((part): part is FilePart => part.type === "file").map((part) => ({ ...part }))
|
|
154
|
+
|
|
155
|
+
if ((!text || text === "list" || text === "clear") && !files.length) {
|
|
156
|
+
await toast(text === "clear" ? clear(input.sessionID) : summary(input.sessionID), "info", 5000)
|
|
157
|
+
throw new Error(HANDLED)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!busy.has(input.sessionID)) {
|
|
161
|
+
const cmd = parseCommand(body)
|
|
162
|
+
if (!cmd) {
|
|
163
|
+
output.parts.length = 0
|
|
164
|
+
output.parts.push({ type: "text", text: body } as any, ...files)
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
await client.session.prompt({
|
|
169
|
+
path: { id: input.sessionID },
|
|
170
|
+
body: {
|
|
171
|
+
noReply: true,
|
|
172
|
+
parts: [{ type: "text", text }, ...files],
|
|
173
|
+
} as any,
|
|
174
|
+
})
|
|
175
|
+
await client.session.command({
|
|
176
|
+
path: { id: input.sessionID },
|
|
177
|
+
body: {
|
|
178
|
+
command: cmd.command,
|
|
179
|
+
arguments: cmd.arguments,
|
|
180
|
+
parts: files,
|
|
181
|
+
} as any,
|
|
182
|
+
})
|
|
183
|
+
throw new Error(HANDLED)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
output.parts.length = 0
|
|
187
|
+
output.parts.push({ type: "text", text: `/queue ${body}` } as any, ...files)
|
|
188
|
+
},
|
|
133
189
|
"chat.message": async ({ sessionID }, output) => {
|
|
134
190
|
const text = output.parts.find((part): part is TextPart => part.type === "text" && !part.synthetic)
|
|
135
191
|
if (!text) return
|
|
@@ -140,9 +196,9 @@ export const QueuePlugin: Plugin = async ({ client }) => {
|
|
|
140
196
|
const files = output.parts.filter((part): part is FilePart => part.type === "file")
|
|
141
197
|
const trimmed = body.trim()
|
|
142
198
|
|
|
143
|
-
if ((!trimmed || trimmed === "list") && !files.length) {
|
|
199
|
+
if ((!trimmed || trimmed === "list" || trimmed === "clear") && !files.length) {
|
|
144
200
|
hide(output.message.id, text)
|
|
145
|
-
await toast(summary(sessionID), "info", 5000)
|
|
201
|
+
await toast(trimmed === "clear" ? clear(sessionID) : summary(sessionID), "info", 5000)
|
|
146
202
|
return
|
|
147
203
|
}
|
|
148
204
|
|
|
@@ -169,7 +225,9 @@ export const QueuePlugin: Plugin = async ({ client }) => {
|
|
|
169
225
|
|
|
170
226
|
const info = { agent: output.message.agent, model: { ...output.message.model } }
|
|
171
227
|
const command = parseCommand(body)
|
|
172
|
-
const item = command
|
|
228
|
+
const item = command
|
|
229
|
+
? { info, text: trimmed, files, ...command }
|
|
230
|
+
: { info, text: label(body, files.length), parts }
|
|
173
231
|
|
|
174
232
|
enqueue(sessionID, item)
|
|
175
233
|
hide(output.message.id, text)
|
package/package.json
CHANGED