@assuredloop/orca-bot-kit 0.27.0 → 0.29.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 +55 -15
- package/SETUP.md +30 -14
- package/package.json +1 -1
- package/rules/mail.md +3 -3
- package/skills/obk-bot-building/SKILL.md +29 -16
- package/skills/obk-grooming/SKILL.md +4 -3
- package/src/book.js +91 -10
- package/src/bot.js +246 -118
- package/src/clear.js +7 -3
- package/src/cli.js +116 -36
- package/src/commands.js +23 -11
- package/src/conversations.js +73 -3
- package/src/groom.js +27 -9
- package/src/health.js +89 -8
- package/src/hooks.js +44 -2
- package/src/init.js +36 -29
- package/src/launch.js +202 -18
- package/src/message.js +42 -2
- package/src/orca-reload.applescript +21 -22
- package/src/orca-runtime.cjs +1 -1
- package/src/orca.js +47 -14
- package/src/permissions.js +80 -23
- package/src/pi.js +55 -0
- package/src/rules.js +18 -4
- package/src/run.js +155 -0
- package/src/skills.js +50 -34
- package/src/sources.js +73 -4
- package/src/temp.js +65 -20
- package/src/up.js +72 -15
package/README.md
CHANGED
|
@@ -141,19 +141,41 @@ words, `dangerously-skip`
|
|
|
141
141
|
([ADR 0015](docs/adr/0015-three-approval-levels-auto-by-default.md)). Only
|
|
142
142
|
`obk permission approval` changes it, after your yes: `session add` and
|
|
143
143
|
`session change` take no approval, and refuse an extra argument that sets one
|
|
144
|
-
([ADR
|
|
144
|
+
([ADR 0051](docs/adr/0051-permission-changes-and-risky-operations-have-commands-of-their-own.md))
|
|
145
|
+
or that widens what the session may write or run, such as `--add-dir`, Claude
|
|
146
|
+
Code's `--settings` or Codex's `--profile`. On Codex, `-c` passes only for a
|
|
147
|
+
short list of keys known to be safe, such as `model_reasoning_effort`
|
|
148
|
+
([ADR 0049](docs/adr/0049-extra-arguments-that-widen-a-session-are-refused.md)).
|
|
149
|
+
A Codex session's work dir stays in its bot folder: Codex may write in its work
|
|
150
|
+
dir, so `session add` and `session change` refuse one outside the bot folder for
|
|
151
|
+
a Codex session
|
|
152
|
+
([ADR 0045](docs/adr/0045-a-codex-sessions-work-dir-stays-in-its-bot-folder.md)).
|
|
153
|
+
|
|
154
|
+
A bot or a session can also run on [Pi](https://pi.dev) (`--harness pi`, tested
|
|
155
|
+
with Pi 1.1.0). Pi asks before no tool call, so a Pi session is always
|
|
156
|
+
`dangerously-skip`, and the kit refuses `auto` and `ask` for it. Sign in to Pi
|
|
157
|
+
yourself first: `obk up` asks Pi which models it can use, and starts nothing
|
|
158
|
+
when Pi has none, or not the one the session names. The kit gives a Pi session
|
|
159
|
+
its id, so `obk up` resumes it after its tab is closed. Pi has no context
|
|
160
|
+
setting: name a model that carries it. Mail, temporary sessions, skills and
|
|
161
|
+
health for a Pi session come later, and so do `obk init --harness pi` and
|
|
162
|
+
`obk session clear` and `compact` on Pi.
|
|
145
163
|
|
|
146
164
|
Every bot gets the kit's default permission rules, written by the kit with
|
|
147
165
|
nobody asked: its own commands for this bots folder, reading a long message,
|
|
148
|
-
`git add` and `git commit
|
|
149
|
-
([ADR
|
|
150
|
-
`obk permission`, `obk retire`, `obk pause`, `obk restart`, `obk bot create
|
|
151
|
-
|
|
152
|
-
|
|
166
|
+
`git add` and `git commit`
|
|
167
|
+
([ADR 0050](docs/adr/0050-the-kit-writes-every-bots-default-permission-rules.md)).
|
|
168
|
+
`obk permission`, `obk retire`, `obk pause`, `obk restart`, `obk bot create`,
|
|
169
|
+
`obk bot change`, `obk session add`, `obk session change`, `obk init` and
|
|
170
|
+
`obk run` are not in the set, and keep your yes
|
|
171
|
+
([ADR 0051](docs/adr/0051-permission-changes-and-risky-operations-have-commands-of-their-own.md)).
|
|
153
172
|
A rule beyond the set is written by `obk permission allow`, after your yes to
|
|
154
|
-
the exact rule. A bot that still holds the
|
|
155
|
-
|
|
156
|
-
|
|
173
|
+
the exact rule. A bot that still holds a rule that the kit gave every bot
|
|
174
|
+
before and that is no longer in the set keeps it: the rule of `obk restart`,
|
|
175
|
+
`obk bot create`, `obk bot change`, `obk session add`, `obk session change`,
|
|
176
|
+
`obk session mail` or `obk session nudge`, or of Orca's
|
|
177
|
+
`orca orchestration check --run`. `obk rules build` and `obk up` name it, with
|
|
178
|
+
the `obk permission disallow` command that takes it back.
|
|
157
179
|
|
|
158
180
|
Neither command touches Orca. `obk up` is what makes it all real there: an Orca
|
|
159
181
|
project per bot, a tab per session, each started at the bot home with its own
|
|
@@ -451,6 +473,7 @@ obk permission disallow --bots /path/to/my-bots --bot api-bot --rule 'Bash(gh pr
|
|
|
451
473
|
obk permission approval --bots /path/to/my-bots --bot api-bot --session daily --approval ask
|
|
452
474
|
obk permission approval --bots /path/to/my-bots --bot api-bot --temps --approval dangerously-skip
|
|
453
475
|
obk session change --bots /path/to/my-bots --bot api-bot --session daily --model opus
|
|
476
|
+
obk session change --bots /path/to/my-bots --bot bot-father --session daily --harness codex
|
|
454
477
|
obk skills remove --bots /path/to/my-bots --bot api-bot --skill kit:obk-tdd
|
|
455
478
|
obk pause --bots /path/to/my-bots --bot api-bot [--session daily]
|
|
456
479
|
obk unpause --bots /path/to/my-bots --bot api-bot [--session daily]
|
|
@@ -458,8 +481,11 @@ obk retire --bots /path/to/my-bots --bot api-bot [--session daily]
|
|
|
458
481
|
```
|
|
459
482
|
|
|
460
483
|
A change to a charter or a setting is written at once, and a running session
|
|
461
|
-
takes it when it next starts. A session
|
|
462
|
-
|
|
484
|
+
takes it when it next starts. A session can move to the other harness with
|
|
485
|
+
`--harness`, Bot Father's management session too: it then starts a new
|
|
486
|
+
conversation there, the book keeps the old one in its history, and its model,
|
|
487
|
+
effort and context go back to the new harness's defaults unless you give them
|
|
488
|
+
too. A pause closes the tabs the way a restart does, with the
|
|
463
489
|
same refusals, and one more: it also refuses a tab with only its shell in front
|
|
464
490
|
whose conversation the book does not name. `obk up` leaves what is paused
|
|
465
491
|
closed until `obk unpause` brings it back with its conversations. Retiring a
|
|
@@ -514,7 +540,9 @@ obk groom --bots /path/to/my-bots --on --at 04:00 --run-on codex --model gpt-6-s
|
|
|
514
540
|
Each fire then makes a temporary Codex session of the grooming session's, named
|
|
515
541
|
`groom-<date and time>`, which does the grooming, mails its report back, and is
|
|
516
542
|
retired; the grooming session sends the report on to the management session. A
|
|
517
|
-
run an earlier fire left behind is retired at the next one.
|
|
543
|
+
run an earlier fire left behind is retired at the next one. Each run also
|
|
544
|
+
carries the grooming session's own prompt, as `--on` finds it: after you change
|
|
545
|
+
that prompt, run `obk groom --at <HH:MM>` alone, which keeps the runs on Codex.
|
|
518
546
|
|
|
519
547
|
## When something is wrong
|
|
520
548
|
|
|
@@ -549,6 +577,13 @@ What it looks for:
|
|
|
549
577
|
`<bot>.<session>` every fleet's sessions had before 0.10.0, which a session of
|
|
550
578
|
another fleet can write to. Its next start through the kit gives it one of its
|
|
551
579
|
own, so the finding names the `obk restart … --session` that does it;
|
|
580
|
+
- **a session whose mail waits and Orca has not told it**: mail older than an
|
|
581
|
+
hour that is unread and that Orca's notice never reached, while the session's
|
|
582
|
+
harness is idle now. Orca can stop telling a session that sat idle through an
|
|
583
|
+
Orca restart or update, so the finding names the `obk restart … --session`
|
|
584
|
+
that opens a new tab, which Orca tells. It counts the letters and gives the
|
|
585
|
+
time of the oldest; it never shows their subject or text. A busy session is
|
|
586
|
+
not named: Orca tells it when its turn ends;
|
|
552
587
|
- **a process still running in a retired session's work dir**: one from before
|
|
553
588
|
retire stopped them, or one it could not stop, with its pid and command;
|
|
554
589
|
- **leftovers no book owns**: an Orca project inside your bots folder with no
|
|
@@ -671,9 +706,14 @@ real Orca and the real harnesses on your own machine. No CI runner can do that,
|
|
|
671
706
|
so these are run by hand before a change that touches Orca or a harness is
|
|
672
707
|
merged. A system test touches only what it creates and removes it afterwards,
|
|
673
708
|
except the mailbox Runs Orca gives no way to delete, which the run names; it
|
|
674
|
-
never closes a tab it did not open.
|
|
675
|
-
`~/.
|
|
676
|
-
|
|
709
|
+
never closes a tab it did not open. Before any test loads, the command copies
|
|
710
|
+
your `~/.claude.json` and `~/.codex/config.toml` into a new
|
|
711
|
+
`obk-config-backup-*` folder in your temp folder, readable by you alone, and
|
|
712
|
+
says where; if a copy fails, no test runs. After the run it says whether each
|
|
713
|
+
file still matches its copy, and never copies one back: that is yours to
|
|
714
|
+
decide. The trust its harnesses write into your `~/.codex/config.toml` and
|
|
715
|
+
`~/.claude.json` for the run's own throwaway folders is taken out again after
|
|
716
|
+
the run, and named; nothing else in them is changed.
|
|
677
717
|
An Orca project the run leaves in one of its own throwaway folders is named,
|
|
678
718
|
with its setup id, and fails the run; the command removes none itself.
|
|
679
719
|
|
package/SETUP.md
CHANGED
|
@@ -38,15 +38,26 @@ git --version
|
|
|
38
38
|
/Applications/Orca.app/Contents/Resources/bin/orca status --json
|
|
39
39
|
claude --version # at least one of these
|
|
40
40
|
codex --version # 0.156.1 or newer
|
|
41
|
+
pi --version # 1.1.0, for a bot on Pi
|
|
41
42
|
```
|
|
42
43
|
|
|
43
44
|
The Orca line must come back with `ok: true` and `result.runtime.reachable:
|
|
44
|
-
true`.
|
|
45
|
-
|
|
45
|
+
true`. When it answers that Orca is not running or not reachable, ask them to
|
|
46
|
+
start it, then run the line again.
|
|
47
|
+
|
|
48
|
+
When the line fails with an error instead of an answer, the error is the
|
|
49
|
+
problem. It does not show that Orca is stopped. Tell them the error as the CLI
|
|
50
|
+
printed it, and do not ask them to start Orca for it. For example, `EPERM` with
|
|
51
|
+
`process.cwd failed` or `uv_cwd` means that the CLI cannot read the folder the
|
|
52
|
+
shell is in, while Orca can be running. Run the line again from their home
|
|
53
|
+
folder.
|
|
46
54
|
|
|
47
55
|
On the Orca CLI path: `/usr/local/bin/orca` is a root-only symlink on some
|
|
48
56
|
machines and fails for an ordinary user, so use the full path above. If Orca
|
|
49
|
-
lives somewhere else here, find it and set `OBK_ORCA` to it.
|
|
57
|
+
lives somewhere else here, find it and set `OBK_ORCA` to it. If that is another
|
|
58
|
+
build of Orca with its own data folder, also set `OBK_ORCA_DATA` to the full
|
|
59
|
+
path of that folder, for example `$HOME/Library/Application Support/orca-lean`.
|
|
60
|
+
The kit reads Orca's launch arguments from the `profiles` folder there.
|
|
50
61
|
|
|
51
62
|
If Node is older than 24.21.0, say so and stop. The kit's book of sessions is a
|
|
52
63
|
SQLite write transaction through Node's own `node:sqlite`, and 24.21.0 is the
|
|
@@ -132,8 +143,9 @@ Read everything it prints. Three parts of it matter to them:
|
|
|
132
143
|
not stop Bot Father for the kit's own commands, reading a long message
|
|
133
144
|
beside the bots folder, and its commits. The kit writes this set for every
|
|
134
145
|
bot, with nobody asked. Say in a sentence what it allows, and that
|
|
135
|
-
`obk permission`, `obk retire`, `obk pause`
|
|
136
|
-
|
|
146
|
+
`obk permission`, `obk retire`, `obk pause`, `obk restart`, `obk bot create`,
|
|
147
|
+
`obk bot change`, `obk session add`, `obk session change`, `obk init` and
|
|
148
|
+
`obk run` are not in it and keep their yes;
|
|
137
149
|
- **anything it says about Orca's own default launch arguments.** Orca adds
|
|
138
150
|
these to every agent it launches, relaunches and resumes. When they carry a
|
|
139
151
|
permission bypass, every session runs in that mode whatever approval level the
|
|
@@ -192,21 +204,22 @@ that folder itself, exactly as you gave it to `obk init`:
|
|
|
192
204
|
"$ORCA" terminal read --terminal <handle> --screen --json
|
|
193
205
|
```
|
|
194
206
|
|
|
195
|
-
What you will see, and the usual answer. Each goes as one
|
|
207
|
+
What you will see, and the usual answer. Each key goes as one
|
|
196
208
|
`"$ORCA" terminal send --terminal <handle> --text …`, with the return inside the
|
|
197
209
|
text and **no** `--enter`, because a menu takes a return as the keypress it is
|
|
198
|
-
waiting for:
|
|
210
|
+
waiting for. Where the Send cell names `obk` commands, send no keys: the kit
|
|
211
|
+
answers that screen, by the command and its arguments below the table.
|
|
199
212
|
|
|
200
213
|
| On screen | Send | Which is | Proven on |
|
|
201
214
|
|---|---|---|---|
|
|
202
215
|
| Claude Code's folder trust list | `\x1b[B\r` | down, then return: its selection starts on **No, exit** | Claude Code 2.1.283 |
|
|
203
216
|
| Codex's directory trust, `1. Trust and continue` (older: `1. Yes, continue`) | `\r` | return: its selection starts on **Trust and continue** | Codex 0.160.0 |
|
|
204
|
-
| Codex's `Hooks need review` |
|
|
217
|
+
| Codex's `Hooks need review` | `obk session trust-hooks`, or `obk temp trust-hooks` for a temporary session | the kit checks the hooks, then goes to **Trust all and continue** | Codex 0.160.0 |
|
|
205
218
|
| Codex's `/new`: `Where should the new conversation run?` | `\r` | return: its selection starts on **Current checkout** (bot home); on 0.162.0 it is called **1. Use current Git worktree** (Keep using the current working directory). Never **Create new Git worktree** | Codex 0.160.0; Codex 0.162.0 (#516) |
|
|
206
219
|
| Codex's update offer, `1. Update now` | `\r` | return: its selection starts on **Update now**, which accepts it | Codex 0.156.1; seen on 0.157.1 (offering 0.158.0) with the selection on **Update now**, not pressed; 0.158.0 offered 0.160.0, taken on 2026-10-02 by Bot Father's restarts |
|
|
207
220
|
| `[oh-my-zsh] Would you like to update?` | `n` | they update their own shell | oh-my-zsh bf77e35 |
|
|
208
|
-
| Claude Code's `Teach auto mode about your environment?` |
|
|
209
|
-
| Claude Code 2.1.289's `Teach auto mode about your environment?`, a numbered list: `1. Yes`, `2. Not now`, `3. Don't show again`, and `Enter to confirm · Esc to cancel`, above the input box |
|
|
221
|
+
| Claude Code's `Teach auto mode about your environment?` | `obk session answer`, or `obk temp answer` for a temporary session | the kit sends Esc, which cancels it (**Not now**). On 2.1.283 and 2.1.302 it is a form, not a list: `How you use Claude here`, `Also scan shell history`, `Also scan your other repos`, `Continue`, and `Enter to continue · Esc to cancel`. On 2.1.283, `Also scan shell history` shows `true` and the foot starts `←/→ to change usage`. On 2.1.302, it shows `false` and the foot starts `←/→ to change`. **Never send a return**: Enter is **Continue**, which starts a scan of the project, recent Claude sessions and, by default, the machine's shell history | Not yet proven: Esc cancels in Claude Code 2.1.283's code, not yet seen live (#370); the form was seen live twice on 2.1.283, in #261's live test and #239's live run 1 (#416); 2.1.278 showed a numbered list, answered with 2. Not now; the 2.1.302 form was seen live once, in #604 |
|
|
222
|
+
| Claude Code 2.1.289's `Teach auto mode about your environment?`, a numbered list: `1. Yes`, `2. Not now`, `3. Don't show again`, and `Enter to confirm · Esc to cancel`, above the input box | `obk session answer`, or `obk temp answer` for a temporary session | the kit goes down, then returns, to **2. Not now**: its selection starts on **1. Yes**. The kit reads the screen after the down arrow, and sends the return only with the selection on **2. Not now**. Never **1. Yes**, which starts the teach scan, and never **3. Don't show again** | Not yet proven: the list was seen live on 2.1.289 with its selection on 1. Yes, in #489's live run 1, and was not answered there |
|
|
210
223
|
|
|
211
224
|
Each key counts on where the selection starts, as it did on the version named.
|
|
212
225
|
Read the screen in front of you before you send: if the selection is somewhere
|
|
@@ -233,8 +246,9 @@ that a narrow permission rule of the kit's default set allows it rather than a
|
|
|
233
246
|
rule to type into any tab. On Codex's hooks review, the maker runs
|
|
234
247
|
`obk temp trust-hooks --bots <their path> --name <session>`. On Claude Code's
|
|
235
248
|
`Teach auto mode` screen, it runs `obk temp answer --bots <their path> --name <session>`.
|
|
236
|
-
That command reads the screen and answers only when it holds one of the
|
|
237
|
-
shapes in the table exactly as the kit knows it: Esc for the 2.1.283
|
|
249
|
+
That command reads the screen and answers only when it holds one of the
|
|
250
|
+
shapes in the table exactly as the kit knows it: Esc for the 2.1.283 and
|
|
251
|
+
2.1.302 forms, and
|
|
238
252
|
**2. Not now** for the 2.1.289 list. On the list it sends the arrows, reads the
|
|
239
253
|
screen again, and sends the return only with the selection on **2. Not now**.
|
|
240
254
|
It refuses any other screen, types nothing then, and prints what it saw.
|
|
@@ -298,8 +312,10 @@ obk health --bots <their path>
|
|
|
298
312
|
|
|
299
313
|
It reads the setup and says what it found, and writes nothing at all. It exits 1
|
|
300
314
|
when it found something, so do not read a non-zero exit as a broken setup. The
|
|
301
|
-
one exception is an error with no findings
|
|
302
|
-
|
|
315
|
+
one exception is an error with no findings: that is a real problem. Tell them
|
|
316
|
+
the error as it is. When it says Orca is not answering, run the Orca line from
|
|
317
|
+
step 1 again, and go by what that gives: start Orca only when it answers that
|
|
318
|
+
Orca is not running.
|
|
303
319
|
|
|
304
320
|
Relay what it says in plain words. Nothing it prints is a verdict: which findings
|
|
305
321
|
matter is for them, or for Bot Father, to judge. If something looks wrong to you,
|
package/package.json
CHANGED
package/rules/mail.md
CHANGED
|
@@ -8,9 +8,9 @@ To reach another session, ask the kit for the road rather than guessing an
|
|
|
8
8
|
address: `"${OBK_CLI:-obk}" message to --bots <bots> --to <bot>/<session>`. It
|
|
9
9
|
answers with the address and the command that carries it.
|
|
10
10
|
|
|
11
|
-
If your harness refuses a message, do not send it again by another road. Tell
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
If your harness refuses a message, do not send it again by another road. Tell your maker or your
|
|
12
|
+
user, with the harness's reason. A refusal can mean a missing permission rule. Send the refusal to
|
|
13
|
+
whoever adds your rules: your maker, your user or your fleet's manager.
|
|
14
14
|
|
|
15
15
|
Mail is queued, not an interruption. When Orca says you have mail, read it
|
|
16
16
|
with `"${OBK_CLI:-obk}" message check --bots <bots>`, never a bare `orca
|
|
@@ -213,10 +213,14 @@ its commits. The kit writes this default set itself, with nobody asked:
|
|
|
213
213
|
does not have yet to its `bot.yaml` and its settings, or for Codex its
|
|
214
214
|
`.codex/rules/obk.rules`, and say which rules they wrote. The set leaves out
|
|
215
215
|
the commands that keep the user's yes: `obk permission`, `obk retire`, `obk
|
|
216
|
-
pause`, `obk restart`, `obk bot create
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
216
|
+
pause`, `obk restart`, `obk bot create`, `obk bot change`, `obk session add`,
|
|
217
|
+
`obk session change`, `obk init` and `obk run`. A bot that still holds a rule that the
|
|
218
|
+
kit gave every bot before and that is no longer in the set (`obk restart`,
|
|
219
|
+
`obk bot create`, `obk bot change`, `obk session add`, `obk session change`,
|
|
220
|
+
`obk session mail`, `obk session nudge`, or Orca's `orca orchestration check
|
|
221
|
+
--run`) keeps it, and `obk rules build` and `obk up` name
|
|
222
|
+
it with the `obk permission disallow` command that takes it back; run that
|
|
223
|
+
command only after the user's yes. Never write a
|
|
220
224
|
permission rule into a settings or rules file by hand.
|
|
221
225
|
|
|
222
226
|
The charter decides what else the bot needs. What it lets the bot do without
|
|
@@ -241,7 +245,11 @@ out; the kit refuses it, and it refuses a rule of its own default set too. Keep
|
|
|
241
245
|
each rule narrow: a program and what it does. The kit refuses a rule that lets
|
|
242
246
|
the bot run any command, a program with any arguments, or a shell or
|
|
243
247
|
interpreter with any arguments; a user who wants one for a bot adds it to that
|
|
244
|
-
bot's settings themselves.
|
|
248
|
+
bot's settings themselves. For a test run with a time limit, propose `obk run`
|
|
249
|
+
with the real command after `--`, such as
|
|
250
|
+
`Bash(<kit> run --timeout 300 -- go test:*)`: the kit refuses a rule for
|
|
251
|
+
`perl -e 'alarm …'`, since perl runs whatever its arguments say. Propose no
|
|
252
|
+
rule for what the charter does not grant.
|
|
245
253
|
|
|
246
254
|
A bot that should learn across its sessions can use its harness's own memory,
|
|
247
255
|
turned on for that bot alone, whatever the user's own setting is for everything
|
|
@@ -284,26 +292,29 @@ sometimes. Read each screen with `<orca> terminal read --terminal <handle>
|
|
|
284
292
|
--screen`, where `<handle>` is the terminal the kit named for the tab and
|
|
285
293
|
`<orca>` is the Orca CLI the kit uses: `$OBK_ORCA` when that is set, and
|
|
286
294
|
otherwise `/Applications/Orca.app/Contents/Resources/bin/orca`. A bare
|
|
287
|
-
`orca` can fail. Send each
|
|
288
|
-
<handle> --text …`, with the return inside the text and no `--enter
|
|
295
|
+
`orca` can fail. Send each key as one `<orca> terminal send --terminal
|
|
296
|
+
<handle> --text …`, with the return inside the text and no `--enter`. Where
|
|
297
|
+
the Send cell names `obk` commands, send no keys: the kit answers that
|
|
298
|
+
screen, by the commands below, the first for a long-lived session and the
|
|
299
|
+
second for a temporary one.
|
|
289
300
|
|
|
290
301
|
| On screen | Send | Which is | Proven on |
|
|
291
302
|
|---|---|---|---|
|
|
292
303
|
| Claude Code's trust list | `\x1b[B\r` | down, return: off **No, exit** | Claude Code 2.1.283 |
|
|
293
304
|
| Codex's `Trust this folder?` | `\r` | return: it starts on yes | Codex 0.160.0 |
|
|
294
|
-
| Codex's
|
|
305
|
+
| Codex's hooks review | `obk session trust-hooks`, `obk temp trust-hooks` | trust | Codex 0.160.0 |
|
|
295
306
|
| Codex's `Where should the new conversation run?` | `\r` | **Current checkout** | Codex 0.160.0 |
|
|
296
307
|
| Codex's update offer, `1. Update now` | `\r` | **Update now**, where it starts | Codex 0.156.1 |
|
|
297
308
|
| `[oh-my-zsh] Would you like to update?` | `n` | no: the user updates it | oh-my-zsh bf77e35 |
|
|
298
|
-
| Claude Code's
|
|
299
|
-
|
|
|
309
|
+
| Claude Code's Teach form | `obk session answer`, `obk temp answer` | Esc | Not yet proven, below |
|
|
310
|
+
| Its 2.1.289 list | `obk session answer`, `obk temp answer` | 2. Not now | Not yet proven, below |
|
|
300
311
|
|
|
301
312
|
Each key counts on where the selection starts on the version named. Read the
|
|
302
313
|
screen before you send: if the selection is elsewhere or the options have
|
|
303
314
|
moved, go to the answer with the arrows, then return. Never a digit and then
|
|
304
315
|
return: on Codex's update offer that took the highlighted **Update now**,
|
|
305
316
|
whatever the digit was. Codex's trust said `1. Yes, continue` on older
|
|
306
|
-
versions.
|
|
317
|
+
versions.
|
|
307
318
|
Its update offer was proven on 0.156.1; 0.157.1 showed it with the selection on
|
|
308
319
|
Update now, not pressed, and 0.158.0 offered 0.160.0, taken on 2026-10-02 by
|
|
309
320
|
Bot Father's restarts.
|
|
@@ -314,8 +325,10 @@ shell history. Esc cancels it (Not now) in its code; that is not yet proven
|
|
|
314
325
|
live, though the form was seen live twice on 2.1.283. On 2.1.278 it was a
|
|
315
326
|
numbered list, answered with `2. Not now`, and on 2.1.289 it is one again:
|
|
316
327
|
`1. Yes`, `2. Not now`, `3. Don't show again`, above the input box, its
|
|
317
|
-
selection on 1. Yes (seen live, not answered, in #489).
|
|
318
|
-
|
|
328
|
+
selection on 1. Yes (seen live, not answered, in #489). The kit goes down to
|
|
329
|
+
2. Not now, reads the screen, and only then returns. On 2.1.302 it is the
|
|
330
|
+
form again, with `Also scan shell history` on `false` and a foot that starts
|
|
331
|
+
`←/→ to change` (seen live in #604); the kit sends it Esc too. Codex's hooks
|
|
319
332
|
question matters most. The kit's hook is how the book learns which
|
|
320
333
|
conversation the session is running, and until it is answered the
|
|
321
334
|
conversation has not started. Codex's trust applies to the repository root,
|
|
@@ -324,15 +337,15 @@ conversation runs (0.156.1, 0.157.1, 0.158.0 and 0.160.0 all did); the answer is
|
|
|
324
337
|
always the current checkout, the bot home, and never `2. New worktree`:
|
|
325
338
|
the kit never makes a git worktree.
|
|
326
339
|
|
|
327
|
-
The hooks review of a Codex temporary session you made yourself is
|
|
328
|
-
|
|
340
|
+
The hooks review of a Codex temporary session you made yourself is answered
|
|
341
|
+
through the kit: answer it with `obk temp trust-hooks --bots <bots> --name
|
|
329
342
|
<session>`, not with the keys. It reads the screen, chooses **Trust all and
|
|
330
343
|
continue** only if the review is there, and checks that the review went. A
|
|
331
344
|
permission rule can allow that one command, where it cannot allow keys sent
|
|
332
345
|
into any tab. The same goes for Claude Code's `Teach auto mode` screen on a
|
|
333
346
|
temporary session you made: answer it with `obk temp answer --bots <bots>
|
|
334
347
|
--name <session>`. It answers only a shape in the table exactly as the kit
|
|
335
|
-
knows it (Esc on the 2.1.283
|
|
348
|
+
knows it (Esc on the 2.1.283 and 2.1.302 forms, 2. Not now on the 2.1.289 list, the return
|
|
336
349
|
only after a second look), refuses any other screen, and checks that it went. The kit's
|
|
337
350
|
default set allows it, as it allows `obk temp trust-hooks`.
|
|
338
351
|
|
|
@@ -171,9 +171,10 @@ For a bot with its own memory, the report gets a short section:
|
|
|
171
171
|
- **A proposed cut**, when it is near or over the limit: which entries could go
|
|
172
172
|
or be merged, and why.
|
|
173
173
|
|
|
174
|
-
A contradiction is not yours to fix. Send it to that bot's own session,
|
|
175
|
-
|
|
176
|
-
|
|
174
|
+
A contradiction is not yours to fix. Send it to that bot's own session, from
|
|
175
|
+
your own session, for Bot Father's pass: `obk message to --to <bot>/<session>`,
|
|
176
|
+
run in your own tab, gives the road. In a session's tab the kit sends only as
|
|
177
|
+
that session (#570), so name no other with `--from`. Where the road
|
|
177
178
|
is the mailbox, `obk message send` carries it, whatever you run on. Where it is
|
|
178
179
|
an address, that road is Claude Code's own messaging, and it is yours only if
|
|
179
180
|
you run on Claude Code yourself; if you do not, put the finding in your report
|
package/src/book.js
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
// theirs. Beside each session's tab it holds the harness session id that
|
|
7
7
|
// session is running under, and every id it ran under before.
|
|
8
8
|
|
|
9
|
-
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import { closeSync, constants, existsSync, fstatSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
10
10
|
import path from 'node:path';
|
|
11
|
+
import { pathToFileURL } from 'node:url';
|
|
11
12
|
import { DatabaseSync } from 'node:sqlite';
|
|
12
13
|
import { isDeepStrictEqual } from 'node:util';
|
|
13
14
|
import { parse, stringify } from 'yaml';
|
|
@@ -85,11 +86,18 @@ function withHistoryListed(entry) {
|
|
|
85
86
|
* is, never half of it, and a writer that dies leaves the old file in place:
|
|
86
87
|
* the new text goes to a file of this process's own and is moved over the book,
|
|
87
88
|
* which is one operation the file system either did or did not do.
|
|
89
|
+
*
|
|
90
|
+
* Whatever is already at that file's name is taken away and the file is made
|
|
91
|
+
* new, never written through: a session can leave a link there in its own bot
|
|
92
|
+
* folder, which would take the book's text out of it, and the move would make
|
|
93
|
+
* the book that link (#599). A link made there in between makes the write fail,
|
|
94
|
+
* and the old book stays whole.
|
|
88
95
|
*/
|
|
89
96
|
export function writeBook(home, book) {
|
|
90
97
|
const file = bookFile(home);
|
|
91
98
|
const half = `${file}.${process.pid}.part`;
|
|
92
|
-
|
|
99
|
+
rmSync(half, { force: true });
|
|
100
|
+
writeFileSync(half, `${HEADER}${stringify(book)}`, { flag: 'wx' });
|
|
93
101
|
renameSync(half, file);
|
|
94
102
|
}
|
|
95
103
|
|
|
@@ -171,17 +179,40 @@ function lockOn(file, waitMs, how = 'IMMEDIATE') {
|
|
|
171
179
|
// Only while this process holds no turn on it: closing any descriptor of a
|
|
172
180
|
// file lets go of every lock the process holds on that file, and SQLite's
|
|
173
181
|
// own are among them. A file a turn is held on was written already.
|
|
182
|
+
//
|
|
183
|
+
// A session can write its bot's folder of locks, so a link, a FIFO or a
|
|
184
|
+
// folder may sit at the lock's name, and a lock taken through a link would
|
|
185
|
+
// make or write a file anywhere the kit may write (#605). So the kit opens
|
|
186
|
+
// the file itself without following a link, and SQLite opens it only if it
|
|
187
|
+
// is there and makes nothing. A link put at the name only while SQLite
|
|
188
|
+
// opens it is followed, and SQLite names the file it opened, so a file of
|
|
189
|
+
// any other name is closed before it is locked or written. And the turn
|
|
190
|
+
// ends with ROLLBACK, so SQLite never writes the file it locks.
|
|
191
|
+
//
|
|
192
|
+
// What is left, accepted by the architect on #605: SQLite reads the first
|
|
193
|
+
// 100 bytes of the file it opens before the kit can ask its name, so such a
|
|
194
|
+
// link can have a file outside read into SQLite's memory and dropped; and a
|
|
195
|
+
// FIFO put there that instant can hang the command. A lock taken by name
|
|
196
|
+
// cannot close that without a lock Node does not have.
|
|
174
197
|
if (!heldHere.has(file)) {
|
|
175
198
|
try {
|
|
176
199
|
mkdirSync(path.dirname(file), { recursive: true });
|
|
177
|
-
|
|
200
|
+
plainFile(file);
|
|
178
201
|
} catch (error) {
|
|
179
202
|
throw new LockNotWritable(file, error);
|
|
180
203
|
}
|
|
181
204
|
}
|
|
182
205
|
|
|
183
|
-
|
|
206
|
+
let db;
|
|
207
|
+
try {
|
|
208
|
+
db = new DatabaseSync(new URL(`${pathToFileURL(file).href}?mode=rw`));
|
|
209
|
+
} catch (error) {
|
|
210
|
+
throw new LockNotWritable(file, error);
|
|
211
|
+
}
|
|
184
212
|
try {
|
|
213
|
+
if (db.location() !== path.join(realpathSync(path.dirname(file)), path.basename(file))) {
|
|
214
|
+
throw new LockNotWritable(file, notPlain(`a link at it led SQLite to ${db.location()}`));
|
|
215
|
+
}
|
|
185
216
|
// A writer that arrives while another is working waits for it rather than
|
|
186
217
|
// failing at once, and gives up saying so rather than waiting for ever.
|
|
187
218
|
db.exec(`PRAGMA busy_timeout = ${waitMs}`);
|
|
@@ -201,7 +232,7 @@ function lockOn(file, waitMs, how = 'IMMEDIATE') {
|
|
|
201
232
|
return {
|
|
202
233
|
release() {
|
|
203
234
|
try {
|
|
204
|
-
db.exec('
|
|
235
|
+
db.exec('ROLLBACK');
|
|
205
236
|
} finally {
|
|
206
237
|
db.close();
|
|
207
238
|
const left = heldHere.get(file) - 1;
|
|
@@ -212,6 +243,30 @@ function lockOn(file, waitMs, how = 'IMMEDIATE') {
|
|
|
212
243
|
};
|
|
213
244
|
}
|
|
214
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Make the lock file at `file` if it is not there, and open it for writing
|
|
248
|
+
* without following a link or waiting on a FIFO. Anything at the name but a
|
|
249
|
+
* plain file is refused.
|
|
250
|
+
*/
|
|
251
|
+
function plainFile(file) {
|
|
252
|
+
let fd;
|
|
253
|
+
try {
|
|
254
|
+
fd = openSync(file, constants.O_WRONLY | constants.O_CREAT | constants.O_APPEND | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
// A link, a folder and a FIFO with no reader, in that order.
|
|
257
|
+
if (['ELOOP', 'EISDIR', 'ENXIO'].includes(error.code)) throw notPlain(error.code);
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
if (!fstatSync(fd).isFile()) throw notPlain('not a plain file');
|
|
262
|
+
} finally {
|
|
263
|
+
closeSync(fd);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const NOT_PLAIN = 'ENOTPLAIN';
|
|
268
|
+
const notPlain = (message) => Object.assign(new Error(message), { code: NOT_PLAIN });
|
|
269
|
+
|
|
215
270
|
/** How many turns this process holds on each lock file, by its path. */
|
|
216
271
|
const heldHere = new Map();
|
|
217
272
|
|
|
@@ -256,11 +311,36 @@ const botLocks = (home) => {
|
|
|
256
311
|
*/
|
|
257
312
|
class LockNotWritable extends Error {
|
|
258
313
|
constructor(file, why) {
|
|
259
|
-
super(
|
|
314
|
+
super(why.code === NOT_PLAIN
|
|
315
|
+
? `something other than a plain file is at the name of the kit's lock ${file} (${why.message}), so the kit did not take the turn, and nothing was done. Remove ${file}; the kit makes it again.`
|
|
316
|
+
: `the kit cannot write its lock ${file} (${why.code ?? why.message}), so it did not take the turn, and nothing was done. A Codex session started before this version of the kit cannot write its bot's folder of locks: restart this session with the kit. Elsewhere, make ${path.dirname(file)} writable.`);
|
|
260
317
|
this.code = why.code;
|
|
261
318
|
}
|
|
262
319
|
}
|
|
263
320
|
|
|
321
|
+
/**
|
|
322
|
+
* The right to change the bot's `bot.yaml` (#595), held from the read a command
|
|
323
|
+
* makes its change on until that change is written. Two commands that change
|
|
324
|
+
* one bot at the same time then each change what the other left, and neither
|
|
325
|
+
* change is lost.
|
|
326
|
+
*
|
|
327
|
+
* The book's kind of lock, on a file of its own beside the book's: `temp make`
|
|
328
|
+
* writes bot.yaml while it holds the book's, and nothing takes the book's while
|
|
329
|
+
* it holds this one, so neither waits on the other.
|
|
330
|
+
*/
|
|
331
|
+
export function takeBotTurn(home) {
|
|
332
|
+
const file = botLockFile(home);
|
|
333
|
+
try {
|
|
334
|
+
return lockOn(file, WAIT_MS);
|
|
335
|
+
} catch (error) {
|
|
336
|
+
if (error instanceof LockNotWritable) throw error;
|
|
337
|
+
throw new Error(`waited ${WAIT_MS / 1000} seconds for something else to finish writing ${path.join(home, 'bot.yaml')} and it did not (${error.message}). bot.yaml was not changed. Try again; if nothing is running, remove ${file}, which is where writers take their turn.`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** Beside the book's lock, with the bot's name: the lock files of its sessions carry one more part. */
|
|
342
|
+
const botLockFile = (home) => path.join(botLocks(home), `${encodeURIComponent(path.basename(realpathSync(home)))}.bot.lock`);
|
|
343
|
+
|
|
264
344
|
/**
|
|
265
345
|
* One session's turn at its mailbox (#321): held by `session mailbox` for its
|
|
266
346
|
* whole step, by `message check` in the session's own tab for its bind, read and
|
|
@@ -468,10 +548,11 @@ export function rememberSession(entry = {}, id, ended) {
|
|
|
468
548
|
|
|
469
549
|
/**
|
|
470
550
|
* The session entry once the id it held has turned out to have no conversation
|
|
471
|
-
* behind it: the id goes into the history with that
|
|
472
|
-
*
|
|
551
|
+
* behind it on the session's harness: the id goes into the history with that
|
|
552
|
+
* reason, and anything else worth knowing about it, such as the harness it ran
|
|
553
|
+
* on, and the entry holds none until the harness names the new one.
|
|
473
554
|
*/
|
|
474
|
-
export function forgetSession(entry, ended) {
|
|
555
|
+
export function forgetSession(entry, ended, more = {}) {
|
|
475
556
|
const { session, ...rest } = entry;
|
|
476
|
-
return { ...rest, history: [...(entry.history ?? []), { session, ended, at: new Date().toISOString() }] };
|
|
557
|
+
return { ...rest, history: [...(entry.history ?? []), { session, ended, ...more, at: new Date().toISOString() }] };
|
|
477
558
|
}
|