@itsaplan/runner 0.5.2 → 0.6.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 +97 -7
  2. package/dist/cli.js +927 -730
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -11,6 +11,8 @@ 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
+ - Receives the tasks on a port instead of polling, for an agent whose Connection is
15
+ Endpoint. See [Receiving tasks on a port](#receiving-tasks-on-a-port).
14
16
 
15
17
  ## Quick start
16
18
 
@@ -149,6 +151,86 @@ options describe every agent at once; an entry has priority over them.
149
151
 
150
152
  An agent whose key the instance refuses stops on its own, and the others keep working.
151
153
 
154
+ ## Receiving tasks on a port
155
+
156
+ By default the runner polls the instance for tasks. With `listen`, the instance sends each
157
+ task to the runner when the task is created. The runner then does not poll.
158
+
159
+ Use `listen` for a runner on a server that the instance can reach. On a laptop behind a
160
+ router, use polling. Polling needs no setup, and the chat is equally fast with it: a chat
161
+ claim waits on the server until a message comes.
162
+
163
+ **1.** In the agent's settings, open Connection and select Endpoint. Enter the public
164
+ HTTPS address of the runner's port (see [Reaching the port](#reaching-the-port)), and
165
+ save. Copy the signing secret.
166
+
167
+ **2.** Add `listen` and `signingSecret` to `itsaplan-runner.json`:
168
+
169
+ ```json
170
+ {
171
+ "url": "https://plan.example.com",
172
+ "apiKey": "the agent's key",
173
+ "agent": "claude",
174
+ "listen": 4000,
175
+ "signingSecret": "whsec_…"
176
+ }
177
+ ```
178
+
179
+ **3.** Start the runner. It logs `listening on port 4000`. In the agent's settings, click
180
+ Send test. The result must be `200`.
181
+
182
+ The runner answers each task immediately. Then it runs the task as it runs a polled task,
183
+ with the same command, heartbeats and result. The rules for a request:
184
+
185
+ - The runner accepts only a `POST` request signed with the secret of one of its agents.
186
+ - It refuses a request whose signature time is more than five minutes from its clock.
187
+ - If the instance sends a task again while the task runs, the runner does not start it a
188
+ second time.
189
+
190
+ Several agents can use one port. The runner identifies the agent by the secret, so each
191
+ agent needs its own `signingSecret`. Put the agents in `agents`:
192
+
193
+ ```json
194
+ {
195
+ "url": "https://plan.example.com",
196
+ "agent": "claude",
197
+ "listen": 4000,
198
+ "agents": [
199
+ { "apiKey": "the first agent's key", "signingSecret": "whsec_first…" },
200
+ { "apiKey": "the second agent's key", "signingSecret": "whsec_second…" }
201
+ ]
202
+ }
203
+ ```
204
+
205
+ In the same file, the runner polls for each agent that has no `listen`.
206
+
207
+ ### Reaching the port
208
+
209
+ The runner serves plain HTTP. In production, the instance requires HTTPS. Put a tunnel or a
210
+ reverse proxy in front of the port:
211
+
212
+ - **Reverse proxy** on a server with a domain. Caddy gets the certificate automatically:
213
+
214
+ ```
215
+ runner.example.com {
216
+ reverse_proxy localhost:4000
217
+ }
218
+ ```
219
+
220
+ - **Cloudflare Tunnel.** `cloudflared tunnel --url http://localhost:4000` prints a
221
+ `trycloudflare.com` address. The address changes each time the tunnel starts, and then
222
+ you must change the agent's URL too. A named tunnel on your own domain keeps the same
223
+ address.
224
+ - **ngrok.** `ngrok http 4000`. A static domain in your ngrok account keeps the same
225
+ address.
226
+ - **Tailscale Funnel.** `tailscale funnel 4000` publishes the port at the machine's
227
+ `ts.net` address.
228
+
229
+ On a private network without a tunnel, add the runner's host to `SSRF_ALLOWED_HOSTS` on
230
+ the instance. That host must also use HTTPS. When `NODE_ENV` on the instance is not
231
+ `production` or `test`, the instance also accepts `http://` and private addresses, for
232
+ example `http://localhost:4000`.
233
+
152
234
  ## Settings
153
235
 
154
236
  The runner reads `./itsaplan-runner.json`. For a different file, give the path as an
@@ -176,6 +258,8 @@ priority over both. An `agents` entry has priority over all three, for the field
176
258
  | `pollIntervalMs` | `ITSAPLAN_POLL_INTERVAL_MS` | `3000` | Wait after an empty queue. Minimum 1000 |
177
259
  | `timeoutMs` | `ITSAPLAN_TIMEOUT_MS` | `1800000` | Time before the runner stops a task |
178
260
  | `outputFormat` | `ITSAPLAN_OUTPUT_FORMAT` | the preset's | How the runner reads a chat answer |
261
+ | `listen` | `ITSAPLAN_LISTEN` | — | Port that receives the tasks. The runner does not poll for the agent |
262
+ | `signingSecret` | `ITSAPLAN_SIGNING_SECRET` | — | The agent's signing secret. Required with `listen` |
179
263
 
180
264
  Set `agent` or `command`. With the environment you need no file at all:
181
265
 
@@ -200,12 +284,18 @@ sends `$ITSAPLAN_API_KEY` as a bearer token, and acts as its own user with its r
200
284
 
201
285
  Every run holds these variables:
202
286
 
203
- | Variable | What it holds |
204
- | ------------------------ | ----------------------------------------------------------------- |
205
- | `ITSAPLAN_URL` | the instance that sent the task |
206
- | `ITSAPLAN_API_KEY` | the agent's key, for the API and the MCP server |
207
- | `ITSAPLAN_TRIGGER` | `mention`, `delegation`, `field`, `schedule`, `manual`, or `chat` |
208
- | `ITSAPLAN_SYSTEM_PROMPT` | the context of the run, for a command that takes a system prompt |
287
+ | Variable | What it holds |
288
+ | ------------------------ | --------------------------------------------------------------------------- |
289
+ | `ITSAPLAN_URL` | the instance that sent the task |
290
+ | `ITSAPLAN_API_KEY` | the agent's key, for the API and the MCP server |
291
+ | `ITSAPLAN_TRIGGER` | `mention`, `delegation`, `field`, `schedule`, `manual`, `status`, or `chat` |
292
+ | `ITSAPLAN_SYSTEM_PROMPT` | the context of the run, for a command that takes a system prompt |
293
+ | `ITSAPLAN_TIMEOUT_MS` | the effective task timeout in milliseconds, including per-agent overrides |
294
+
295
+ Custom commands can read `ITSAPLAN_TIMEOUT_MS` to configure their own execution timeout.
296
+ The runner supplies the resolved `timeoutMs`, overriding any inherited value or value in
297
+ `env`. Its timer starts when the command is spawned, so startup and waiting inside the
298
+ command count toward the same limit.
209
299
 
210
300
  A queued run adds three more:
211
301
 
@@ -259,7 +349,7 @@ Without a session the server sends the last 20 messages of the conversation as p
259
349
 
260
350
  - **Node 20 or later.**
261
351
  - **An It's a Plan instance 0.10.0 or later.** The chat needs 0.11.0. An older instance
262
- gives the runner only the queued runs.
352
+ gives the runner only the queued runs. `listen` needs 1.6.0.
263
353
  - **An external agent in your project.** The runner authenticates with its API key.
264
354
  - **The coding agent, installed and signed in on the same machine.** The runner starts the
265
355
  CLI as you, but it cannot sign in for you.