opencode-queue 0.1.0 → 0.2.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 +6 -3
- package/index.ts +76 -38
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,9 +8,10 @@ This plugin adds a `/queue ...` prefix that keeps the current run focused instea
|
|
|
8
8
|
|
|
9
9
|
- Queues normal prompts entered while a session is busy
|
|
10
10
|
- Queues slash commands like `/queue /review` and `/queue /commit`
|
|
11
|
-
-
|
|
12
|
-
- Filters queued placeholders out of model input so they do not interrupt the current run
|
|
11
|
+
- Hides queued placeholders from both the UI transcript and the running agent
|
|
13
12
|
- Replays queued input in order once the session becomes idle
|
|
13
|
+
- Replays queued commands as a visible `/command` message before executing them
|
|
14
|
+
- Shows the current queue with `/queue list`
|
|
14
15
|
|
|
15
16
|
## Install
|
|
16
17
|
|
|
@@ -34,15 +35,17 @@ While the agent is busy:
|
|
|
34
35
|
/queue continue after the current task finishes
|
|
35
36
|
/queue /review
|
|
36
37
|
/queue /commit
|
|
38
|
+
/queue list
|
|
37
39
|
```
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
Queued items stay hidden while the current run is still working, then replay automatically when the session becomes idle.
|
|
40
42
|
|
|
41
43
|
## Notes
|
|
42
44
|
|
|
43
45
|
- This is a `/queue` plugin, not a keyboard shortcut plugin. OpenCode plugins cannot currently register custom TUI keybindings.
|
|
44
46
|
- Idle `/queue some text` is treated like a normal prompt with the `/queue` prefix removed.
|
|
45
47
|
- Idle `/queue /command` is left alone and is not intercepted.
|
|
48
|
+
- `/queue list` shows the in-memory queue for the current session.
|
|
46
49
|
|
|
47
50
|
## License
|
|
48
51
|
|
package/index.ts
CHANGED
|
@@ -13,7 +13,7 @@ const COMMAND = /^\/(\S+)(?:\s+([\s\S]*))?$/
|
|
|
13
13
|
|
|
14
14
|
type InputPart = TextPartInput | FilePartInput | AgentPartInput | SubtaskPartInput
|
|
15
15
|
type Info = { agent: string; model: { providerID: string; modelID: string } }
|
|
16
|
-
type Item = { info: Info;
|
|
16
|
+
type Item = { info: Info; text: string; parts?: InputPart[]; command?: string; arguments?: string }
|
|
17
17
|
|
|
18
18
|
const label = (body: string, files: number) => {
|
|
19
19
|
const text = body.trim() || `${files} attachment${files === 1 ? "" : "s"}`
|
|
@@ -25,14 +25,72 @@ const parseCommand = (body: string) => {
|
|
|
25
25
|
return match ? { command: match[1], arguments: match[2] ?? "" } : undefined
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
const QueuePlugin: Plugin = async ({ client }) => {
|
|
28
|
+
export const QueuePlugin: Plugin = async ({ client }) => {
|
|
29
29
|
const queue = new Map<string, Item[]>()
|
|
30
30
|
const hidden = new Set<string>()
|
|
31
31
|
const busy = new Set<string>()
|
|
32
32
|
const flushing = new Set<string>()
|
|
33
33
|
|
|
34
|
-
const toast = (message: string, variant: "info" | "error") =>
|
|
35
|
-
client.tui.showToast({ body: { message, variant, duration
|
|
34
|
+
const toast = (message: string, variant: "info" | "error", duration = 2500) =>
|
|
35
|
+
client.tui.showToast({ body: { message, variant, duration } }).catch(() => undefined)
|
|
36
|
+
|
|
37
|
+
const hide = (id: string, text: TextPart) => {
|
|
38
|
+
hidden.add(id)
|
|
39
|
+
text.text = ""
|
|
40
|
+
text.synthetic = true
|
|
41
|
+
text.ignored = true
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const enqueue = (sid: string, item: Item) => {
|
|
45
|
+
const items = queue.get(sid)
|
|
46
|
+
if (items) items.push(item)
|
|
47
|
+
else queue.set(sid, [item])
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const summary = (sid: string) => {
|
|
51
|
+
const items = queue.get(sid) ?? []
|
|
52
|
+
if (!items.length) return "Queue is empty"
|
|
53
|
+
return items.map((item, i) => `${i + 1}. ${item.text}`).join("\n")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const replay = async (sid: string, item: Item) => {
|
|
57
|
+
if (!item.command || item.arguments === undefined) {
|
|
58
|
+
if (!item.parts?.length) {
|
|
59
|
+
console.warn("QueuePlugin skipped queued item without replayable content")
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await client.session.prompt({
|
|
64
|
+
path: { id: sid },
|
|
65
|
+
body: {
|
|
66
|
+
agent: item.info.agent,
|
|
67
|
+
model: item.info.model,
|
|
68
|
+
parts: item.parts.map((part) => ({ ...part, id: undefined })),
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
await client.session.prompt({
|
|
75
|
+
path: { id: sid },
|
|
76
|
+
body: {
|
|
77
|
+
agent: item.info.agent,
|
|
78
|
+
model: item.info.model,
|
|
79
|
+
noReply: true,
|
|
80
|
+
parts: [{ type: "text", text: item.text }],
|
|
81
|
+
},
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
await client.session.command({
|
|
85
|
+
path: { id: sid },
|
|
86
|
+
body: {
|
|
87
|
+
agent: item.info.agent,
|
|
88
|
+
model: `${item.info.model.providerID}/${item.info.model.modelID}`,
|
|
89
|
+
command: item.command,
|
|
90
|
+
arguments: item.arguments,
|
|
91
|
+
},
|
|
92
|
+
})
|
|
93
|
+
}
|
|
36
94
|
|
|
37
95
|
const flush = async (sid: string) => {
|
|
38
96
|
if (flushing.has(sid)) return
|
|
@@ -46,28 +104,7 @@ const QueuePlugin: Plugin = async ({ client }) => {
|
|
|
46
104
|
while (list.length) {
|
|
47
105
|
const item = list.shift()
|
|
48
106
|
if (!item) break
|
|
49
|
-
|
|
50
|
-
if ("command" in item) {
|
|
51
|
-
await client.session.command({
|
|
52
|
-
path: { id: sid },
|
|
53
|
-
body: {
|
|
54
|
-
agent: item.info.agent,
|
|
55
|
-
model: `${item.info.model.providerID}/${item.info.model.modelID}`,
|
|
56
|
-
command: item.command,
|
|
57
|
-
arguments: item.arguments,
|
|
58
|
-
},
|
|
59
|
-
})
|
|
60
|
-
continue
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
await client.session.prompt({
|
|
64
|
-
path: { id: sid },
|
|
65
|
-
body: {
|
|
66
|
-
agent: item.info.agent,
|
|
67
|
-
model: item.info.model,
|
|
68
|
-
parts: item.parts.map((part) => ({ ...part, id: undefined })),
|
|
69
|
-
},
|
|
70
|
-
})
|
|
107
|
+
await replay(sid, item)
|
|
71
108
|
}
|
|
72
109
|
} catch (error) {
|
|
73
110
|
const message = error instanceof Error ? error.message : String(error)
|
|
@@ -101,10 +138,16 @@ const QueuePlugin: Plugin = async ({ client }) => {
|
|
|
101
138
|
if (body === undefined) return
|
|
102
139
|
|
|
103
140
|
const files = output.parts.filter((part): part is FilePart => part.type === "file")
|
|
104
|
-
|
|
141
|
+
const trimmed = body.trim()
|
|
142
|
+
|
|
143
|
+
if ((!trimmed || trimmed === "list") && !files.length) {
|
|
144
|
+
hide(output.message.id, text)
|
|
145
|
+
await toast(summary(sessionID), "info", 5000)
|
|
146
|
+
return
|
|
147
|
+
}
|
|
105
148
|
|
|
106
149
|
if (!busy.has(sessionID)) {
|
|
107
|
-
if (
|
|
150
|
+
if (trimmed.startsWith("/")) return
|
|
108
151
|
text.text = body
|
|
109
152
|
return
|
|
110
153
|
}
|
|
@@ -126,15 +169,11 @@ const QueuePlugin: Plugin = async ({ client }) => {
|
|
|
126
169
|
|
|
127
170
|
const info = { agent: output.message.agent, model: { ...output.message.model } }
|
|
128
171
|
const command = parseCommand(body)
|
|
129
|
-
const item = command ? { info, ...command } : { info, parts }
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const note = label(body, files.length)
|
|
135
|
-
hidden.add(output.message.id)
|
|
136
|
-
text.text = `[queued] ${note}`
|
|
137
|
-
await toast(`Queued: ${note}`, "info")
|
|
172
|
+
const item = command ? { info, text: trimmed, ...command } : { info, text: label(body, files.length), parts }
|
|
173
|
+
|
|
174
|
+
enqueue(sessionID, item)
|
|
175
|
+
hide(output.message.id, text)
|
|
176
|
+
await toast(`Queued: ${item.text}`, "info")
|
|
138
177
|
},
|
|
139
178
|
"experimental.chat.messages.transform": async (_, output) => {
|
|
140
179
|
output.messages = output.messages.filter((msg) => !hidden.has(msg.info.id))
|
|
@@ -142,5 +181,4 @@ const QueuePlugin: Plugin = async ({ client }) => {
|
|
|
142
181
|
}
|
|
143
182
|
}
|
|
144
183
|
|
|
145
|
-
export { QueuePlugin }
|
|
146
184
|
export default QueuePlugin
|
package/package.json
CHANGED