lobstah 0.7.2 → 0.7.3
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/dist/main.js +3301 -2194
- package/dist/runner.js +903 -296
- package/docs/configuration.md +83 -0
- package/docs/man.md +44 -1
- package/docs/vocabulary.md +4 -4
- package/package.json +1 -1
package/docs/configuration.md
CHANGED
|
@@ -32,6 +32,7 @@ The descriptor's `repo` field resolves here; the key is what dispatchers name.
|
|
|
32
32
|
| `env` | no | Environment merged into every dispatch for this repo. |
|
|
33
33
|
| `pickup` | no (`false`) | Opt this repo into `[pickup.github]` multi-repo mode. Explicit per repo — nothing becomes pickable by being configured. |
|
|
34
34
|
| `pushEarly`, `draftPr`, `checkpointOnStop` | no (inherit `[limits]`) | Override remote preservation for this repo's headless dispatches. |
|
|
35
|
+
| `poolKeep` | no | Extra gitignore-style patterns a pool reset keeps (e.g. `["apps/ios/DerivedData/"]`), on top of the built-in keep list. See [`[pools.<name>]`](#poolsname--warm-worktrees-for-headless-dispatches). |
|
|
35
36
|
| `humanGateChecks` | no | Check names that fail until a person approves the change (e.g. `["owner approval"]`). `*` matches any run of characters. On a PR of a dispatch in this repo, a failed human gate never starts a PR repair or a CI-fix continuation. A PR whose only failing checks are human gates shows `repair.status: waiting` with `heldBy: human-gate`. A worker adds gates for one PR with `lobstah report --human-gate <check>`. |
|
|
36
37
|
|
|
37
38
|
`[repos.<key>.harness]` — per-repo harness defaults: `default` (`claude` \|
|
|
@@ -156,6 +157,87 @@ repos = ["homebase", "matcha"]
|
|
|
156
157
|
repos = ["lobstah", "lavish"]
|
|
157
158
|
```
|
|
158
159
|
|
|
160
|
+
## `[pools.<name>]` — warm worktrees for headless dispatches
|
|
161
|
+
|
|
162
|
+
A pool is a set of worktrees for one repo that the daemon keeps warm (checked
|
|
163
|
+
out, dependencies installed), with no session attached. A dispatch that names
|
|
164
|
+
the pool takes a free one, and lobstah resets it and starts a fresh headless
|
|
165
|
+
session there: a new conversation without the cold start (new worktree, full
|
|
166
|
+
install, first build). Pools are separate from traps; a trap never takes pool
|
|
167
|
+
work, and pool work is never addressed to one.
|
|
168
|
+
|
|
169
|
+
```toml
|
|
170
|
+
[pools.codeclaw]
|
|
171
|
+
repo = "homebase"
|
|
172
|
+
size = 3
|
|
173
|
+
overflow = "headless" # or "queue"
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
| Key | Default | Meaning |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| `repo` | — (required) | The `[repos.<key>]` the pool's worktrees check out. |
|
|
179
|
+
| `size` | `1` | How many worktrees the daemon keeps. Slots live at `~/.lobstah/pools/<name>/1` … `/<size>`. Lowering it leaves the extra slots on disk, unused. |
|
|
180
|
+
| `overflow` | `"headless"` | When every pool worktree is taken: `"headless"` runs the dispatch in a normal cold worktree (`worktrees/<id>`); `"queue"` leaves it queued until a pool worktree is free (`man tend` shows `queued work waits: no free worktree in pool <name>`). |
|
|
181
|
+
|
|
182
|
+
**Dispatching.** `lobstah dispatch --pool <name> --brief …` (`--repo` defaults
|
|
183
|
+
to the pool's repo and must match it if given). The OpenClaw
|
|
184
|
+
`lobstah_dispatch` tool takes `pool`, and `[pickup].pools` routes tracker
|
|
185
|
+
issues to a pool. The descriptor field is `pool`. A pool dispatch counts
|
|
186
|
+
against `[limits].maxConcurrent` like any headless dispatch.
|
|
187
|
+
|
|
188
|
+
**Claim and reset.** The runner takes the first free slot (its worktree lock,
|
|
189
|
+
the same lock every headless dispatch holds) and resets it: fetch trunk,
|
|
190
|
+
check out `lobstah/<id>` from `origin/<trunk>` (a daemon repair starts from
|
|
191
|
+
its PR's head instead), remove untracked and ignored files except the keep
|
|
192
|
+
list, remove production env files, then run the repo's `setup` commands. The
|
|
193
|
+
first status note says `pool worktree <name>/<slot>`, or `pool <name> full:
|
|
194
|
+
cold worktree`. Evidence records `pool: <name>/<slot>`.
|
|
195
|
+
|
|
196
|
+
The keep list (gitignore-style, matched at any depth unless anchored) is
|
|
197
|
+
dependency installs, build caches and local env files:
|
|
198
|
+
`node_modules/`, `.pnpm-store/`, `.venv/`, `vendor/bundle/`, `Pods/`,
|
|
199
|
+
`.turbo/`, `.next/cache/`, `.nx/cache/`, `.cache/`, `*.tsbuildinfo`,
|
|
200
|
+
`target/`, `.gradle/`, `.build/`, `.env*`, `.dev.vars`, plus the repo's
|
|
201
|
+
`poolKeep`. Any `.env*` file whose name contains `prod` is removed even so: a
|
|
202
|
+
production env file is never left in a pool worktree.
|
|
203
|
+
|
|
204
|
+
**Safety.** A reset never discards work. A slot with uncommitted changes
|
|
205
|
+
(tracked edits, or untracked files git does not ignore, outside `scratch`
|
|
206
|
+
paths) or commits on no remote branch is taken out of rotation instead, a
|
|
207
|
+
`pool-out` helm notice names it and its path, and the dispatch is served per
|
|
208
|
+
`overflow`. Commit and push (or discard) the work there; the next warm-up
|
|
209
|
+
pass finds it clean and pushed and returns it (a quiet `pool-returned`
|
|
210
|
+
notice).
|
|
211
|
+
|
|
212
|
+
**Release.** A slot returns to the pool when its dispatch finishes (done,
|
|
213
|
+
failed or cancelled): the lock is released, and the runner has pushed the
|
|
214
|
+
branch (`pushEarly`/`checkpointOnStop`). The warm-up then checks it; one left
|
|
215
|
+
with unpushed or uncommitted work goes out of rotation as above.
|
|
216
|
+
|
|
217
|
+
**Follow-ups** keep `[limits].reuseWorktree`: a follow-up of a pool dispatch
|
|
218
|
+
continues in the same pool worktree, same branch and HEAD, while it is free
|
|
219
|
+
and no pool dispatch has claimed it since. Once a pool dispatch has reset it,
|
|
220
|
+
the follow-up falls back exactly as when the origin's worktree is gone (a
|
|
221
|
+
fresh worktree from trunk, cold session with a progress note) and its first
|
|
222
|
+
status note says `fresh worktree (origin pool worktree <name>/<slot> was
|
|
223
|
+
reused by <id>)`. A follow-up dispatched with `--pool` takes that fresh
|
|
224
|
+
worktree from the pool; one without stays out of it.
|
|
225
|
+
|
|
226
|
+
**Warm-up.** Each tick the daemon starts a warm-up process for a pool with
|
|
227
|
+
something due (at most one per pool, at most once per 30s): it creates
|
|
228
|
+
missing slots (a detached checkout of trunk, then `setup`) while the
|
|
229
|
+
worktrees volume has `[limits].minFreeGB` free, fetches trunk every 10
|
|
230
|
+
minutes, checks slots whose dispatch has finished, and re-checks
|
|
231
|
+
out-of-rotation slots every 5 minutes. It takes a slot's lock before it
|
|
232
|
+
looks at it, so a claimed slot is never touched. Its log is
|
|
233
|
+
`~/.lobstah/pools/<name>/warm.log`.
|
|
234
|
+
|
|
235
|
+
**Visibility and cleanup.** `lobstah man tend` and `lobstah status` list each
|
|
236
|
+
pool: size, free, which dispatch holds each claimed slot, which slots are out
|
|
237
|
+
of rotation and why, and which are still being made. Pool worktrees live
|
|
238
|
+
outside `~/.lobstah/worktrees`: `lobstah cull`, the retention cull,
|
|
239
|
+
`releaseOnMerge` and the free-space guard never remove them.
|
|
240
|
+
|
|
159
241
|
## `[pickup]` — tracker loops (`lobstah pick`)
|
|
160
242
|
|
|
161
243
|
| Key | Default | Meaning |
|
|
@@ -163,6 +245,7 @@ repos = ["lobstah", "lavish"]
|
|
|
163
245
|
| `pollSecs` | `45` | Poll cadence. Outbound only — no webhooks, ever. |
|
|
164
246
|
| `liveComment` | `true` | Keep one editable, marked status comment per dispatch. Routine edits are capped at once per minute; human-needed and terminal transitions still post a fresh notification comment. Falls back to transition comments if editing is unavailable. |
|
|
165
247
|
| `notifyCommand` | — | Pickup's own hook, fired on tracker-report transitions with `LOBSTAH_KEY`, `LOBSTAH_UUID`, `LOBSTAH_VERB`, `LOBSTAH_NOTE`, `LOBSTAH_PR_URL`. |
|
|
248
|
+
| `pools` | `[]` | Pool names (`["codeclaw"]`). An issue dispatch for repo R runs in the first listed `[pools.<name>]` whose `repo` is R. Review rounds are follow-ups and keep their chain's worktree rule. |
|
|
166
249
|
|
|
167
250
|
### Token sources (both trackers)
|
|
168
251
|
|
package/docs/man.md
CHANGED
|
@@ -1308,6 +1308,47 @@ replaces the custom title. `CLAUDE_CODE_DISABLE_TERMINAL_TITLE=1` on the
|
|
|
1308
1308
|
command that starts Claude Code stops that for that one process; the start
|
|
1309
1309
|
command `man throw` opens sets it. Codex also sets the terminal title.
|
|
1310
1310
|
|
|
1311
|
+
### Warm worktree pools
|
|
1312
|
+
|
|
1313
|
+
Bursty work (a tracker's issues, CodeClaw's delegations) wants a fresh
|
|
1314
|
+
conversation per issue but not a cold start. A pool
|
|
1315
|
+
([`[pools.<name>]`](configuration.md#poolsname--warm-worktrees-for-headless-dispatches))
|
|
1316
|
+
is a set of worktrees for one repo that the daemon keeps checked out and
|
|
1317
|
+
installed, with no session attached:
|
|
1318
|
+
|
|
1319
|
+
```
|
|
1320
|
+
lobstah dispatch --pool codeclaw --brief-text "<full brief>"
|
|
1321
|
+
```
|
|
1322
|
+
|
|
1323
|
+
The runner takes a free pool worktree, resets it to `lobstah/<id>` from trunk
|
|
1324
|
+
(untracked files go, except dependency installs, build caches and local env
|
|
1325
|
+
files; production env files always go), runs the repo's `setup`, and starts a
|
|
1326
|
+
new headless session there. The first status note names it: `pool worktree
|
|
1327
|
+
codeclaw/2`. When the pool is full, `overflow = "headless"` runs the dispatch
|
|
1328
|
+
in a cold worktree (`pool codeclaw full: cold worktree`) and `overflow =
|
|
1329
|
+
"queue"` leaves it queued until a pool worktree frees up. Pool work counts
|
|
1330
|
+
against `maxConcurrent`, and no trap ever takes it.
|
|
1331
|
+
|
|
1332
|
+
A reset never discards work. A pool worktree with uncommitted changes or
|
|
1333
|
+
commits on no remote is taken out of rotation, and a `pool-out` notice names
|
|
1334
|
+
it (`pool worktree codeclaw/2 (~/.lobstah/pools/codeclaw/2) is out of
|
|
1335
|
+
rotation: 1 commit(s) on no remote branch`). Push or discard the work there;
|
|
1336
|
+
the daemon's next warm-up returns it.
|
|
1337
|
+
|
|
1338
|
+
Follow-ups keep the usual rule: `lobstah send <id>` to finished pool work
|
|
1339
|
+
continues the same conversation in the same pool worktree while no pool
|
|
1340
|
+
dispatch has reset it since. Once one has, the follow-up starts as it does
|
|
1341
|
+
when its origin's worktree is gone: a fresh worktree, a cold session with a
|
|
1342
|
+
progress note, and a first note that says `fresh worktree (origin pool
|
|
1343
|
+
worktree codeclaw/2 was reused by <id>)`.
|
|
1344
|
+
|
|
1345
|
+
`man tend` and `lobstah status` list each pool:
|
|
1346
|
+
|
|
1347
|
+
```
|
|
1348
|
+
pools[1]{pool,repo,size,free,overflow,claimed,out,warming}:
|
|
1349
|
+
codeclaw,homebase,3,1,headless,1: 6a1f0c2e,2: 1 commit(s) on no remote branch (after 9b3d0a11),
|
|
1350
|
+
```
|
|
1351
|
+
|
|
1311
1352
|
### Culling and disk space
|
|
1312
1353
|
|
|
1313
1354
|
Worktrees are 1 to 8 GB each. `lobstah cull` sweeps what is finished: `done/`
|
|
@@ -1321,7 +1362,9 @@ when the same clean-and-pushed safety check passes; unsaved soak checkouts
|
|
|
1321
1362
|
remain protected even without a registration. A worktree that follow-ups
|
|
1322
1363
|
reused is one worktree shared by the chain: it stays while any dispatch in
|
|
1323
1364
|
the chain is queued or active, and it ages from the newest dispatch that
|
|
1324
|
-
used it.
|
|
1365
|
+
used it. Pool worktrees (`~/.lobstah/pools/`) are never culled, by `lobstah
|
|
1366
|
+
cull` or by the daemon: a pool keeps its worktrees, and the dispatch records
|
|
1367
|
+
that used them age out as usual.
|
|
1325
1368
|
|
|
1326
1369
|
Without `--apply` it is a dry run: it measures each target and prints the
|
|
1327
1370
|
sizes. A worktree is measured with one `du -sk`; where `du` is missing
|
package/docs/vocabulary.md
CHANGED
|
@@ -396,7 +396,7 @@ evidence).
|
|
|
396
396
|
| descriptor `pr` | `{ url, headRefName?, headSha? }`: the existing PR a dispatch works on. A PR repair and a pickup rebase chore carry it. The runner pushes no branch and opens no PR for such a dispatch; its worker pushes to the PR's head branch. |
|
|
397
397
|
| push rule | What a repair or rebase brief tells its worker: push only to the PR's head branch; on a non-fast-forward rejection, fetch, rebase the commits onto the moved head again, and push with `--force-with-lease` on the head just fetched, at most three times; a hook failure from a real test or type error is not retried; when it cannot push, report `failed "push rejected: <rejection text>; moved head <sha>"` and leave the PR as it was. That report marks the PR's repair `blocked` at the moved head and posts a `push-failed` notice. |
|
|
398
398
|
| checks unknown | Without `Checks: read`, the check re-reads the PR without `statusCheckRollup`: the PR state is recorded, `checks.unknown` is `no permission`, and the check's output carries the permission `error`. `pr:ready` never stands on unknown checks. |
|
|
399
|
-
| PR record | `~/.lobstah/prs/<owner>__<repo>__<n>.json` — the PR's latest observation keyed by the PR, not by a dispatch: the evidence `pr` object (with `title`, read on every check; a title change is not a state change) plus `key`, `repo` (`<owner>/<repo>`), `dispatches` (the ids whose watch observed it; empty for a human's or a culled PR), and `firstSeenAt` (the time of the first observation; written once, never rewritten). `firstSeenAt`, then the PR number, is the order of every PR list. A record from before `firstSeenAt` existed sorts by number at the earliest `firstSeenAt` in the set, and its next observation writes that time as its `firstSeenAt`. **Owner:** `packages/core/src/prs.ts` (`upsertPr`, `readPrs`); the one writer is the preset's observation path (`observePr`), on every observation, man-owned or dispatch-owned — a dispatch-owned one also stamps that dispatch's evidence, which stays the per-dispatch view. Tend's `pr:*` kinds and `stack-ready` aggregation (member
|
|
399
|
+
| PR record | `~/.lobstah/prs/<owner>__<repo>__<n>.json` — the PR's latest observation keyed by the PR, not by a dispatch: the evidence `pr` object (with `title`, read on every check; a title change is not a state change) plus `key`, `repo` (`<owner>/<repo>`), `dispatches` (the ids whose watch observed it; empty for a human's or a culled PR), and `firstSeenAt` (the time of the first observation; written once, never rewritten). `firstSeenAt`, then the PR number, is the order of every PR list. A record from before `firstSeenAt` existed sorts by number at the earliest `firstSeenAt` in the set, and its next observation writes that time as its `firstSeenAt`. **Owner:** `packages/core/src/prs.ts` (`upsertPr`, `readPrs`); the one writer is the preset's observation path (`observePr`), on every observation, man-owned or dispatch-owned — a dispatch-owned one also stamps that dispatch's evidence, which stays the per-dispatch view. Tend's `pr:*` kinds and `stack-ready` aggregation (member readiness is contained in one stack item), the glass PRs tab and stacks, the merged/closed notice, and PR acks read records first and fall back to dispatch evidence only for a PR with no record yet. `cull` removes records merged or closed longer than its window, never open ones. |
|
|
400
400
|
|
|
401
401
|
Every event carries `headSha`. A dispatch-owned PR watch records work events,
|
|
402
402
|
then the repair planner decides whether to follow up. One repair runs per PR
|
|
@@ -481,7 +481,7 @@ worktree is refused (the session lock); a stale one is adopted.
|
|
|
481
481
|
| protected ref | `refs/lobstah/traps/<trapId>` in the trap's repository: the trap's last HEAD, written at sign-on and before stow, a ghost sweep, or `cull` removes the checkout. It is not a branch, so worktree removal and branch cleanup leave it and its commit in place. A missing checkout is recreated from it. A trap checkout whose revision cannot be written there is not removed by a ghost sweep or cull. |
|
|
482
482
|
| trap request | A trap the human asked for from the glass's **+ New trap** button: a `trap-request` request in `requests/<id>.json` with a repo and a harness. It wakes the helm as a `trap-request` event; the helm starts it with `man throw --new --request <id>`, which takes the request's repo and harness and closes it. |
|
|
483
483
|
| notice | The helm's attention channel for non-status events (`~/.lobstah/notices/`): reservations (`trap-starting`), reservations that did not start (`trap-start-failed`), trap requests from the glass (`trap-request`), sign-ons, a trap free to take work (`trap-available`), a batch throw settled (`trap-batch`), sign-offs, ghosts, defective enlistments, orphaned work, bounced messages, PRs merged or closed, watches held over the fork cap (`watch-held`), failing (`watch-failing`), and recovered (`watch-recovered`), free-space holds (`disk-held`, `disk-cleared`), worktrees released after their PR merged (`worktree-released`, one per cull pass), a repair or rebase that could not push to its PR's branch (`push-failed`), repairs stopped on a PR that made no progress (`repair-stopped`), and a human's answer to a decision (`decision-answer`; its ref is the request id in `~/.lobstah/requests/`). A trap leaves the registry only through a `trap-stowed` or `trap-ghosted` notice — the end-state is always explicit. Consumed by `man wait`/the park. A trap's start wakes once, with `trap-available` when it is listening; `trap-starting`, `trap-signed-on`, `trap-stowed`, `trap-listening` (older homes), a ghost of an idle trap, and a batch member's `trap-available` are quiet: consumed without waking anyone, shown in tend, the glass, and (sign-offs and idle ghosts) the digest's `traps` table. A batch throw wakes once, with `trap-batch` when every throw settled: counts, and the names that failed. tend always shows the recent tail. |
|
|
484
|
-
| stack-ready notice | One wake for an all-ready PR stack, with the PRs in bottom-first merge order. One persistent item contains member readiness and
|
|
484
|
+
| stack-ready notice | One wake for an all-ready PR stack, with the PRs in bottom-first merge order. One persistent item contains member readiness, without member ready lobsters; while any member is not ready (a ready member still inside its settle window included) the item is quiet and reads `stack waiting: <n> of <m> ready: … — #<k> ready, confirming for <x> more min; #<j> conflicts`; growth replaces its notice, and bottom-first merges keep its identity and ack without another wake. Its link follows the current top PR; legacy duplicates collapse silently. |
|
|
485
485
|
|
|
486
486
|
Delivery routes by ownership, same as watches: a continuation for a chain
|
|
487
487
|
claimed by a live trap is addressed back to that trap and stays sticky.
|
|
@@ -543,8 +543,8 @@ never until someone acknowledges it.
|
|
|
543
543
|
| `pr:review` | An open PR has unresolved review questions, or requested changes that lobstah cannot repair, has exhausted, or is configured not to repair. | Every thread resolved and no changes requested, or merged / closed. |
|
|
544
544
|
| `pr:checks` | An open PR has a failed latest check, and lobstah cannot repair it, has exhausted attempts, or is configured not to repair. | Green on the head, or merged / closed. |
|
|
545
545
|
| `pr:conflict` | An open PR conflicts with its base, and lobstah cannot repair it, has exhausted attempts, or is configured not to repair. | The merge state leaves `DIRTY`, or merged / closed. |
|
|
546
|
-
| `pr:ready` | An open, non-draft PR has no review condition, a mergeable state (`CLEAN`, `HAS_HOOKS`, or `UNSTABLE`), and no failed, pending, or unknown latest checks. It is approved or has at least one check, continuously on the same head for `readySettleSecs` (default 600; `0` is immediate). Attention reads re-evaluate expiry without requiring a new forge event. In a trunk-rooted same-repo stack of two or more non-fork PRs, member
|
|
547
|
-
| `stack-ready` | Every open member of a trunk-rooted same-repo base/head chain meets the identical `pr:ready` rule, including settling. Enabled with `pr:ready` (or explicitly with `stack-ready`). One persistent item per stack, born at the bottom PR and carried by surviving membership through growth, restacks and merges. Member ready/watch items
|
|
546
|
+
| `pr:ready` | An open, non-draft PR has no review condition, a mergeable state (`CLEAN`, `HAS_HOOKS`, or `UNSTABLE`), and no failed, pending, or unknown latest checks. It is approved or has at least one check, continuously on the same head for `readySettleSecs` (default 600; `0` is immediate). Attention reads re-evaluate expiry without requiring a new forge event. In a trunk-rooted same-repo stack of two or more non-fork PRs, member readiness appears only inside the one stack item; the stack wakes once. | Merged or closed, or the ready conditions stop holding; any not-ready observation or new head resets settling. |
|
|
547
|
+
| `stack-ready` | Every open member of a trunk-rooted same-repo base/head chain meets the identical `pr:ready` rule, including settling. Enabled with `pr:ready` (or explicitly with `stack-ready`). One persistent item per stack, born at the bottom PR and carried by surviving membership through growth, restacks and merges. Member ready/watch items are grouped inside it, not separate lobsters; a member's human problem (`pr:conflict`, `pr:checks`, `pr:review`, `pr:draft`) also stands as that member's own item and walks by its own kind. Approval Gate failures never create an item. Links open the current top PR, with member links inside. Only an all-ready stack walks (pet, glass lob, helm wake); a partly ready one is a quiet standing row in tend (shown as `stack-waiting`), the digest, and the existing glass group, worded `stack waiting: <n> of <m> ready`, with a settling member written as `ready, confirming for <x> more min`. Existing `pr:` watches discover missing links at their normal cadence; `lobstah watch add pr:<owner>/<repo>#<n>` follows a stack from any member. | Any member becomes unready or gets a new head; all ready again wakes once. Growth replaces the notice and wakes once when the larger stack is ready. Bottom-first merges keep the item and ack without a wake for unchanged surviving heads; a remaining lone PR returns to normal behavior. Legacy duplicates collapse without a wake. |
|
|
548
548
|
| `report` | A filed report (`report --report`, `man file`) has no ack for this filing. Opt-in. Key `report:<lane>:<uuid>` or `report:helm:<grounds>:<rid>`. | Not cleared: it stays listed until culled. Opening it on the glass acks it (`by: glass`, the first time kept; no wake, and a CLI or API read does not), `lobstah attention ack <key>` acks it, and a newer report in the same chain acks the older; an acked report no longer walks, and `man tend`'s `viewed` column shows when it was acked. Refiling it stands it again until it is opened again. |
|
|
549
549
|
|
|
550
550
|
`pr:*` kinds read only the `pr:` watch's evidence — never a forge call —
|