opencode-courier 0.2.3 → 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 +198 -193
- package/dist/index.d.ts +4 -8
- package/dist/index.js +66 -85
- package/dist/kernel/protocol.d.ts +20 -0
- package/dist/kernel/protocol.js +36 -0
- package/dist/kernel/tool.d.ts +5 -0
- package/dist/kernel/tool.js +5 -0
- package/dist/person/events.d.ts +23 -0
- package/dist/person/events.js +113 -0
- package/dist/person/form.d.ts +26 -0
- package/dist/person/form.js +64 -0
- package/dist/person/index.d.ts +17 -0
- package/dist/person/index.js +5 -0
- package/dist/{relay.d.ts → person/permission.d.ts} +29 -25
- package/dist/{relay.js → person/permission.js} +11 -9
- package/dist/person/ports.d.ts +58 -0
- package/dist/person/ports.js +2 -0
- package/dist/{question → person/question}/answer.d.ts +33 -4
- package/dist/{question → person/question}/answer.js +49 -17
- package/dist/person/question/index.d.ts +7 -0
- package/dist/{question → person/question}/index.js +3 -3
- package/dist/person/question/lifecycle.d.ts +43 -0
- package/dist/{question → person/question}/lifecycle.js +10 -19
- package/dist/{question → person/question}/pure.d.ts +4 -6
- package/dist/{question → person/question}/pure.js +3 -4
- package/dist/{question → person/question}/relay.d.ts +1 -1
- package/dist/{question → person/question}/relay.js +27 -35
- package/dist/{question → person/question}/runtime.d.ts +2 -3
- package/dist/{question → person/question}/runtime.js +2 -3
- package/dist/{question → person/question}/shared.d.ts +13 -3
- package/dist/person/question/shared.js +52 -0
- package/dist/person/question/types.d.ts +121 -0
- package/dist/person/question/types.js +1 -0
- package/dist/person/rpc-handlers.d.ts +160 -0
- package/dist/person/rpc-handlers.js +62 -0
- package/dist/person/rpc.d.ts +227 -0
- package/dist/person/rpc.js +70 -0
- package/dist/person/text.d.ts +68 -0
- package/dist/{notices.js → person/text.js} +53 -190
- package/dist/person/tools.d.ts +20 -0
- package/dist/person/tools.js +55 -0
- package/dist/person/tui.d.ts +46 -0
- package/dist/platform/bounded.d.ts +12 -0
- package/dist/{bounded.js → platform/bounded.js} +6 -4
- package/dist/platform/events.d.ts +34 -0
- package/dist/platform/events.js +121 -0
- package/dist/platform/hub.d.ts +195 -0
- package/dist/{hub.js → platform/hub.js} +78 -119
- package/dist/platform/json.d.ts +8 -0
- package/dist/platform/json.js +15 -0
- package/dist/platform/version.d.ts +16 -0
- package/dist/{version.js → platform/version.js} +5 -11
- package/dist/rpc.d.ts +2 -0
- package/dist/rpc.js +3 -0
- package/dist/scheduling/index.d.ts +6 -0
- package/dist/scheduling/index.js +5 -0
- package/dist/{later.d.ts → scheduling/later.d.ts} +13 -9
- package/dist/{later.js → scheduling/later.js} +18 -9
- package/dist/scheduling/text.d.ts +6 -0
- package/dist/scheduling/text.js +10 -0
- package/dist/scheduling/tools.d.ts +14 -0
- package/dist/scheduling/tools.js +48 -0
- package/dist/skill.d.ts +17 -0
- package/dist/skill.js +62 -0
- package/dist/{cleanup.d.ts → tree/cleanup.d.ts} +5 -26
- package/dist/{cleanup.js → tree/cleanup.js} +20 -15
- package/dist/tree/context.d.ts +15 -0
- package/dist/tree/context.js +34 -0
- package/dist/tree/domain.d.ts +101 -0
- package/dist/tree/domain.js +68 -0
- package/dist/tree/events.d.ts +71 -0
- package/dist/tree/events.js +204 -0
- package/dist/tree/forget.d.ts +24 -0
- package/dist/tree/forget.js +59 -0
- package/dist/tree/group.d.ts +82 -0
- package/dist/tree/group.js +203 -0
- package/dist/tree/index.d.ts +10 -0
- package/dist/tree/index.js +11 -0
- package/dist/tree/limits.d.ts +10 -0
- package/dist/tree/limits.js +60 -0
- package/dist/tree/panel.d.ts +13 -0
- package/dist/tree/panel.js +75 -0
- package/dist/tree/ports.d.ts +276 -0
- package/dist/tree/ports.js +1 -0
- package/dist/tree/report.d.ts +23 -0
- package/dist/tree/report.js +33 -0
- package/dist/tree/roster.d.ts +27 -0
- package/dist/tree/roster.js +62 -0
- package/dist/tree/rpc-handlers.d.ts +201 -0
- package/dist/tree/rpc-handlers.js +20 -0
- package/dist/tree/rpc.d.ts +344 -0
- package/dist/tree/rpc.js +83 -0
- package/dist/tree/send.d.ts +14 -0
- package/dist/tree/send.js +99 -0
- package/dist/tree/spawn.d.ts +21 -0
- package/dist/tree/spawn.js +173 -0
- package/dist/tree/status.d.ts +128 -0
- package/dist/tree/status.js +62 -0
- package/dist/tree/stop.d.ts +33 -0
- package/dist/tree/stop.js +116 -0
- package/dist/tree/store.d.ts +24 -0
- package/dist/tree/store.js +264 -0
- package/dist/tree/subtree.d.ts +101 -0
- package/dist/tree/subtree.js +60 -0
- package/dist/tree/summary.d.ts +7 -0
- package/dist/tree/summary.js +35 -0
- package/dist/tree/text.d.ts +77 -0
- package/dist/tree/text.js +287 -0
- package/dist/tree/tools.d.ts +54 -0
- package/dist/tree/tools.js +167 -0
- package/dist/tree/tui.d.ts +53 -0
- package/dist/tui.d.ts +3 -0
- package/dist/tui.js +1219 -0
- package/dist/webhook/index.d.ts +5 -0
- package/dist/webhook/index.js +3 -0
- package/dist/{webhook.d.ts → webhook/receiver.d.ts} +25 -36
- package/dist/{webhook.js → webhook/receiver.js} +66 -67
- package/dist/webhook/subscriptions.d.ts +29 -0
- package/dist/webhook/subscriptions.js +52 -0
- package/dist/webhook/text.d.ts +23 -0
- package/dist/webhook/text.js +82 -0
- package/dist/webhook/tools.d.ts +19 -0
- package/dist/webhook/tools.js +46 -0
- package/package.json +18 -7
- package/dist/bounded.d.ts +0 -10
- package/dist/courier.d.ts +0 -95
- package/dist/courier.js +0 -131
- package/dist/hub.d.ts +0 -325
- package/dist/json.d.ts +0 -4
- package/dist/json.js +0 -4
- package/dist/notices.d.ts +0 -246
- package/dist/question/index.d.ts +0 -6
- package/dist/question/lifecycle.d.ts +0 -52
- package/dist/question/shared.js +0 -24
- package/dist/roster.d.ts +0 -81
- package/dist/roster.js +0 -175
- package/dist/tools.d.ts +0 -74
- package/dist/tools.js +0 -228
- package/dist/version.d.ts +0 -22
- package/dist/watch.d.ts +0 -61
- package/dist/watch.js +0 -289
- /package/dist/{storage.d.ts → platform/storage.d.ts} +0 -0
- /package/dist/{storage.js → platform/storage.js} +0 -0
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ prompt](#demos)).
|
|
|
22
22
|
## Quickstart
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
npm install -g @opencode/cli@2.0.
|
|
25
|
+
npm install -g @opencode/cli@2.0.28 # OpenCode V2, at the version courier is tested on
|
|
26
26
|
opencode plugin add opencode-courier # by name, so OpenCode offers new releases
|
|
27
27
|
opencode # in any folder
|
|
28
28
|
```
|
|
@@ -39,106 +39,97 @@ If OpenCode asks to allow a child's command, the parent passes the question to y
|
|
|
39
39
|
sometimes stall on a request; if nothing moves for a few minutes, send the prompt again. Next:
|
|
40
40
|
[Using it](#using-it).
|
|
41
41
|
|
|
42
|
+
|
|
42
43
|
## OpenCode V2 native
|
|
43
44
|
|
|
44
45
|
Courier is built on OpenCode V2's plugin API (`@opencode/plugin`), not the V1 one
|
|
45
46
|
(`@opencode-ai/plugin`), and needs OpenCode V2. Each release is pinned to one V2 version and tested
|
|
46
|
-
against it end to end; the current one is tested on **OpenCode 2.0.
|
|
47
|
+
against it end to end; the current one is tested on **OpenCode 2.0.28**. CI also runs the live
|
|
47
48
|
suite on the newest OpenCode release, so a host release that breaks the plugin shows up there
|
|
48
49
|
first. Older releases and their versions: [Supported OpenCode version](#supported-opencode-version).
|
|
49
50
|
|
|
50
|
-
> **Status: early.** Passes an end-to-end test inside a live OpenCode V2 server driven by a
|
|
51
|
-
> scripted stand-in model, and a smoke test with real (free) models: see
|
|
52
|
-
> [Development](#development).
|
|
53
|
-
|
|
54
51
|
## Courier or the built-in subagent tool?
|
|
55
52
|
|
|
56
53
|
OpenCode V2 has its own [`subagent` tool](https://opencode.ai/v2/docs/agents): it starts an agent
|
|
57
54
|
with fresh context in a child session, and the parent either waits for its final answer or, with
|
|
58
|
-
`background: true`, carries on and is notified when the child finishes
|
|
59
|
-
`sessionID` back
|
|
60
|
-
|
|
61
|
-
nothing from you on the way.
|
|
62
|
-
|
|
63
|
-
Use courier when the work needs more than one result back. On top of what the built-in tool does,
|
|
64
|
-
it adds:
|
|
55
|
+
`background: true`, carries on and is notified when the child finishes; passing the child's
|
|
56
|
+
`sessionID` back prompts it again. Use it when one result back is all you need: a search, a review,
|
|
57
|
+
a self-contained task that needs nothing from you on the way. Use courier when the work needs more:
|
|
65
58
|
|
|
66
|
-
- **Messages from the child, mid-run:**
|
|
67
|
-
is its final answer. With courier a child reports progress, or asks its parent something,
|
|
59
|
+
- **Messages from the child, mid-run:** a child reports progress, or asks its parent something,
|
|
68
60
|
whenever it likes with `courier_send`, and any session can message any other.
|
|
69
|
-
- **Waking an idle session from anywhere:** a message from a child, a schedule or
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
reminders.
|
|
73
|
-
- **Webhooks:** GitHub reviews, comments and CI runs, or any signed POST, wake the
|
|
74
|
-
[subscribed](#webhooks) session.
|
|
61
|
+
- **Waking an idle session from anywhere:** a message from a child, a schedule (`courier_later`) or
|
|
62
|
+
a [webhook](#webhooks) (GitHub reviews, comments and CI runs, or any signed POST) starts a new turn
|
|
63
|
+
in a session that has ended its own.
|
|
75
64
|
- **Worktree isolation:** `isolate: true` gives a child its own git worktree, so children can edit
|
|
76
|
-
files in parallel
|
|
65
|
+
files in parallel; each commits on `courier/<its session ID>` and reports that branch for its
|
|
66
|
+
parent to merge ([nested isolation](docs/reference.md#nested-isolation)).
|
|
77
67
|
- **Relaying to you:** a child's permission requests and questions reach you through the parent,
|
|
78
68
|
and your answers go back; a form only you can answer is pointed out to you.
|
|
79
69
|
|
|
80
70
|
## How it works
|
|
81
71
|
|
|
82
72
|
A parent session calls `courier_spawn`, gets a session id back immediately and ends its turn. The
|
|
83
|
-
child works on its own and, when it is done or stuck, calls `courier_send` with the parent's id
|
|
84
|
-
That message lands in the parent's inbox and OpenCode starts a new turn
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
There is no polling anywhere: a message is admitted into the target session's inbox through
|
|
91
|
-
OpenCode's own API, which wakes the session, the same way OpenCode's background subagents report to
|
|
92
|
-
their parent. Delivery is `steer` by default (injected into the target's running turn, or starts
|
|
93
|
-
one if idle); `queue: true` waits until the current turn ends.
|
|
73
|
+
child works on its own and, when it is done or stuck, calls `courier_send` with the parent's id, a
|
|
74
|
+
status and what it produced. That message lands in the parent's inbox and OpenCode starts a new turn
|
|
75
|
+
for the parent if it is idle. There is no polling anywhere: a message is admitted into the target
|
|
76
|
+
session's inbox through OpenCode's own API, the way OpenCode's background subagents report to their
|
|
77
|
+
parent. Delivery is `steer` by default (injected into the target's running turn, or starts one if
|
|
78
|
+
idle); `queue: true` waits until the current turn ends.
|
|
94
79
|
|
|
95
80
|
## Demos
|
|
96
81
|
|
|
97
82
|
**The parent does not wait.** Prompt: *"Spawn two children: one runs `sleep 30` then lists the
|
|
98
83
|
exports of math.js, the other runs `sleep 45` then lists the exports of text.js. Don't wait for
|
|
99
84
|
them: meanwhile write a short README.md for this folder yourself, and add their reports to it when
|
|
100
|
-
they arrive."*
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
seconds in, while both children still sleep. Each report then starts a new turn on its own (the
|
|
104
|
-
`Message from ses_…` line is the child's message): the first after about 30 seconds, the second
|
|
105
|
-
after about 45, and the parent folds each into the README and cancels the check-in it had scheduled
|
|
106
|
-
for itself.
|
|
85
|
+
they arrive."* The recording at the top is this run: the parent writes its README and finishes its
|
|
86
|
+
turn 16 seconds in, while both children still sleep; each report (the `Message from ses_…` line)
|
|
87
|
+
then starts a new turn on its own, and the parent folds it into the README.
|
|
107
88
|
|
|
108
89
|
**A child's question reaches you.** Prompt: *"Spawn a child session to add a multiply function to
|
|
109
90
|
math.js. It must ask me first whether to name it multiply, times or product."*
|
|
110
91
|
|
|
111
92
|

|
|
112
93
|
|
|
113
|
-
|
|
114
|
-
|
|
94
|
+
The child's question opens in the parent's session as OpenCode's own question form, `times` is
|
|
95
|
+
picked, and the child adds `times` to math.js and reports. Both recordings are real runs with a free
|
|
96
|
+
model on [OpenCode Zen](https://opencode.ai/zen) (Muse Spark 1.3).
|
|
97
|
+
|
|
98
|
+
## Recursive orchestration
|
|
115
99
|
|
|
116
|
-
|
|
117
|
-
|
|
100
|
+
A session can split a goal across child sessions, each child deciding in turn whether to split its
|
|
101
|
+
part again, with the results checked and integrated at every level on the way back up. The plugin
|
|
102
|
+
ships the playbook as an OpenCode skill, `courier-orchestrate`, listed in every session: models are
|
|
103
|
+
told to load it at the start of an implementation task that may have independent parts, and
|
|
104
|
+
`/skills` in the TUI attaches it to your prompt. What it says: [the
|
|
105
|
+
reference](docs/reference.md#the-courier-orchestrate-skill).
|
|
118
106
|
|
|
119
107
|
## Install
|
|
120
108
|
|
|
121
109
|
### Supported OpenCode version
|
|
122
110
|
|
|
123
|
-
Requires OpenCode V2: the `opencode` command from `@opencode/cli` (also installed as `opencode2
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
named before a tool fails. More in [the reference](docs/reference.md#the-opencode-version).
|
|
111
|
+
Requires OpenCode V2: the `opencode` command from `@opencode/cli` (also installed as `opencode2`).
|
|
112
|
+
Each release is built and tested against exactly one OpenCode V2 version; a row lists the releases
|
|
113
|
+
pinned to its version. OpenCode never checks it:
|
|
114
|
+
the plugin installs and loads on any V2 host, and on a version other than the pinned one writes one
|
|
115
|
+
line to the server log naming both ([more](docs/reference.md#the-opencode-version)).
|
|
129
116
|
|
|
130
117
|
| opencode-courier | OpenCode V2 (`opencode` and `@opencode/plugin`) |
|
|
131
118
|
|---|---|
|
|
132
|
-
| 0.
|
|
119
|
+
| 0.4.0 | 2.0.28 |
|
|
120
|
+
| 0.2.3, 0.3.0 | 2.0.26 |
|
|
133
121
|
| 0.2.2 | 2.0.24 |
|
|
134
122
|
| 0.2.1 | 2.0.23 |
|
|
135
123
|
| 0.2.0 | 2.0.22 |
|
|
136
124
|
| 0.1.6 | 0.0.0-beta-19271 (the beta line: `opencode2` from `@opencode-ai/cli`, and `@opencode-ai/plugin`) |
|
|
137
125
|
|
|
126
|
+
The [terminal part](#in-the-terminal) relies on the host to provide its UI modules and is tested on
|
|
127
|
+
OpenCode 2.0.28 only; the server part works as before on older V2 hosts from 2.0.4 on.
|
|
128
|
+
|
|
138
129
|
Check yours with `opencode --version`, and install the matching CLI with:
|
|
139
130
|
|
|
140
131
|
```bash
|
|
141
|
-
npm install -g @opencode/cli@2.0.
|
|
132
|
+
npm install -g @opencode/cli@2.0.28
|
|
142
133
|
```
|
|
143
134
|
|
|
144
135
|
### The plugin
|
|
@@ -148,23 +139,14 @@ opencode plugin add opencode-courier
|
|
|
148
139
|
```
|
|
149
140
|
|
|
150
141
|
This installs the package from npm and adds `"opencode-courier"` to `plugins` in the global
|
|
151
|
-
configuration (`~/.config/opencode/opencode.json`)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
entry that carries a version to the name, and what to look at when an update still does not show:
|
|
157
|
-
[Updating the plugin](docs/reference.md#updating-the-plugin).
|
|
158
|
-
|
|
159
|
-
### From a local clone
|
|
160
|
-
|
|
161
|
-
```bash
|
|
162
|
-
git clone https://github.com/ivopogace/opencode-courier && cd opencode-courier
|
|
163
|
-
bun install && npm run build
|
|
164
|
-
```
|
|
142
|
+
configuration (`~/.config/opencode/opencode.json`); its options ([session trees](#session-trees),
|
|
143
|
+
[webhooks](#webhooks)) go in the object form of that entry. Install it by name, without a version:
|
|
144
|
+
OpenCode only checks plugins for updates when their entry is not an exact version, so
|
|
145
|
+
`opencode-courier@0.2.1` is never offered a newer release ([moving such an
|
|
146
|
+
entry](docs/reference.md#updating-the-plugin)).
|
|
165
147
|
|
|
166
|
-
|
|
167
|
-
|
|
148
|
+
From a local clone, `bun install && npm run build`, then list its `dist` **directory** in
|
|
149
|
+
`opencode.json` (V2 uses `plugins`, plural; a path to a file is ignored with a warning):
|
|
168
150
|
|
|
169
151
|
```jsonc
|
|
170
152
|
{
|
|
@@ -175,76 +157,150 @@ Then list it in `opencode.json` (V2 uses `plugins`, plural). A local plugin path
|
|
|
175
157
|
## Using it
|
|
176
158
|
|
|
177
159
|
1. Keep the background server running so sessions can be woken while you are away (`opencode
|
|
178
|
-
service start
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
160
|
+
service start`), and run one OpenCode server per data directory where you can: a second one, such
|
|
161
|
+
as `opencode serve` next to it, cannot pass your answer on to a child the other runs
|
|
162
|
+
([why](docs/reference.md#two-servers-on-one-data-directory)).
|
|
163
|
+
2. Give the agents that run children the permissions their work needs: a child that hits an approval
|
|
164
|
+
prompt waits until you answer it through its parent. Let them use the `question` tool if they
|
|
165
|
+
should be able to ask you. Choose a web search provider before children search the web (run one
|
|
166
|
+
web search in your own session, or use OpenCode's "Third-party search" setting), or the first
|
|
167
|
+
child to search shows that prompt in its own session, which only you can answer there.
|
|
168
|
+
3. Your own session's prompts are relayed nowhere, and each one stalls the whole tree until you
|
|
169
|
+
answer it. Before an unattended run, allow the orchestrating session's toolchain up front: one rule
|
|
170
|
+
on `bun run *` or `npm run *` (the skill has checks run through the project's scripts), `git`, and
|
|
171
|
+
whatever else its checks need.
|
|
172
|
+
4. Use `isolate: true` whenever children edit files in parallel. The worktree is made from the last
|
|
173
|
+
commit (from an isolated session, its HEAD), so an uncommitted `opencode.json` is not there: keep
|
|
174
|
+
providers and models in the global config, or commit the file. A worktree is kept until you
|
|
175
|
+
`courier_cleanup` it, which refuses to drop uncommitted changes or unbranched commits unless told to.
|
|
176
|
+
5. For a long-running child, schedule a `courier_later` check-in for yourself and `courier_cancel` it
|
|
177
|
+
when the child reports: it covers what the plugin cannot see, such as a server that stops while a
|
|
178
|
+
child runs.
|
|
194
179
|
|
|
195
180
|
## Tools
|
|
196
181
|
|
|
197
182
|
| Tool | Does |
|
|
198
183
|
|---|---|
|
|
199
|
-
| `courier_spawn` |
|
|
200
|
-
| `courier_send` |
|
|
201
|
-
| `courier_status` | One look at a session: outcome, idle time, last reply and
|
|
202
|
-
| `courier_children` | Lists the sessions this one (or a given
|
|
203
|
-
| `
|
|
204
|
-
| `
|
|
205
|
-
| `
|
|
206
|
-
| `
|
|
207
|
-
| `
|
|
208
|
-
| `
|
|
184
|
+
| `courier_spawn` | Starts a session on the parent's model with the task and a brief, and returns at once; `isolate: true` gives it its own git worktree, `group` holds its report with its siblings'. |
|
|
185
|
+
| `courier_send` | Messages a session, waking it if idle; from a child to its parent, a `status` and `artifacts` make it the child's [report](docs/reference.md#a-childs-report). |
|
|
186
|
+
| `courier_status` | One look at a session: outcome, idle time, last reply and what it waits on. For check-ins, not for waiting. |
|
|
187
|
+
| `courier_children` | Lists the sessions this one (or a given one) started, with their state, worktree and join group. |
|
|
188
|
+
| `courier_tree` | Lists the whole tree under a session, depth first, with each one's depth and report state. |
|
|
189
|
+
| `courier_stop` | Interrupts a session below you and everything under it, and cancels their scheduled messages; `cleanup: true` removes their worktrees too. |
|
|
190
|
+
| `courier_cleanup` | Removes an isolated child's worktree, unless it holds unsaved work, and forgets the child. |
|
|
191
|
+
| `courier_answer` | Passes your answer to a permission request or a question that a session under this one waits on. |
|
|
192
|
+
| `courier_later` | Schedules a message for a session (this one by default) in `delayMinutes` or `at` a time. |
|
|
193
|
+
| `courier_cancel` | Drops a message scheduled with `courier_later`. |
|
|
194
|
+
| `courier_subscribe` | Subscribes a session to webhook deliveries for a topic (`owner/repo`, `owner/repo#12` or a name). |
|
|
195
|
+
| `courier_unsubscribe` | Drops one topic, or all of a session's. |
|
|
196
|
+
|
|
197
|
+
Each tool's parameters: [the reference](docs/reference.md#the-tools).
|
|
209
198
|
|
|
210
199
|
## How children reach you
|
|
211
200
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
201
|
+
- **A child reports once, with a status**: `done`, `partial`, `blocked` (it needs a decision from
|
|
202
|
+
its parent, and says what) or `failed`, with its artifacts (branch, commits, files, checks) listed
|
|
203
|
+
in the message. A message without a status is progress, not the report.
|
|
204
|
+
[More](docs/reference.md#a-childs-report).
|
|
205
|
+
- **A join group wakes you once.** Start a batch of sessions with the same `group` name, and their
|
|
206
|
+
`done`, `partial` and `failed` reports are held until the last one is in and your turn has ended,
|
|
207
|
+
then delivered in one message. A `blocked` report, a failure, a question or a permission request
|
|
208
|
+
still reaches you at once. A member that will not report holds the others back: drop it with
|
|
209
|
+
`courier_cleanup`, and the group is released without it. [More](docs/reference.md#join-groups).
|
|
210
|
+
- **A child that fails, or ends its turn without reporting, is reported** to its parent by the
|
|
211
|
+
plugin, with the error or its last reply, waking it if idle; one that waits on a request, its own
|
|
212
|
+
children, a scheduled message or a webhook is not, nor is one that answers what you typed in its
|
|
213
|
+
session. [More](docs/reference.md#a-child-that-fails).
|
|
214
|
+
- **Permission requests and questions reach you.** A child's request or question is passed to the
|
|
215
|
+
session at the top of its tree, any number of levels down; that session asks you and passes your
|
|
216
|
+
answer back, or links its own question to the child's so your pick goes straight to it. Whichever
|
|
217
|
+
answer comes first counts, and the other is withdrawn.
|
|
223
218
|
[More](docs/reference.md#a-child-that-asks-for-permission).
|
|
224
|
-
- **
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
219
|
+
- **Some forms only you can answer**, such as OpenCode's web search asking for a provider: the top
|
|
220
|
+
session is told, and you answer in the child's session. [More](docs/reference.md#a-child-that-shows-a-form).
|
|
221
|
+
- **You can see and stop a whole subtree** with `courier_tree` and `courier_stop`, or in the
|
|
222
|
+
[terminal](#in-the-terminal). [More](docs/reference.md#seeing-and-stopping-a-subtree).
|
|
223
|
+
|
|
224
|
+
Children, held reports, scheduled messages and open questions survive a compaction or a restart;
|
|
225
|
+
entries are dropped after 14 days ([roster](docs/reference.md#roster)).
|
|
226
|
+
|
|
227
|
+
## Session trees
|
|
228
|
+
|
|
229
|
+
A spawned session may start sessions of its own, so the sessions form a tree, bounded by three
|
|
230
|
+
options in the plugin's `options`, in the global config:
|
|
231
|
+
|
|
232
|
+
```jsonc
|
|
233
|
+
{
|
|
234
|
+
"plugins": [{ "package": "opencode-courier", "options": { "maxDepth": 2, "maxChildren": 3 } }]
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
| Option | Default | |
|
|
239
|
+
|---|---|---|
|
|
240
|
+
| `maxDepth` | `3` | How deep the tree goes: your session is depth 0, the sessions it starts depth 1. A session at `maxDepth` cannot start sessions. |
|
|
241
|
+
| `maxChildren` | `5` | How many sessions one session started may have live at once. |
|
|
242
|
+
| `maxTotal` | `20` | How many spawned sessions may be live at once in one tree. |
|
|
243
|
+
|
|
244
|
+
`courier_spawn` refuses a spawn past a limit, naming it and saying what to do instead: do the work
|
|
245
|
+
itself, or wait for a child to report. A session counts while it is live: its turn runs, it owes
|
|
246
|
+
its parent a report (its task or a message reached it after it last reported, failed or was
|
|
247
|
+
interrupted), or a session below it is live. One that has reported, with nothing live below it,
|
|
248
|
+
does not. Each spawned session's brief says when to split its task and when to do it itself
|
|
249
|
+
([more](docs/reference.md#session-trees-and-their-limits)).
|
|
250
|
+
|
|
251
|
+
## In the terminal
|
|
252
|
+
|
|
253
|
+
The package ships a part for OpenCode's terminal UI, which the TUI loads from the server's plugin
|
|
254
|
+
list; the server half behaves the same whether or not a TUI is attached. In a session that started
|
|
255
|
+
sessions, the prompt's footer shows what the tree under it is doing: how many run, wait on you or
|
|
256
|
+
are idle, and for each [join group](docs/reference.md#join-groups) how many of its members have
|
|
257
|
+
reported:
|
|
258
|
+
|
|
259
|
+
```
|
|
260
|
+
courier 2 running · 1 waiting on you · group pair 1/2
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
`/courier tree` opens a panel beside the session with the tree under it, a row per session indented
|
|
264
|
+
by depth, with what it does, its report and its group's standing; a session that started others gets
|
|
265
|
+
a bar of the parts reported, a leaf how long it ran, its last tool call and its cost. `↑`/`↓` select,
|
|
266
|
+
`enter` opens the session, `s` stops it and everything under it after you confirm, as `courier_stop`
|
|
267
|
+
would, and `q` closes the panel; `/courier stop <session>` stops from the prompt.
|
|
268
|
+
|
|
269
|
+
`ctrl+x k` (the leader key, then `k`), `/asks`, the palette's "Courier: what waits on you" or a click
|
|
270
|
+
on "waiting on you" opens the asks inbox: every permission request, question and form waiting
|
|
271
|
+
anywhere in the tree, oldest first, with where it comes from, answered in a dialog with no model in
|
|
272
|
+
between; the session that started the tree hears once that you settled it. The TUI plays OpenCode's
|
|
273
|
+
sounds for a child's report, a group's release and a new ask ([more](docs/reference.md#in-the-terminal)).
|
|
274
|
+
|
|
275
|
+
## Native child sessions
|
|
276
|
+
|
|
277
|
+
Off by default. With `"nativeChildren": true` in the plugin's `options`, a child started without
|
|
278
|
+
`isolate` is also created with OpenCode's own `parentID`, so OpenCode treats it as the parent's
|
|
279
|
+
subagent: the TUI's view of the top session lists and answers the permission requests and forms of
|
|
280
|
+
every session under it, and the paired web, desktop or mobile app's request dock answers a
|
|
281
|
+
grandchild's ask from the top session, on a phone too. Isolated children are created as before, and
|
|
282
|
+
the courier's own tree, limits and relays work as without the option. What changes
|
|
283
|
+
([more](docs/reference.md#native-child-sessions)):
|
|
284
|
+
|
|
285
|
+
- **Deleting a session deletes its native children**, as OpenCode does with subagents.
|
|
286
|
+
- **A child does not take its parent's session rules**: courier passes it an empty ruleset, so it
|
|
287
|
+
may do what it could without the option.
|
|
288
|
+
- **Providers see a subagent**: the child's model requests carry its parent's id for cache routing
|
|
289
|
+
and two headers naming the parent; **on GitHub Copilot**, which meters such requests differently
|
|
290
|
+
from a person's, courier was not tested.
|
|
291
|
+
- **A child's asks show in the top session's view**, not in its own, and it leaves the TUI's session
|
|
292
|
+
lists. Its permission requests and questions are answered once, in OpenCode's own prompt there; the
|
|
293
|
+
top session's model is only told. `opencode run` and the TUI's auto-accept mode answer them as their
|
|
294
|
+
own subagents'.
|
|
295
|
+
- **Turn it on only with OpenCode's TUI or the paired app**, which show those prompts; with a bare
|
|
296
|
+
API client nobody prompts you, and only `courier_answer` is left.
|
|
243
297
|
|
|
244
298
|
## Webhooks
|
|
245
299
|
|
|
246
|
-
|
|
247
|
-
|
|
300
|
+
GitHub reviews, comments, pull requests and completed CI runs, or anything signed posted to
|
|
301
|
+
`/hook/<name>`, can wake a session. The receiver is off unless the plugin has a `webhook` option, in
|
|
302
|
+
the **global** config, since there is one receiver per OpenCode server (`"webhook": true` takes
|
|
303
|
+
every default):
|
|
248
304
|
|
|
249
305
|
```jsonc
|
|
250
306
|
{
|
|
@@ -257,77 +313,26 @@ The receiver is off unless the plugin has a `webhook` option. Put it in the **gl
|
|
|
257
313
|
}
|
|
258
314
|
```
|
|
259
315
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
The secret is never read from `opencode.json` itself (a `secret` key is refused), so the config can
|
|
273
|
-
be committed. Make one with `openssl rand -hex 32 > ~/.config/opencode/courier-webhook-secret` and
|
|
274
|
-
`chmod 600` it. A file is the safer choice with `opencode service start`, whose environment may not
|
|
275
|
-
be your shell's. Without a usable secret the receiver does not start, and the server log says why.
|
|
276
|
-
|
|
277
|
-
On GitHub, add a webhook to the repository (Settings → Webhooks) with content type
|
|
278
|
-
`application/json`, the same secret, and the events you want (pull request reviews, review comments,
|
|
279
|
-
issue comments, pull requests, check suites or workflow runs). GitHub must reach the receiver, and
|
|
280
|
-
by default it only listens on `127.0.0.1`: forward a public URL to it with a tunnel you trust
|
|
281
|
-
(`cloudflared tunnel --url http://127.0.0.1:4097`, `ngrok http 4097`, or `smee --url
|
|
282
|
-
https://smee.io/<channel> --target http://127.0.0.1:4097/github`, which needs no inbound port at
|
|
283
|
-
all) and use `<public URL>/github` as the payload URL. Whatever you expose, only signed deliveries
|
|
284
|
-
are acted on.
|
|
285
|
-
|
|
286
|
-
The receiver starts when OpenCode loads the plugin, which after a server start happens the first
|
|
287
|
-
time a project is used. Until then deliveries fail; GitHub does not retry them on its own, but lists
|
|
288
|
-
them under Recent Deliveries with a Redeliver button.
|
|
289
|
-
|
|
290
|
-
Once it runs, `courier_subscribe` ties a session to a topic and every matching delivery arrives as a
|
|
291
|
-
message, queued behind any running turn and waking the session if idle:
|
|
292
|
-
|
|
293
|
-
- `POST /github` takes GitHub webhook deliveries: reviews, comments, pull requests and issues being
|
|
294
|
-
opened, closed or merged, and completed CI runs reach the sessions subscribed to `owner/repo#N`
|
|
295
|
-
and `owner/repo`; other events with a repository reach `owner/repo`.
|
|
296
|
-
- `POST /hook/<name>` takes anything else, for sessions subscribed to `<name>`: a JSON body's
|
|
297
|
-
`text`, `summary` or `message` field, otherwise the body itself.
|
|
298
|
-
|
|
299
|
-
Every delivery must be signed (`X-Hub-Signature-256`, HMAC-SHA256 under the shared secret), replays
|
|
300
|
-
are refused, and what a session sees is a short summary marked as outside text. Which events wake
|
|
301
|
-
whom, the signing scheme for `/hook/<name>`, limits and status codes: [the
|
|
316
|
+
1. Make the secret with `openssl rand -hex 32 > ~/.config/opencode/courier-webhook-secret` and
|
|
317
|
+
`chmod 600` it. It is never read from `opencode.json` (a `secret` key is refused), so the config
|
|
318
|
+
can be committed; without a usable secret the receiver does not start, and the server log says why.
|
|
319
|
+
2. On GitHub, add a webhook to the repository (Settings → Webhooks) with content type
|
|
320
|
+
`application/json`, the same secret, the events you want, and `<public URL>/github` as the payload
|
|
321
|
+
URL, forwarded to `127.0.0.1:4097` by a tunnel you trust (`smee`, `cloudflared`, `ngrok`). Only
|
|
322
|
+
signed deliveries are acted on.
|
|
323
|
+
3. `courier_subscribe` a session to `owner/repo`, `owner/repo#N` or `<name>`: each matching delivery
|
|
324
|
+
arrives as a short summary marked as outside text, queued behind any running turn and waking the
|
|
325
|
+
session if idle.
|
|
326
|
+
|
|
327
|
+
The options, which events wake whom, the tunnels, the signing scheme and status codes: [the
|
|
302
328
|
reference](docs/reference.md#webhooks).
|
|
303
329
|
|
|
304
330
|
## Development
|
|
305
331
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
npm run build # emits dist/
|
|
311
|
-
OPENCODE_BIN=$(which opencode) npm run test:e2e # live test, about two minutes, no API key
|
|
312
|
-
OPENCODE_BIN=$(which opencode) e2e/real-model.sh # smoke test with a real model, not in CI
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
The live test (`e2e/run.sh`) starts a real OpenCode V2 server in a throwaway project and home
|
|
316
|
-
directory, with this plugin loaded and a scripted OpenAI-compatible stand-in model, and walks every
|
|
317
|
-
tool through the behaviour above. It needs node, npm, bun, git, curl, jq and openssl. New behaviour
|
|
318
|
-
gets a scenario there.
|
|
319
|
-
|
|
320
|
-
The smoke test (`e2e/real-model.sh`) runs the same server with a real model (by default a free one
|
|
321
|
-
on [OpenCode Zen](https://opencode.ai/zen), no key needed) and checks that the parent spawns instead
|
|
322
|
-
of doing the work, ends its turn instead of polling, and is woken by each report;
|
|
323
|
-
`COURIER_SCENARIO=permission` and `COURIER_SCENARIO=question` exercise the two relays. Which models
|
|
324
|
-
pass and what was tuned for them: [docs/real-model.md](docs/real-model.md).
|
|
325
|
-
|
|
326
|
-
CI runs both suites on every push to `main` and every pull request, with the OpenCode CLI at the
|
|
327
|
-
pinned version, and the live suite once more on the newest OpenCode release, where a failure warns
|
|
328
|
-
rather than breaks the build. SonarCloud scans `src/` on `main` and on pull requests. Releases start
|
|
329
|
-
from GitHub and end with a maintainer's 2FA approval on npm: [docs/releasing.md](docs/releasing.md).
|
|
330
|
-
What the plugin API does that the plugin had to work around: [docs/plugin-api-notes.md](docs/plugin-api-notes.md).
|
|
332
|
+
Commands, the suites and CI: [docs/maintainers/development.md](docs/maintainers/development.md). How
|
|
333
|
+
the code is laid out: [architecture.md](docs/maintainers/architecture.md); what the plugin API does
|
|
334
|
+
that courier works around: [plugin-api-notes.md](docs/maintainers/plugin-api-notes.md); releases:
|
|
335
|
+
[releasing.md](docs/maintainers/releasing.md).
|
|
331
336
|
|
|
332
337
|
## Contributing
|
|
333
338
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import { Plugin } from "@opencode/plugin";
|
|
2
2
|
import { Plugin as EffectPlugin } from "@opencode/plugin/effect";
|
|
3
3
|
/**
|
|
4
|
-
* The courier tools and webhook receiver, as a promise plugin
|
|
5
|
-
*
|
|
6
|
-
* the question relay, the Effect half of the same instance, finds its ports.
|
|
4
|
+
* The courier tools and webhook receiver, as a promise plugin joining the hub that runs the scheduler
|
|
5
|
+
* and the watcher; its member carries the location by which the question relay finds its ports.
|
|
7
6
|
*/
|
|
8
7
|
export declare const courier: () => Plugin.Plugin;
|
|
9
8
|
/**
|
|
10
|
-
* The plugin OpenCode loads
|
|
11
|
-
*
|
|
12
|
-
* interrupted, so a child's question could not be withdrawn once its parent answered. Everything
|
|
13
|
-
* else is the promise plugin above, run through the plugin package's own adapter, as OpenCode runs
|
|
14
|
-
* a promise plugin.
|
|
9
|
+
* The plugin OpenCode loads: an Effect plugin, since the question relay wraps OpenCode's question tool
|
|
10
|
+
* and only the Effect API makes its call interruptible; the rest is the promise plugin, adapted.
|
|
15
11
|
*/
|
|
16
12
|
declare const _default: EffectPlugin.Plugin<import("effect/Scope").Scope>;
|
|
17
13
|
export default _default;
|