@itsaplan/runner 0.5.3 → 0.7.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.
Files changed (3) hide show
  1. package/README.md +150 -48
  2. package/dist/cli.js +1039 -741
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -11,6 +11,9 @@ This package runs such agents on your own machine — one, or several at once.
11
11
  Pi, and Oh My Pi (`omp`). Each preset sets the unattended flags and the session resume.
12
12
  - Runs your own `command` instead, if you prefer. The command takes the task on stdin.
13
13
  - Answers the agent's chat. Sends the text and the tool calls while the CLI prints them.
14
+ - Sends the messages and the tool calls of a queued run to the issue while the run works.
15
+ - Receives the tasks on a port instead of polling, for an agent whose Connection is
16
+ Endpoint. See [Receiving tasks on a port](#receiving-tasks-on-a-port).
14
17
 
15
18
  ## Quick start
16
19
 
@@ -105,9 +108,8 @@ on stdin. The runner keeps no session for it, because it does not know how to re
105
108
 
106
109
  ## Several agents on one runner
107
110
 
108
- One key is one agent. A runner set up the way you want it can serve several of them at
109
- once: it polls for each key on its own, and every agent uses the coding agent, the working
110
- directory and the arguments the file already gives.
111
+ One key is one agent. One runner can serve several agents. It polls for each key
112
+ separately, and every agent uses the `agent`, `cwd` and `args` from the file.
111
113
 
112
114
  ```json
113
115
  {
@@ -118,13 +120,13 @@ directory and the arguments the file already gives.
118
120
  }
119
121
  ```
120
122
 
121
- Nothing else changes: the runner gives each command its own `ITSAPLAN_API_KEY`, so the one
122
- MCP config in that folder works for all of them.
123
+ The runner gives each command its own `ITSAPLAN_API_KEY`, so one MCP config in that
124
+ folder works for all agents.
123
125
 
124
- Use `agents` instead of `apiKeys` where an agent needs something of its own. A field an
125
- entry sets replaces the shared one, and it inherits every field it leaves out, except its
126
- `apiKey`, which every entry sets itself. `name` is what the runner's log lines call that
127
- agent; without it they number the agents `#1`, `#2` in the order of the file.
126
+ Use `agents` instead of `apiKeys` when an agent needs its own settings. Each entry sets
127
+ its own `apiKey`. The other fields that an entry sets replace the shared ones, and the
128
+ entry inherits the rest. `name` is the agent's label in the log. Without it, the log
129
+ numbers the agents `#1`, `#2` in the order of the file.
128
130
 
129
131
  ```json
130
132
  {
@@ -142,12 +144,91 @@ agent; without it they number the agents `#1`, `#2` in the order of the file.
142
144
  }
143
145
  ```
144
146
 
145
- `concurrency` counts per agent and per feed, so a runner with three agents at
146
- `concurrency: 2` can have six queued runs in flight and six chat answers alongside them,
147
- each one a coding agent CLI of its own. The environment variables and the command-line
148
- options describe every agent at once; an entry has priority over them.
147
+ `concurrency` counts per agent and per feed. A runner with three agents at
148
+ `concurrency: 2` can run six queued runs and six chat answers at once, each in its own
149
+ CLI process.
149
150
 
150
- An agent whose key the instance refuses stops on its own, and the others keep working.
151
+ If the instance refuses the key of an agent, that agent stops. The other agents continue.
152
+
153
+ ## Receiving tasks on a port
154
+
155
+ By default the runner polls the instance for tasks. With `listen`, the instance sends each
156
+ task to the runner when the task is created. The runner then does not poll.
157
+
158
+ Use `listen` for a runner on a server that the instance can reach. On a laptop behind a
159
+ router, use polling. Polling needs no setup. The chat is as fast with polling, because a
160
+ chat claim waits on the server until a message comes.
161
+
162
+ **1.** In the agent's settings, open Connection and select Endpoint. Enter the public
163
+ HTTPS address of the runner's port (see [Reaching the port](#reaching-the-port)), and
164
+ save. Copy the signing secret.
165
+
166
+ **2.** Add `listen` and `signingSecret` to `itsaplan-runner.json`:
167
+
168
+ ```json
169
+ {
170
+ "url": "https://plan.example.com",
171
+ "apiKey": "the agent's key",
172
+ "agent": "claude",
173
+ "listen": 4000,
174
+ "signingSecret": "whsec_…"
175
+ }
176
+ ```
177
+
178
+ **3.** Start the runner. It logs `listening on port 4000`. In the agent's settings, click
179
+ Send test. The result must be `200`.
180
+
181
+ The runner answers each task immediately. Then it runs the task as it runs a polled task,
182
+ with the same command, heartbeats and result. The rules for a request:
183
+
184
+ - The runner accepts only a `POST` request signed with the secret of one of its agents.
185
+ - It refuses a request whose signature time is more than five minutes from its clock.
186
+ - If the instance sends a task again while the task runs, the runner does not start it a
187
+ second time.
188
+
189
+ Several agents can use one port. The runner identifies the agent by the secret, so each
190
+ agent needs its own `signingSecret`. Put the agents in `agents`:
191
+
192
+ ```json
193
+ {
194
+ "url": "https://plan.example.com",
195
+ "agent": "claude",
196
+ "listen": 4000,
197
+ "agents": [
198
+ { "apiKey": "the first agent's key", "signingSecret": "whsec_first…" },
199
+ { "apiKey": "the second agent's key", "signingSecret": "whsec_second…" }
200
+ ]
201
+ }
202
+ ```
203
+
204
+ In the same file, the runner polls for each agent that has no `listen`.
205
+
206
+ ### Reaching the port
207
+
208
+ The runner serves plain HTTP. In production, the instance requires HTTPS. Put a tunnel or a
209
+ reverse proxy in front of the port:
210
+
211
+ - **Reverse proxy** on a server with a domain. Caddy gets the certificate automatically:
212
+
213
+ ```
214
+ runner.example.com {
215
+ reverse_proxy localhost:4000
216
+ }
217
+ ```
218
+
219
+ - **Cloudflare Tunnel.** `cloudflared tunnel --url http://localhost:4000` prints a
220
+ `trycloudflare.com` address. The address changes each time the tunnel starts, and then
221
+ you must change the agent's URL too. A named tunnel on your own domain keeps the same
222
+ address.
223
+ - **ngrok.** `ngrok http 4000`. A static domain in your ngrok account keeps the same
224
+ address.
225
+ - **Tailscale Funnel.** `tailscale funnel 4000` publishes the port at the machine's
226
+ `ts.net` address.
227
+
228
+ On a private network without a tunnel, add the runner's host to `SSRF_ALLOWED_HOSTS` on
229
+ the instance. That host must also use HTTPS. When `NODE_ENV` on the instance is not
230
+ `production` or `test`, the instance also accepts `http://` and private addresses, for
231
+ example `http://localhost:4000`.
151
232
 
152
233
  ## Settings
153
234
 
@@ -161,21 +242,23 @@ npx -y @itsaplan/runner /path/to/config.json
161
242
  The environment variables have priority over the file. The command-line options have
162
243
  priority over both. An `agents` entry has priority over all three, for the fields it sets.
163
244
 
164
- | Field | Environment variable | Default | What it does |
165
- | ---------------- | --------------------------- | ------------------ | ------------------------------------------------------------------ |
166
- | `url` | `ITSAPLAN_URL` | required | Your Itsaplan instance |
167
- | `apiKey` | `ITSAPLAN_API_KEY` | required | The external agent's key. Replaced by `apiKeys` or `agents` |
168
- | `apiKeys` | | — | Several keys, all with these settings. Not with `agents` |
245
+ | Field | Environment variable | Default | What it does |
246
+ | ---------------- | --------------------------- | ------------------ | ---------------------------------------------------------------------- |
247
+ | `url` | `ITSAPLAN_URL` | required | Your Itsaplan instance |
248
+ | `apiKey` | `ITSAPLAN_API_KEY` | required | The external agent's key. Replaced by `apiKeys` or `agents` |
249
+ | `apiKeys` | | — | Several keys, all with these settings. Not with `agents` |
169
250
  | `agents` | | — | Several agents, each with its own key and settings. Not with `apiKeys` |
170
- | `agent` | `ITSAPLAN_AGENT` | — | The coding agent to run |
171
- | `args` | | `[]` | Arguments added after the preset's |
172
- | `command` | `ITSAPLAN_COMMAND` | — | Your own command, instead of `agent` |
173
- | `cwd` | `ITSAPLAN_CWD` | where you start it | Working directory for the command |
174
- | `env` | | `{}` | Extra variables for the command |
175
- | `concurrency` | `ITSAPLAN_CONCURRENCY` | `1` | Tasks at once, per agent. Queued runs and chat answers count apart |
176
- | `pollIntervalMs` | `ITSAPLAN_POLL_INTERVAL_MS` | `3000` | Wait after an empty queue. Minimum 1000 |
177
- | `timeoutMs` | `ITSAPLAN_TIMEOUT_MS` | `1800000` | Time before the runner stops a task |
178
- | `outputFormat` | `ITSAPLAN_OUTPUT_FORMAT` | the preset's | How the runner reads a chat answer |
251
+ | `agent` | `ITSAPLAN_AGENT` | — | The coding agent to run |
252
+ | `args` | | `[]` | Arguments added after the preset's |
253
+ | `command` | `ITSAPLAN_COMMAND` | — | Your own command, instead of `agent` |
254
+ | `cwd` | `ITSAPLAN_CWD` | where you start it | Working directory for the command |
255
+ | `env` | | `{}` | Extra variables for the command |
256
+ | `concurrency` | `ITSAPLAN_CONCURRENCY` | `1` | Tasks at once, per agent. Queued runs and chat answers count apart |
257
+ | `pollIntervalMs` | `ITSAPLAN_POLL_INTERVAL_MS` | `3000` | Wait after an empty queue. Minimum 1000 |
258
+ | `timeoutMs` | `ITSAPLAN_TIMEOUT_MS` | `1800000` | Time before the runner stops a task |
259
+ | `outputFormat` | `ITSAPLAN_OUTPUT_FORMAT` | the preset's | How the runner reads the output. See [The output](#the-output) |
260
+ | `listen` | `ITSAPLAN_LISTEN` | — | Port that receives the tasks. The runner does not poll for the agent |
261
+ | `signingSecret` | `ITSAPLAN_SIGNING_SECRET` | — | The agent's signing secret. Required with `listen` |
179
262
 
180
263
  Set `agent` or `command`. With the environment you need no file at all:
181
264
 
@@ -190,8 +273,8 @@ npx -y @itsaplan/runner --agent claude
190
273
 
191
274
  The server writes the task text. The text states what occurred, what to do, and to post
192
275
  the result as a comment on the issue. Your own `command` receives the text on **stdin**. A
193
- preset gives it to its CLI in the way that CLI accepts it: on stdin for `claude` and
194
- `codex`, as an argument for `antigravity`, `copilot`, `opencode`, `pi` and `omp`.
276
+ preset gives it to its CLI in the way that CLI accepts it: on stdin for `claude`, `codex`
277
+ and `pi`, as an argument for `antigravity`, `copilot`, `opencode` and `omp`.
195
278
 
196
279
  The agent reads all other data through the MCP server at `$ITSAPLAN_URL/mcp`. The agent
197
280
  sends `$ITSAPLAN_API_KEY` as a bearer token, and acts as its own user with its role.
@@ -206,12 +289,11 @@ Every run holds these variables:
206
289
  | `ITSAPLAN_API_KEY` | the agent's key, for the API and the MCP server |
207
290
  | `ITSAPLAN_TRIGGER` | `mention`, `delegation`, `field`, `schedule`, `manual`, `status`, or `chat` |
208
291
  | `ITSAPLAN_SYSTEM_PROMPT` | the context of the run, for a command that takes a system prompt |
209
- | `ITSAPLAN_TIMEOUT_MS` | the effective task timeout in milliseconds, including per-agent overrides |
292
+ | `ITSAPLAN_TIMEOUT_MS` | the task timeout in milliseconds |
210
293
 
211
- Custom commands can read `ITSAPLAN_TIMEOUT_MS` to configure their own execution timeout.
212
- The runner supplies the resolved `timeoutMs`, overriding any inherited value or value in
213
- `env`. Its timer starts when the command is spawned, so startup and waiting inside the
214
- command count toward the same limit.
294
+ A command can read `ITSAPLAN_TIMEOUT_MS` to set its own timeout. The value is the
295
+ `timeoutMs` of that agent, and it replaces a value from the environment or from `env`.
296
+ The time starts when the command starts.
215
297
 
216
298
  A queued run adds three more:
217
299
 
@@ -230,25 +312,46 @@ A chat message adds three others in their place, and `ITSAPLAN_TRIGGER` is `chat
230
312
  | `ITSAPLAN_SESSION_ID` | the coding agent's session to resume. Empty for the first message |
231
313
 
232
314
  The task text of a chat message includes the conversation, unless a session holds it. See
233
- Sessions below.
315
+ [Sessions](#sessions).
234
316
 
235
- ### The result
317
+ ## The output
236
318
 
237
- Exit code `0` is a success. Any other code is a failure. The end of stderr then becomes
238
- the error. Stdout becomes the run's output in both cases.
319
+ Exit code `0` is a success. Any other code is a failure, and the end of stderr becomes the
320
+ error.
239
321
 
240
- ## Chat
241
-
242
- The runner streams the answer to the chat while the command runs. It reports the tool
243
- calls with the answer. `outputFormat` tells the runner how to read the output:
322
+ `outputFormat` tells the runner how to read stdout:
244
323
 
245
- - `text` — all the output is the answer.
324
+ - `text` — all of stdout is the answer.
246
325
  - `claude-stream-json`, `codex-jsonl`, `opencode-json`, `antigravity-stream-json`,
247
326
  `copilot-json`, `pi-json` — the event stream of that CLI. `pi` and `omp` both use
248
- `pi-json`.
327
+ `pi-json`. The runner reads the text and the tool calls from it.
249
328
 
250
329
  A preset sets `outputFormat` for you. Set it yourself only with your own `command`. If the
251
- output does not agree with the format, the runner sends the output as plain text.
330
+ output does not agree with the format, the runner uses the output as plain text.
331
+
332
+ A queued run reports one answer as its output. With an event stream format, the answer is
333
+ the text that the agent wrote after its last tool call.
334
+
335
+ ### Run transcripts
336
+
337
+ While a queued run works, the runner sends the agent's messages and tool calls to the
338
+ instance. The issue shows them as the run's transcript.
339
+
340
+ - The tool calls need an event stream format. With `text`, the transcript holds only the
341
+ output.
342
+ - With `codex-jsonl`, the run shows an **Open in Codex** link. The link opens the session
343
+ only where that session is: in the Codex app of the viewer, or on a connected host. The
344
+ link does not copy the session.
345
+ - The instance keeps up to 2,000 events or 1 MB for each attempt, and marks a transcript
346
+ that it cut. The cut does not change the result of the run.
347
+ - If the last upload fails with a network or server error, the runner tries it one more
348
+ time. Then it reports the result of the command, even with an incomplete transcript.
349
+ Any other refusal fails the run.
350
+
351
+ ## Chat
352
+
353
+ The runner streams the answer to the chat while the command runs. It sends the tool calls
354
+ with the answer.
252
355
 
253
356
  ### Sessions
254
357
 
@@ -264,8 +367,7 @@ Without a session the server sends the last 20 messages of the conversation as p
264
367
  ## Requirements
265
368
 
266
369
  - **Node 20 or later.**
267
- - **An It's a Plan instance 0.10.0 or later.** The chat needs 0.11.0. An older instance
268
- gives the runner only the queued runs.
370
+ - **An It's a Plan instance 1.7.0 or later.**
269
371
  - **An external agent in your project.** The runner authenticates with its API key.
270
372
  - **The coding agent, installed and signed in on the same machine.** The runner starts the
271
373
  CLI as you, but it cannot sign in for you.