@assuredloop/orca-bot-kit 0.27.0 → 0.28.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 +48 -15
- package/SETUP.md +26 -13
- package/package.json +1 -1
- package/rules/mail.md +3 -3
- package/skills/obk-bot-building/SKILL.md +24 -15
- 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 +95 -34
- package/src/commands.js +20 -11
- package/src/conversations.js +73 -3
- package/src/groom.js +27 -9
- package/src/health.js +18 -7
- package/src/hooks.js +7 -1
- package/src/init.js +36 -29
- package/src/launch.js +202 -18
- package/src/message.js +42 -2
- package/src/orca-reload.applescript +1 -1
- package/src/orca-runtime.cjs +1 -1
- package/src/orca.js +16 -7
- package/src/permissions.js +30 -16
- package/src/pi.js +55 -0
- package/src/rules.js +5 -2
- package/src/skills.js +50 -34
- package/src/sources.js +73 -4
- package/src/temp.js +65 -20
- package/src/up.js +68 -13
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` and `obk init` are
|
|
170
|
+
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
|
|
|
@@ -671,9 +699,14 @@ real Orca and the real harnesses on your own machine. No CI runner can do that,
|
|
|
671
699
|
so these are run by hand before a change that touches Orca or a harness is
|
|
672
700
|
merged. A system test touches only what it creates and removes it afterwards,
|
|
673
701
|
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
|
-
|
|
702
|
+
never closes a tab it did not open. Before any test loads, the command copies
|
|
703
|
+
your `~/.claude.json` and `~/.codex/config.toml` into a new
|
|
704
|
+
`obk-config-backup-*` folder in your temp folder, readable by you alone, and
|
|
705
|
+
says where; if a copy fails, no test runs. After the run it says whether each
|
|
706
|
+
file still matches its copy, and never copies one back: that is yours to
|
|
707
|
+
decide. The trust its harnesses write into your `~/.codex/config.toml` and
|
|
708
|
+
`~/.claude.json` for the run's own throwaway folders is taken out again after
|
|
709
|
+
the run, and named; nothing else in them is changed.
|
|
677
710
|
An Orca project the run leaves in one of its own throwaway folders is named,
|
|
678
711
|
with its setup id, and fails the run; the command removes none itself.
|
|
679
712
|
|
package/SETUP.md
CHANGED
|
@@ -38,11 +38,19 @@ 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
|
|
@@ -132,8 +140,9 @@ Read everything it prints. Three parts of it matter to them:
|
|
|
132
140
|
not stop Bot Father for the kit's own commands, reading a long message
|
|
133
141
|
beside the bots folder, and its commits. The kit writes this set for every
|
|
134
142
|
bot, with nobody asked. Say in a sentence what it allows, and that
|
|
135
|
-
`obk permission`, `obk retire`, `obk pause`
|
|
136
|
-
|
|
143
|
+
`obk permission`, `obk retire`, `obk pause`, `obk restart`, `obk bot create`,
|
|
144
|
+
`obk bot change`, `obk session add`, `obk session change` and `obk init` are
|
|
145
|
+
not in it and keep their yes;
|
|
137
146
|
- **anything it says about Orca's own default launch arguments.** Orca adds
|
|
138
147
|
these to every agent it launches, relaunches and resumes. When they carry a
|
|
139
148
|
permission bypass, every session runs in that mode whatever approval level the
|
|
@@ -192,21 +201,22 @@ that folder itself, exactly as you gave it to `obk init`:
|
|
|
192
201
|
"$ORCA" terminal read --terminal <handle> --screen --json
|
|
193
202
|
```
|
|
194
203
|
|
|
195
|
-
What you will see, and the usual answer. Each goes as one
|
|
204
|
+
What you will see, and the usual answer. Each key goes as one
|
|
196
205
|
`"$ORCA" terminal send --terminal <handle> --text …`, with the return inside the
|
|
197
206
|
text and **no** `--enter`, because a menu takes a return as the keypress it is
|
|
198
|
-
waiting for:
|
|
207
|
+
waiting for. Where the Send cell names `obk` commands, send no keys: the kit
|
|
208
|
+
answers that screen, by the command and its arguments below the table.
|
|
199
209
|
|
|
200
210
|
| On screen | Send | Which is | Proven on |
|
|
201
211
|
|---|---|---|---|
|
|
202
212
|
| Claude Code's folder trust list | `\x1b[B\r` | down, then return: its selection starts on **No, exit** | Claude Code 2.1.283 |
|
|
203
213
|
| 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` |
|
|
214
|
+
| 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
215
|
| 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
216
|
| 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
217
|
| `[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 |
|
|
218
|
+
| 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 |
|
|
219
|
+
| 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
220
|
|
|
211
221
|
Each key counts on where the selection starts, as it did on the version named.
|
|
212
222
|
Read the screen in front of you before you send: if the selection is somewhere
|
|
@@ -233,8 +243,9 @@ that a narrow permission rule of the kit's default set allows it rather than a
|
|
|
233
243
|
rule to type into any tab. On Codex's hooks review, the maker runs
|
|
234
244
|
`obk temp trust-hooks --bots <their path> --name <session>`. On Claude Code's
|
|
235
245
|
`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
|
|
246
|
+
That command reads the screen and answers only when it holds one of the
|
|
247
|
+
shapes in the table exactly as the kit knows it: Esc for the 2.1.283 and
|
|
248
|
+
2.1.302 forms, and
|
|
238
249
|
**2. Not now** for the 2.1.289 list. On the list it sends the arrows, reads the
|
|
239
250
|
screen again, and sends the return only with the selection on **2. Not now**.
|
|
240
251
|
It refuses any other screen, types nothing then, and prints what it saw.
|
|
@@ -298,8 +309,10 @@ obk health --bots <their path>
|
|
|
298
309
|
|
|
299
310
|
It reads the setup and says what it found, and writes nothing at all. It exits 1
|
|
300
311
|
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
|
-
|
|
312
|
+
one exception is an error with no findings: that is a real problem. Tell them
|
|
313
|
+
the error as it is. When it says Orca is not answering, run the Orca line from
|
|
314
|
+
step 1 again, and go by what that gives: start Orca only when it answers that
|
|
315
|
+
Orca is not running.
|
|
303
316
|
|
|
304
317
|
Relay what it says in plain words. Nothing it prints is a verdict: which findings
|
|
305
318
|
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` and `obk init`. 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
|
|
@@ -284,26 +288,29 @@ sometimes. Read each screen with `<orca> terminal read --terminal <handle>
|
|
|
284
288
|
--screen`, where `<handle>` is the terminal the kit named for the tab and
|
|
285
289
|
`<orca>` is the Orca CLI the kit uses: `$OBK_ORCA` when that is set, and
|
|
286
290
|
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
|
|
291
|
+
`orca` can fail. Send each key as one `<orca> terminal send --terminal
|
|
292
|
+
<handle> --text …`, with the return inside the text and no `--enter`. Where
|
|
293
|
+
the Send cell names `obk` commands, send no keys: the kit answers that
|
|
294
|
+
screen, by the commands below, the first for a long-lived session and the
|
|
295
|
+
second for a temporary one.
|
|
289
296
|
|
|
290
297
|
| On screen | Send | Which is | Proven on |
|
|
291
298
|
|---|---|---|---|
|
|
292
299
|
| Claude Code's trust list | `\x1b[B\r` | down, return: off **No, exit** | Claude Code 2.1.283 |
|
|
293
300
|
| Codex's `Trust this folder?` | `\r` | return: it starts on yes | Codex 0.160.0 |
|
|
294
|
-
| Codex's
|
|
301
|
+
| Codex's hooks review | `obk session trust-hooks`, `obk temp trust-hooks` | trust | Codex 0.160.0 |
|
|
295
302
|
| Codex's `Where should the new conversation run?` | `\r` | **Current checkout** | Codex 0.160.0 |
|
|
296
303
|
| Codex's update offer, `1. Update now` | `\r` | **Update now**, where it starts | Codex 0.156.1 |
|
|
297
304
|
| `[oh-my-zsh] Would you like to update?` | `n` | no: the user updates it | oh-my-zsh bf77e35 |
|
|
298
|
-
| Claude Code's
|
|
299
|
-
|
|
|
305
|
+
| Claude Code's Teach form | `obk session answer`, `obk temp answer` | Esc | Not yet proven, below |
|
|
306
|
+
| Its 2.1.289 list | `obk session answer`, `obk temp answer` | 2. Not now | Not yet proven, below |
|
|
300
307
|
|
|
301
308
|
Each key counts on where the selection starts on the version named. Read the
|
|
302
309
|
screen before you send: if the selection is elsewhere or the options have
|
|
303
310
|
moved, go to the answer with the arrows, then return. Never a digit and then
|
|
304
311
|
return: on Codex's update offer that took the highlighted **Update now**,
|
|
305
312
|
whatever the digit was. Codex's trust said `1. Yes, continue` on older
|
|
306
|
-
versions.
|
|
313
|
+
versions.
|
|
307
314
|
Its update offer was proven on 0.156.1; 0.157.1 showed it with the selection on
|
|
308
315
|
Update now, not pressed, and 0.158.0 offered 0.160.0, taken on 2026-10-02 by
|
|
309
316
|
Bot Father's restarts.
|
|
@@ -314,8 +321,10 @@ shell history. Esc cancels it (Not now) in its code; that is not yet proven
|
|
|
314
321
|
live, though the form was seen live twice on 2.1.283. On 2.1.278 it was a
|
|
315
322
|
numbered list, answered with `2. Not now`, and on 2.1.289 it is one again:
|
|
316
323
|
`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
|
-
|
|
324
|
+
selection on 1. Yes (seen live, not answered, in #489). The kit goes down to
|
|
325
|
+
2. Not now, reads the screen, and only then returns. On 2.1.302 it is the
|
|
326
|
+
form again, with `Also scan shell history` on `false` and a foot that starts
|
|
327
|
+
`←/→ to change` (seen live in #604); the kit sends it Esc too. Codex's hooks
|
|
319
328
|
question matters most. The kit's hook is how the book learns which
|
|
320
329
|
conversation the session is running, and until it is answered the
|
|
321
330
|
conversation has not started. Codex's trust applies to the repository root,
|
|
@@ -324,15 +333,15 @@ conversation runs (0.156.1, 0.157.1, 0.158.0 and 0.160.0 all did); the answer is
|
|
|
324
333
|
always the current checkout, the bot home, and never `2. New worktree`:
|
|
325
334
|
the kit never makes a git worktree.
|
|
326
335
|
|
|
327
|
-
The hooks review of a Codex temporary session you made yourself is
|
|
328
|
-
|
|
336
|
+
The hooks review of a Codex temporary session you made yourself is answered
|
|
337
|
+
through the kit: answer it with `obk temp trust-hooks --bots <bots> --name
|
|
329
338
|
<session>`, not with the keys. It reads the screen, chooses **Trust all and
|
|
330
339
|
continue** only if the review is there, and checks that the review went. A
|
|
331
340
|
permission rule can allow that one command, where it cannot allow keys sent
|
|
332
341
|
into any tab. The same goes for Claude Code's `Teach auto mode` screen on a
|
|
333
342
|
temporary session you made: answer it with `obk temp answer --bots <bots>
|
|
334
343
|
--name <session>`. It answers only a shape in the table exactly as the kit
|
|
335
|
-
knows it (Esc on the 2.1.283
|
|
344
|
+
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
345
|
only after a second look), refuses any other screen, and checks that it went. The kit's
|
|
337
346
|
default set allows it, as it allows `obk temp trust-hooks`.
|
|
338
347
|
|
|
@@ -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
|
}
|