lobstah 0.6.4 → 0.6.6
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 +7 -7
- package/dist/main.js +1309 -611
- package/dist/runner.js +4 -3
- package/docs/configuration.md +1 -0
- package/docs/design.md +9 -4
- package/docs/harness/claude-code.md +13 -14
- package/docs/harness/codex.md +5 -0
- package/docs/man.md +105 -11
- package/docs/vocabulary.md +41 -3
- package/package.json +1 -1
package/dist/runner.js
CHANGED
|
@@ -770,7 +770,8 @@ var DEFAULT_SOAK = {
|
|
|
770
770
|
deferSecs: 90,
|
|
771
771
|
ttlSecs: 1800,
|
|
772
772
|
beat: true,
|
|
773
|
-
pausedTtlSecs: 86400
|
|
773
|
+
pausedTtlSecs: 86400,
|
|
774
|
+
signOffGraceSecs: 600
|
|
774
775
|
};
|
|
775
776
|
var DEFAULT_HELM = {
|
|
776
777
|
ttlSecs: 1800,
|
|
@@ -1741,10 +1742,10 @@ function chainPr(id, lane) {
|
|
|
1741
1742
|
// packages/core/dist/pr-repair.js
|
|
1742
1743
|
var PUSH_RETRIES = 3;
|
|
1743
1744
|
var PUSH_REJECTED = "push rejected:";
|
|
1744
|
-
function pushRule(branch, id = "<dispatch id>", mode = "rebase") {
|
|
1745
|
+
function pushRule(branch, id = "<dispatch id>", mode = "rebase", also = []) {
|
|
1745
1746
|
const b = branch ?? "the PR's head branch";
|
|
1746
1747
|
return [
|
|
1747
|
-
`Push only to the existing branch ${b}.`,
|
|
1748
|
+
also.length ? `Push only to the existing branch ${b} and the branches of the PRs stacked on it named below.` : `Push only to the existing branch ${b}.`,
|
|
1748
1749
|
mode === "merge" ? `If the push is rejected as non-fast-forward because ${b} moved, fetch ${b}, merge the moved head into your branch, and push again with a normal push. Retry at most ${PUSH_RETRIES} times. Never force-push.` : `If the push is rejected as non-fast-forward because ${b} moved, fetch ${b}, rebase your commits onto the moved head again, and push with \`--force-with-lease=${b}:<the head you just fetched>\`. Retry at most ${PUSH_RETRIES} times.`,
|
|
1749
1750
|
"If a push hook fails with a real test or type error, do not retry the push: fix the error, commit, and push again.",
|
|
1750
1751
|
`If you still cannot push, report \`lobstah report ${id} failed "${PUSH_REJECTED} <rejection text>; moved head <full sha of the head you fetched>"\` and leave the PR as it was.`,
|
package/docs/configuration.md
CHANGED
|
@@ -107,6 +107,7 @@ send a continuation.
|
|
|
107
107
|
| `deferSecs` | `90` | A soaking session whose park heartbeat is this fresh holds unaddressed matching bait — the daemon waits instead of spawning. Addressed bait (`--for session:<id>`) waits regardless, until the registration is gone. |
|
|
108
108
|
| `ttlSecs` | `1800` | Heartbeat age past which a registration is a ghost trap: the sweep removes it and requeues an unfinished catch, finalizes done/failed in `done/`, or finalizes a cancelled catch as failed. A fresh `lobstah report` or beat counts as liveness. If the daemon's own tick gap exceeds this TTL, it grants a full TTL after resume before sweeping any traps. |
|
|
109
109
|
| `beat` | `true` | The post-tool hook (`lobstah soak beat`) refreshes a soaking session's liveness and writes its catch's activity, at most once per 30 seconds per trap. With `false` the hook does nothing, and a trap's liveness comes from its reports and its park only. |
|
|
110
|
+
| `signOffGraceSecs` | `600` | After a trap signs off, its address is held this long: messages to it wait for a re-soak of the same worktree instead of bouncing, `send` to it queues with a warning, and work addressed to it raises no `bait-orphaned` notice. After the grace the daemon bounces the held messages. `0` bounces at sign-off. |
|
|
110
111
|
| `pausedTtlSecs` | `86400` (24 hours) | A trap whose catch last reported `paused` is kept out of the ghost sweep this long after the report. `report paused --until <iso|duration>` sets the expiry instead. After it, the sweep removes the trap as usual, and the notice says the pause expired. |
|
|
111
112
|
|
|
112
113
|
## `[helm]` — the orchestrator seat (`lobstah man helm`)
|
package/docs/design.md
CHANGED
|
@@ -694,7 +694,7 @@ with [Preact](https://preactjs.com) and [htm](https://github.com/developit/htm)
|
|
|
694
694
|
| `src/store.ts` | the one store: the latest snapshot, the route, the prefs, the open modal, the stale flag, the lob state |
|
|
695
695
|
| `src/actions.ts` | every change the page can make (open or close a modal, set a preference, hide a lob, receive a snapshot) |
|
|
696
696
|
| `src/main.ts` | the entry: builds the store from localStorage and the URL, renders `App` into the body on every change, starts polling |
|
|
697
|
-
| `src/poll.ts` | `/data` polling: every 2s, one request at a time, paused while the tab is hidden |
|
|
697
|
+
| `src/poll.ts` | `/data` polling: every 2s, one request at a time, paused while the tab is hidden; a 304 applies only the beats (server time and heartbeats) |
|
|
698
698
|
| `src/route.ts` | the hash tabs (`#deck`, `#dispatches`, `#traps`, `#prs`, `#notices`) |
|
|
699
699
|
| `src/prefs.ts` | the per-browser preferences and lob hides, in localStorage |
|
|
700
700
|
| `src/components/*.ts` | `App`, `Header`, `Deck`, `Dispatches`, `Traps`, `PRs`, `Notices`, `Modal` (dispatch, trap, helm, PR, settings), `Lobs` |
|
|
@@ -710,9 +710,14 @@ the active tab's section renders.
|
|
|
710
710
|
The pure helpers stay beside the server in `apps/cli/src` and are imported by
|
|
711
711
|
both sides: `glass-diff.ts` (the per-section selectors, tab route, filters,
|
|
712
712
|
PR modal data) and `glass-lobs.ts` (which lobs walk). The `/data` payload
|
|
713
|
-
type, `GlassSnapshot`, lives in `@lobstah/core`: `
|
|
714
|
-
|
|
715
|
-
on both sides.
|
|
713
|
+
type, `GlassSnapshot`, lives in `@lobstah/core`: `glass-poll.ts` builds it
|
|
714
|
+
from `buildGlassSnapshot()` and every component reads it, so a renamed field
|
|
715
|
+
fails typecheck on both sides. `/data` carries dispatch summaries and the
|
|
716
|
+
last day's history (at least the newest few of each kind), with an ETag that
|
|
717
|
+
leaves out heartbeats and ages; those ride in the `x-lobstah-beats` header
|
|
718
|
+
of every answer, a 304 too. A dispatch modal fetches the rest from
|
|
719
|
+
`/data/dispatch/<id>`, and `/data/older?kind=dispatches|notices|prs&offset=<n>`
|
|
720
|
+
pages in older history. The client typechecks under its own
|
|
716
721
|
`apps/cli/glass/tsconfig.json` (DOM lib). `preact` and `htm` are dev
|
|
717
722
|
dependencies of `apps/cli`: they exist only inside the bundled page.
|
|
718
723
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# lobstah in Claude Code
|
|
2
2
|
|
|
3
|
-
Use `/lobstah:man` to
|
|
4
|
-
|
|
3
|
+
Use `/lobstah:man` to take the helm. You can also say "Lobstah man, take
|
|
4
|
+
the helm."
|
|
5
5
|
|
|
6
|
-
This page covers the plugin's
|
|
6
|
+
This page covers the plugin's skills, session id, and Stop hook.
|
|
7
7
|
See [docs/man.md](../man.md) for the full pattern.
|
|
8
8
|
|
|
9
9
|
The lobstah man skill brings up the glass and prints its URL. Open it in the
|
|
@@ -41,18 +41,17 @@ row, and the session-start brief says when the plugin is behind.
|
|
|
41
41
|
`lobstah send <id> "<instruction>"` steers live or queued work and wakes a
|
|
42
42
|
finished dispatch as a follow-up.
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
The plugin has four skills and no slash commands:
|
|
45
45
|
|
|
46
|
-
|
|
|
47
|
-
|
|
|
48
|
-
| `/lobstah:
|
|
49
|
-
| `/lobstah:
|
|
50
|
-
| `/lobstah:
|
|
51
|
-
| `/lobstah:
|
|
52
|
-
| `/lobstah:stow` | `lobstah stow` |
|
|
46
|
+
| Skill | What it does |
|
|
47
|
+
| ----- | ------------ |
|
|
48
|
+
| `/lobstah:man` | Take the helm: `lobstah man helm`, the glass, then the orchestrator flow. |
|
|
49
|
+
| `/lobstah:trap` | Sign on as a trap: `lobstah soak`, then the worker flow. |
|
|
50
|
+
| `/lobstah:stow` | Sign the trap off with `lobstah stow` and report which trap was stowed. |
|
|
51
|
+
| `/lobstah:relieve` | Step down with `lobstah man relieve`; afterwards no `man wait` or `man report`, and no re-take unless asked. |
|
|
53
52
|
|
|
54
|
-
Claude Code also loads
|
|
55
|
-
|
|
53
|
+
Claude Code also loads these skills on its own when you ask for that kind of
|
|
54
|
+
work ("take the helm", "work as a trap", "stow", "step down").
|
|
56
55
|
|
|
57
56
|
## The session id
|
|
58
57
|
|
|
@@ -101,7 +100,7 @@ Without the plugin, `lobstah man init` merges the Stop hook into
|
|
|
101
100
|
|
|
102
101
|
## CLI vs desktop app
|
|
103
102
|
|
|
104
|
-
The plugin, hooks,
|
|
103
|
+
The plugin, hooks, and skills are the same whether you run
|
|
105
104
|
`claude` in a terminal or use the Code tab of the Claude desktop app. What
|
|
106
105
|
differs:
|
|
107
106
|
|
package/docs/harness/codex.md
CHANGED
|
@@ -59,8 +59,13 @@ mentions below. Plain-language requests load the same skills:
|
|
|
59
59
|
/skills
|
|
60
60
|
$lobstah:man take the helm for this repo
|
|
61
61
|
$lobstah:trap work as a trap in this linked worktree
|
|
62
|
+
$lobstah:stow sign this trap off
|
|
63
|
+
$lobstah:relieve step down from the helm
|
|
62
64
|
```
|
|
63
65
|
|
|
66
|
+
The plugin has these four skills and no commands; the Claude Code plugin has
|
|
67
|
+
the same four.
|
|
68
|
+
|
|
64
69
|
The skill instructions lead to `lobstah man helm` and `lobstah soak`,
|
|
65
70
|
respectively. This verification stopped before running either sign-on
|
|
66
71
|
command. The desktop task's supplied skill catalog names
|
package/docs/man.md
CHANGED
|
@@ -229,7 +229,7 @@ of the stack are recorded too:
|
|
|
229
229
|
|
|
230
230
|
The report prints `prs` with every PR it recorded. `lobstah catch` and
|
|
231
231
|
`lobstah status <id>` list each PR (`prs`), `man tend` lists each with its
|
|
232
|
-
state in the story's `pr` column, and the glass
|
|
232
|
+
state in the story's `pr` column, and the glass lists each in stack order with its state. Each
|
|
233
233
|
PR has its own record, so `pr:*` attention and the merged/closed notice are
|
|
234
234
|
per PR. `[limits].releaseOnMerge` keeps the worktree while another PR of
|
|
235
235
|
the dispatch is open. What you get depends on what runs:
|
|
@@ -344,9 +344,11 @@ and the glass show the repair's PR, chore lane, worker, and trap wait.
|
|
|
344
344
|
PR's watch. `lobstah watch hold <key> [--for <id>]`
|
|
345
345
|
holds one PR's watch; with `--for`, the hold ends when that dispatch
|
|
346
346
|
ends. `lobstah watch release <key>` ends any hold.
|
|
347
|
+
- A repair of a PR below this one in the same stack is queued or running
|
|
348
|
+
(`heldBy: stack`): that repair also updates this PR.
|
|
347
349
|
|
|
348
350
|
A waiting repair is recorded on the PR record as `repair.status: waiting`,
|
|
349
|
-
with `heldBy` (`wt:<trap>`, `dispatch:<id8>`, `helm`, `hold`, `settle`,
|
|
351
|
+
with `heldBy` (`wt:<trap>`, `dispatch:<id8>`, `helm`, `hold`, `settle`, `stack`,
|
|
350
352
|
`checks`, `human-gate`, or `repaired`) and `reason`. `repaired` means each
|
|
351
353
|
failing check already had its round at this head; a new commit ends it.
|
|
352
354
|
Unlike the other waits, `repaired` also raises `pr:checks` attention with
|
|
@@ -378,7 +380,25 @@ base is another PR's branch) rebases onto that base and pushes with
|
|
|
378
380
|
`--force-with-lease`; on a rejection it rebases onto the moved head again
|
|
379
381
|
and pushes with a lease on the head just fetched. A checks or review
|
|
380
382
|
repair follows the rebase rule on a rejection. Each retries at most three
|
|
381
|
-
times.
|
|
383
|
+
times.
|
|
384
|
+
|
|
385
|
+
**A repair updates the PRs stacked on it.** When open PRs are stacked on
|
|
386
|
+
the repaired PR (lobstah's PR records show PRs whose base is its head
|
|
387
|
+
branch, and theirs), the brief names each one, in order, and the push rule
|
|
388
|
+
allows their branches. After pushing the repaired PR, the worker brings
|
|
389
|
+
each one up to date with its base:
|
|
390
|
+
|
|
391
|
+
- After a merge-based repair (a standalone PR, or any checks or review
|
|
392
|
+
repair), it merges the updated base into each one and pushes normally,
|
|
393
|
+
never with force.
|
|
394
|
+
- After a rebase-based repair (a conflict repair of a stacked PR), it
|
|
395
|
+
rebases each one onto its updated base and pushes with
|
|
396
|
+
`--force-with-lease`, with the same retries.
|
|
397
|
+
|
|
398
|
+
An update that conflicts is aborted and left as it was, with the PRs above
|
|
399
|
+
it. lobstah repairs that PR as its own conflict repair, under the same
|
|
400
|
+
rules (`repairSettleSecs`, the attempt limit, and the no-progress breaker).
|
|
401
|
+
The list stops below a PR that a live worker holds. A push hook that fails with a real test or
|
|
382
402
|
type error is not retried: the worker fixes the error and pushes again.
|
|
383
403
|
For code already on main, the repair keeps main's version and only this
|
|
384
404
|
PR's own changes. It does not change behavior. If a conflict resolution
|
|
@@ -675,9 +695,21 @@ watcher that is still starting, so arming and ending the turn at once is safe.
|
|
|
675
695
|
wait for attention itself. Without a Stop hook, use the
|
|
676
696
|
[foreground loop below](#without-a-plugin).
|
|
677
697
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
698
|
+
Each plugin hook runs one CLI command, `lobstah hook <event>`:
|
|
699
|
+
|
|
700
|
+
| Hook | Command | What it does |
|
|
701
|
+
| ---- | ------- | ------------ |
|
|
702
|
+
| SessionStart | `lobstah hook session-start` | Announces the session id and a one-line fleet state; for the helm, its charter; for a trap, its worktree. A session with neither role gets its id and the sign-on commands. |
|
|
703
|
+
| Stop | `lobstah hook stop` | Parks the helm or a trap: blocks with standing attention, asks for a watcher in arm mode, or waits in the hook with `--park` (`--timeout <secs>`). Inert for a session with neither role. |
|
|
704
|
+
| PostToolUse | `lobstah hook post-tool-use` | A trap's beat: liveness, activity, pushes, and its PR. Inert for a session with neither role. |
|
|
705
|
+
| SessionEnd | `lobstah hook session-end` | Signs a trap off and keeps its worktree. Inert for a session with neither role. |
|
|
706
|
+
|
|
707
|
+
Each command detects the session's role: the helm (a helm registration, or a
|
|
708
|
+
session opted in with `LOBSTAH_MAN=1` or `.lobstah-man`), a trap (a soaking
|
|
709
|
+
registration), or neither. The older commands stay as aliases and do the same:
|
|
710
|
+
`lobstah man brief`, `lobstah man haul`, `lobstah soak beat`, and
|
|
711
|
+
`lobstah stow --quiet`. Install the Stop hook from the project you'll run the
|
|
712
|
+
lobstah man in:
|
|
681
713
|
|
|
682
714
|
```bash
|
|
683
715
|
# Easiest: the plugin ships the hooks + the man and trap skills, no settings
|
|
@@ -694,10 +726,26 @@ Idempotent, and it only appends to `hooks.Stop` — existing hooks and settings
|
|
|
694
726
|
are preserved verbatim. What it writes:
|
|
695
727
|
|
|
696
728
|
```json
|
|
697
|
-
{ "hooks": { "Stop": [{ "hooks": [{ "type": "command", "command": "lobstah
|
|
729
|
+
{ "hooks": { "Stop": [{ "hooks": [{ "type": "command", "command": "lobstah hook stop", "timeout": 14400 }] }] } }
|
|
698
730
|
```
|
|
699
731
|
|
|
700
|
-
|
|
732
|
+
**A trap's park renews.** When a trap parks in the Stop hook (park mode:
|
|
733
|
+
`--park`, `[helm].park = "block"`, or the default for a Codex trap) and the
|
|
734
|
+
park reaches its `--timeout` with nothing to act on, the hook blocks the stop
|
|
735
|
+
with one instruction: park again, by ending the turn. The next Stop hook
|
|
736
|
+
parks again. Both harnesses take this as a Stop hook's
|
|
737
|
+
`{"decision":"block","reason":…}` output. The hook stops renewing:
|
|
738
|
+
|
|
739
|
+
- when the trap is stowed or its registration is gone (silently);
|
|
740
|
+
- when the daemon is unreachable (no heartbeat in the last 90 s);
|
|
741
|
+
- after 3 parks in a row that each returned within 5 seconds.
|
|
742
|
+
|
|
743
|
+
In the last two cases it prints a `systemMessage` that says why and how to
|
|
744
|
+
park again. A trap in arm mode (the Claude Code default) needs no renewal:
|
|
745
|
+
its background `soak --wait` is re-armed at each turn end.
|
|
746
|
+
|
|
747
|
+
A Stop hook that runs `lobstah man haul` already counts as installed.
|
|
748
|
+
The Stop hook applies to a signed-on helm or trap, or a session opted in with
|
|
701
749
|
`LOBSTAH_MAN=1` or `.lobstah-man`. Queued dispatches count as work in flight.
|
|
702
750
|
|
|
703
751
|
**Delivery guarantee.** Attention wakes are at-least-once with backoff. An
|
|
@@ -851,8 +899,9 @@ lobstah soak --wait # hookless sessions: listen in the foreground
|
|
|
851
899
|
lobstah soak --link <url> # store this session's link for the glass's ↗ open button
|
|
852
900
|
lobstah focus <trap> # focus a live trap from the CLI
|
|
853
901
|
lobstah stow # sign off; an open catch requeues, unread
|
|
854
|
-
# messages
|
|
855
|
-
#
|
|
902
|
+
# messages wait [soak].signOffGraceSecs for a
|
|
903
|
+
# re-soak, then bounce back to the helm;
|
|
904
|
+
# removes the worktree when soak created it
|
|
856
905
|
lobstah stow --keep # sign off and keep the worktree
|
|
857
906
|
lobstah soak --name amber-gull # choose or change this trap's two-word name
|
|
858
907
|
```
|
|
@@ -914,6 +963,29 @@ upstream: on some remote branch);
|
|
|
914
963
|
otherwise it prints `branchKept: <branch> (<reason>)`. A deleted branch
|
|
915
964
|
prints as `branchDeleted:`. `stow --wt <id>` follows the same rules. The
|
|
916
965
|
SessionEnd hook (`lobstah stow --quiet`) signs off and keeps the worktree.
|
|
966
|
+
|
|
967
|
+
**Only the trap's own session signs it off from a hook.** `lobstah hook
|
|
968
|
+
session-end` (and `stow --quiet`) stows a trap only when the ending session
|
|
969
|
+
is the one registered as the trap. Another session in the same worktree (a
|
|
970
|
+
second window, a child session) ending leaves the trap signed on. The
|
|
971
|
+
PostToolUse and SessionStart hooks never beat or adopt a trap for a session
|
|
972
|
+
that is not the trap's.
|
|
973
|
+
|
|
974
|
+
**A sign-off holds the address for a while.** A session that restarts or
|
|
975
|
+
compacts can end (SessionEnd stows it) and sign on again a minute later.
|
|
976
|
+
SessionEnd cannot tell that from an exit: Claude Code's `reason` is `clear`,
|
|
977
|
+
`resume`, `logout`, `prompt_input_exit`, or `other`, and Codex's is always
|
|
978
|
+
`other`. So for `[soak].signOffGraceSecs` (default 600) after any sign-off:
|
|
979
|
+
|
|
980
|
+
- unread messages stay in the trap's inbox, and `send wt:<trap>` queues
|
|
981
|
+
more with a warning instead of refusing;
|
|
982
|
+
- work addressed to the trap raises no `bait-orphaned` notice;
|
|
983
|
+
- a re-soak of the same worktree releases the hold, and the messages deliver
|
|
984
|
+
at its next park.
|
|
985
|
+
|
|
986
|
+
After the grace, the daemon bounces the held messages to the helm
|
|
987
|
+
(`message-bounced`), and `bait-orphaned` is raised as before. A stow that
|
|
988
|
+
removes the worktree, or a grace of `0`, bounces at once.
|
|
917
989
|
Releasing a claim whose last report is `done` or `failed` moves it to `done/`;
|
|
918
990
|
only an unfinished catch requeues (a cancelled catch finalizes as failed).
|
|
919
991
|
|
|
@@ -1011,7 +1083,7 @@ The output holds a one-time ticket and the command that starts the session in
|
|
|
1011
1083
|
the repo's primary checkout:
|
|
1012
1084
|
|
|
1013
1085
|
```bash
|
|
1014
|
-
cd <repo> && CLAUDE_CODE_DISABLE_TERMINAL_TITLE=1 claude "/lobstah:soak --ticket <ticket>"
|
|
1086
|
+
cd <repo> && CLAUDE_CODE_DISABLE_TERMINAL_TITLE=1 claude "/lobstah:trap soak --ticket <ticket>"
|
|
1015
1087
|
cd <repo> && codex '$lobstah:trap soak --ticket <ticket>'
|
|
1016
1088
|
```
|
|
1017
1089
|
|
|
@@ -1065,6 +1137,28 @@ redeemed or withdrawn. The glass sends it only to a page on this machine's
|
|
|
1065
1137
|
own glass address, only on the starting card, and no notice or log carries
|
|
1066
1138
|
it.
|
|
1067
1139
|
|
|
1140
|
+
### The sign-on title
|
|
1141
|
+
|
|
1142
|
+
Sign-on asks the session to apply a title: `lobstah soak` prints
|
|
1143
|
+
`step: Apply this title: <title>` (the trap's name). The session applies it
|
|
1144
|
+
with its harness's title tool when there is one (`set_session_title` in the
|
|
1145
|
+
Claude desktop Code tab, `set_thread_title` in the Codex app) and skips that
|
|
1146
|
+
part when it has none. Then it runs `lobstah soak title-set` (`--session
|
|
1147
|
+
<id>` from outside the worktree), which prints `signOn: complete`.
|
|
1148
|
+
`lobstah trap title-set` does the same, but Claude Code's worktree isolation
|
|
1149
|
+
refuses any command line with the word `trap` in it (it reads it as the
|
|
1150
|
+
shell builtin), so a trap runs `soak title-set`.
|
|
1151
|
+
|
|
1152
|
+
Until then, sign-on is not complete. The SessionStart hook adds the
|
|
1153
|
+
reminder to the session brief, and the Stop hook blocks the stop once per
|
|
1154
|
+
turn with it: `lobstah: sign-on is not complete. Apply this title: <title>
|
|
1155
|
+
(skip this if you have no tool that sets the session title). Then run
|
|
1156
|
+
\`lobstah soak title-set\`.` A turn the Stop hook already continued
|
|
1157
|
+
(`stop_hook_active`) is not reminded again. After three reminders (the
|
|
1158
|
+
third says it is the last), both hooks stop asking; `soak title-set` still
|
|
1159
|
+
completes sign-on later. A session that signs on again keeps a title it
|
|
1160
|
+
confirmed.
|
|
1161
|
+
|
|
1068
1162
|
### The terminal tab name
|
|
1069
1163
|
|
|
1070
1164
|
At sign-on, soak names the session's terminal tab after the trap. It finds the
|
package/docs/vocabulary.md
CHANGED
|
@@ -298,6 +298,41 @@ session id's UUID version (v7 codex, v4 claude), else the descriptor.
|
|
|
298
298
|
| `fail` | Broken configuration or missing requirement — fix before relying on lobstah. |
|
|
299
299
|
| `skip` | The check does not apply on this host (e.g. no harness plugin installed). Never fails the run. |
|
|
300
300
|
|
|
301
|
+
### Hook readiness
|
|
302
|
+
|
|
303
|
+
Parks and wakes run through the plugin's hooks, so doctor checks them. It
|
|
304
|
+
prints one `hooks claude` and one `hooks codex` row for each harness whose
|
|
305
|
+
lobstah plugin is installed (`skip` otherwise). The row starts with the
|
|
306
|
+
readiness of each role: the helm needs `Stop` and `SessionStart`; a trap
|
|
307
|
+
needs all four (`helm ready, trap not ready (needs PostToolUse)`). Then, for
|
|
308
|
+
each hook (`Stop`, `SessionStart`, `PostToolUse`, `SessionEnd`), it says:
|
|
309
|
+
|
|
310
|
+
- **installed**: the plugin's `hooks/hooks.json` declares it (`missing`
|
|
311
|
+
otherwise), and whether the harness has it turned off (`installed but
|
|
312
|
+
disabled`);
|
|
313
|
+
- **trusted**: Codex runs a plugin hook only after the user trusts it in
|
|
314
|
+
`/hooks`, and records trust in `config.toml` as
|
|
315
|
+
`[hooks.state."lobstah@lobstah:hooks/hooks.json:<event>:<group>:<handler>"]`
|
|
316
|
+
with a `trusted_hash`, a hash of the hook's definition. The row says
|
|
317
|
+
`trusted` when that hash matches the hook as the plugin declares it now,
|
|
318
|
+
`changed since trusted` when the hook changed after it was trusted (a
|
|
319
|
+
plugin update; Codex asks again), and `untrusted` without an entry. Claude
|
|
320
|
+
Code has no per-hook trust: `trust n/a`;
|
|
321
|
+
- **last run**: when lobstah last saw the hook run (`never run` when it has
|
|
322
|
+
not). Each hook run stamps `~/.lobstah/hook-runs.json` (`<harness>:<event>`
|
|
323
|
+
→ time; a hook that runs on every tool call stamps at most every 30 s).
|
|
324
|
+
|
|
325
|
+
A hook that is missing, untrusted, or off makes the row `fail`, and the row
|
|
326
|
+
ends with the step to take, for example `In Codex, open /hooks and trust
|
|
327
|
+
lobstah's Stop and SessionStart hooks.` Doctor reads the harness config and
|
|
328
|
+
never writes it.
|
|
329
|
+
|
|
330
|
+
A `listeners` row follows: for each signed-on helm, whether it listens (a
|
|
331
|
+
live `man wait`, or a park in the Stop hook), and for each trap whether it is
|
|
332
|
+
`parked`, `working a catch`, or `not listening`. A helm or trap that is not
|
|
333
|
+
listening makes the row `warn`: no automatic wake reaches it until it parks
|
|
334
|
+
again.
|
|
335
|
+
|
|
301
336
|
## Watch contract
|
|
302
337
|
|
|
303
338
|
A **watch** is a standing outbound poll on something external (a ume review
|
|
@@ -354,7 +389,7 @@ evidence).
|
|
|
354
389
|
| `draft` | Draft flipped (`value`). Evidence only. |
|
|
355
390
|
| `merged` / `closed` | Terminal; the check sets `done`, and the watch retires once delivered. Emitted only on an open → terminal change, never on the first observation. |
|
|
356
391
|
| evidence `pr` | `{ url, number, state, draft, reviewDecision, mergeStateStatus, headSha, checks: { total, passed, failed, pending, unknown? }, review: { unresolvedThreads, changesRequested, lastReviewAt }, observedAt }`. Check counts use only the latest run per check name and app/workflow. `CANCELLED` and `STALE` latest runs are unknown, not failed. The PR record also stores repair status, attempts, and reason. `prBadge` shows `repairing: conflict (attempt 1 of 2)` while a repair is in flight, and ends in `repair waits: <heldBy>` while a repair waits; tend, `catch`, and the glass share the badge. |
|
|
357
|
-
| waiting repair | `repair.status: waiting` on a PR record: a repair is due but is not queued. `heldBy` names the holder: `wt:<trap>` or `dispatch:<id8>` (a live worker holds the PR's head branch or the head branch of a PR below it in the stack), `helm` (the helm cancelled a repair of this PR), `hold` or `dispatch:<id8>` (`watch hold`), `settle` (the head, base head, or failing checks changed less than `[watch].repairSettleSecs` ago, or, for a conflict repair, the PR was approved less than that ago; `until` says when), or `checks` (the latest run of a failing check is in progress or passed). `reason` says what holds it. A wait is not an attempt and raises no attention item. |
|
|
392
|
+
| waiting repair | `repair.status: waiting` on a PR record: a repair is due but is not queued. `heldBy` names the holder: `wt:<trap>` or `dispatch:<id8>` (a live worker holds the PR's head branch or the head branch of a PR below it in the stack), `helm` (the helm cancelled a repair of this PR), `stack` (a repair of a PR below it in the stack is queued or running, and updates this PR too), `hold` or `dispatch:<id8>` (`watch hold`), `settle` (the head, base head, or failing checks changed less than `[watch].repairSettleSecs` ago, or, for a conflict repair, the PR was approved less than that ago; `until` says when), or `checks` (the latest run of a failing check is in progress or passed). `reason` says what holds it. A wait is not an attempt and raises no attention item. |
|
|
358
393
|
| repair chore | A daemon repair or rebase runs in the `chore` lane under `[limits].choreConcurrent`. A repair chore for a trap-built PR waits for its live owning trap up to `[watch].repairTrapWaitSecs` (default 600), then runs headless in its own PR-branch checkout. A headless-built PR's repair runs headless in its origin worktree when safe. No headless chore uses a trap's worktree. This bounded fallback applies only to system repair chores; a person's addressed work is sticky and never falls back. Tend, daemon status, doctor, and the glass show the PR, lane, worker, and trap wait. |
|
|
359
394
|
| live worker | An active headless dispatch, or a trap with an open catch. It holds a branch that its worktree has checked out, that its current branch tracks, or that it pushed during its current dispatch (evidence `pushes`). It holds a PR that its evidence names or that its chain owns. |
|
|
360
395
|
| evidence `pushes` | `[{ branch, at }]`: the branches a dispatch pushed, as lobstah saw them. The runner records its own pushes; the runner records a headless worker's `git push` from the harness event stream (branch names only); `soak beat` records a trap's `git push`. |
|
|
@@ -431,11 +466,14 @@ worktree is refused (the session lock); a stale one is adopted.
|
|
|
431
466
|
| Word | Meaning |
|
|
432
467
|
| ---- | ------- |
|
|
433
468
|
| `soak` | Sign the worktree's trap on and take matching work. From a primary checkout, or with `--repo <key>` from outside any configured repo, it first creates a linked worktree like a dispatch does (fetch trunk, new branch from `origin/<trunk>`, `setup` commands), prints `worktree:`, `created: true`, `branch:`, and, when the session is elsewhere, `instruction: cd <path> ...`; the session works in that directory. If creation fails, nothing is signed on and the partial worktree and branch are removed. `--repo` in a linked worktree must match its repo. A session that already mans a trap re-uses it (resolved from the session id) and never gets a second worktree. `--session` is needed only on first sign-on. `--one` stows after the first catch. `--wait` registers a watcher and waits for work; a quiet timeout exits 3. Workers never run `man` verbs. |
|
|
434
|
-
| `
|
|
469
|
+
| `soak title-set` (alias `trap title-set`) | Confirm this session applied the title sign-on printed (`step: Apply this title: <title>`). The registration records `titleSet` and `titleSetAt` and drops `titlePending`; sign-on is complete. Until then, SessionStart and Stop remind the session once per turn, at most three times. A trap runs `soak title-set`: Claude Code's worktree isolation refuses a command line containing `trap`. |
|
|
470
|
+
| `stow` | Sign the trap off (in the worktree, or elsewhere by session id: `--session <id>` or `$CLAUDE_CODE_SESSION_ID`); an unfinished catch requeues, done/failed finalizes in `done/`, and a cancelled catch finalizes as failed. Unread messages are held for `[soak].signOffGraceSecs` (default 600): they deliver if the same worktree re-soaks in time, else the daemon bounces them to the helm (at once when stow removed the worktree or the grace is `0`). A trap always stows itself freely; stowing someone else's (`--wt`) is steering — the claimed helm's alone. Stow closes the seat, never the session: an opted-in session can only be asked to stop, and a still-looping worker re-enlists visibly (`trap-signed-on`). By default stow removes the worktree when soak created it (`createdWorktree: true`) and prints `worktree: removed`, `path:`, and `returnTo:`; `--keep` leaves it. It keeps the worktree (`worktree: kept`, `reason:`) when soak did not create it, or when it holds uncommitted changes, untracked files that are not ignored, commits absent from its upstream, or no upstream. `--force` explicitly allows discarding unsaved checkout files; `--keep` still keeps it. The branch is deleted (`branchDeleted:`) only when all its commits are on its upstream (with no upstream: on some remote branch); otherwise `branchKept: <branch> (<reason>)`. `--wt` follows the same rules. The SessionEnd hook (`stow --quiet`) signs off only when the ending session is the trap's registered session (another session in the worktree ending is a no-op) and keeps the worktree; SessionEnd cannot tell a restart or compaction from an exit (Claude Code's `reason` is `clear`, `resume`, `logout`, `prompt_input_exit`, or `other`; Codex's is always `other`), so the grace covers a session that signs on again. |
|
|
471
|
+
| sign-off grace | For `[soak].signOffGraceSecs` after a trap signs off (`~/.lobstah/signed-off/<trap>.json`), its address is held: `send wt:<trap>` queues and warns instead of refusing, held messages wait, and work addressed to it raises no `bait-orphaned` notice. A re-soak of the same worktree releases the hold; after the grace, the daemon bounces the held messages (`message-bounced`). |
|
|
435
472
|
| address | `--for wt:<trap>` targets one trap; `session:<id>` is an alias resolved to the trap at dispatch time. **Sticky:** addressed work is never the daemon's — it waits for its trap; an orphan (trap gone) surfaces as a `bait-orphaned` notice for the helm to re-address, release, or cancel. Delivery stamps a receipt (`deliveredTo`/`deliveredAt`) into evidence. Unaddressed work defers to a parked matching trap for `[soak].deferSecs`, then the daemon spawns headless. |
|
|
436
473
|
| message | `send wt:<trap> "<text>"` — a conversational continuation, not work: no branch, no catch, no report obligation. Delivered before bait at the trap's next park, stamped with its sender (`helm` / `session:<id>` / `terminal`); undeliverable messages bounce to the helm as notices. |
|
|
437
474
|
| catch | The active dispatch a trap claimed (`claim.json`, `by: wt:<id>`). One catch per trap; one active item per worktree. The daemon never spawns or restarts it — the session's reports are its liveness. |
|
|
438
|
-
|
|
|
475
|
+
| hook command | `lobstah hook session-start\|stop\|post-tool-use\|session-end`: the command each plugin hook runs. Each detects the session's role (helm, trap, or neither) and runs what `man brief`, `man haul`, `soak beat`, and `stow --quiet` run; those stay as aliases. Stop, PostToolUse, and SessionEnd are inert for a session with neither role; SessionStart announces the session id to every session. |
|
|
476
|
+
| beat | `lobstah soak beat` (`lobstah hook post-tool-use`), run by the plugin's post-tool hook after every tool call. It resolves the trap from the working directory, else from the session id (files only: no git, no network), refreshes the trap's beat (`soaking/<trap>.beat`, separate from the registration), and writes the claimed catch's [activity](#activity). Throttled to one per 30 seconds per trap. Inert in a session that is not soaking, or with `[soak].beat = false`. Always exits 0; errors go to `logs/beat.log`. |
|
|
439
477
|
| ghost trap | A registration whose heartbeat **and** beat lapsed past `[soak].ttlSecs` **after having parked at least once**. The sweep removes the registration, requeues only unfinished catches, finalizes done/failed in `done/`, and posts a `trap-ghosted` notice. It removes a soak-created checkout only when clean and pushed to its upstream; dirty, unpushed, no-upstream, or unreadable checkouts stay, protected from later culls too. The notice includes path, branch, modified-file and unpushed-commit counts, or `unknown` when unavailable. A kept anchor preserves the address on re-soaking. A fresh report or beat keeps a working session out of the sweep; a fresh beat also holds the session lock. A catch whose last report is `paused` keeps its trap until the pause expires ([Waiting on](#waiting-on)). A daemon tick gap longer than the soak TTL starts one full TTL of resume grace before any ghost sweep. |
|
|
440
478
|
| defective enlistment | A stale registration that **never parked** — signed on but never listened (usually no Stop hook). Not swept: the helm gets a `trap-defective` notice with the remedy (`soak --wait`), and the registration stays so the address keeps protecting its work. |
|
|
441
479
|
| reserved trap | A trap `lobstah trap reserve` created before any session signs on as it: a name, a `wt:` id, and a one-time ticket, shown as `starting`. Work addressed to it waits. A session redeems the ticket with `soak --ticket` (or `LOBSTAH_TRAP_TICKET`) and signs on as that trap. Unredeemed past its deadline, it fails with a `trap-start-failed` notice and its work stays queued. `stow --wt <name>` withdraws it. |
|