@assuredloop/orca-bot-kit 0.25.6 → 0.26.1
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 +25 -3
- package/SETUP.md +14 -14
- package/package.json +1 -1
- package/rules/limits.md +3 -3
- package/skills/obk-bot-building/SKILL.md +19 -18
- package/src/bot.js +61 -6
- package/src/cli.js +200 -128
- package/src/commands.js +84 -0
- package/src/health.js +9 -3
- package/src/launch.js +25 -0
- package/src/permissions.js +97 -64
- package/src/temp.js +19 -1
package/README.md
CHANGED
|
@@ -128,8 +128,8 @@ obk up --bots /path/to/my-bots
|
|
|
128
128
|
that file and the rule units, `CLAUDE.md` as a symlink to it, and a `.gitignore`
|
|
129
129
|
for `work/`.
|
|
130
130
|
`session add` writes one session into `bot.yaml`: its harness (the bot's unless
|
|
131
|
-
it says otherwise), model, effort, context window,
|
|
132
|
-
work dir and any extra arguments for the harness. A start prompt is either text
|
|
131
|
+
it says otherwise), model, effort, context window, start prompt,
|
|
132
|
+
work dir and any extra arguments for the harness, and the approval level. A start prompt is either text
|
|
133
133
|
(`--prompt`) or, when it is long, a file in the bot home the session points at
|
|
134
134
|
(`--prompt-file prompts/reviewer.md`), and it reaches the harness as written,
|
|
135
135
|
apart from two things the kit does on purpose: blank space at either end is
|
|
@@ -138,7 +138,22 @@ note about it. Anything you leave out is the
|
|
|
138
138
|
harness's own default; the kit names no model of its own. The approval level is
|
|
139
139
|
`auto` — the harness's real auto mode — unless you ask for `ask` or, in those
|
|
140
140
|
words, `dangerously-skip`
|
|
141
|
-
([ADR 0015](docs/adr/0015-three-approval-levels-auto-by-default.md)).
|
|
141
|
+
([ADR 0015](docs/adr/0015-three-approval-levels-auto-by-default.md)). Only
|
|
142
|
+
`obk permission approval` changes it, after your yes: `session add` and
|
|
143
|
+
`session change` take no approval, and refuse an extra argument that sets one
|
|
144
|
+
([ADR 0041](docs/adr/0041-permission-changes-and-risky-operations-have-commands-of-their-own.md)).
|
|
145
|
+
|
|
146
|
+
Every bot gets the kit's default permission rules, written by the kit with
|
|
147
|
+
nobody asked: its own commands for this bots folder, reading a long message,
|
|
148
|
+
`git add` and `git commit`, and Orca's `orca orchestration check --run`
|
|
149
|
+
([ADR 0036](docs/adr/0036-the-kit-writes-every-bots-default-permission-rules.md)).
|
|
150
|
+
`obk permission`, `obk retire`, `obk pause`, `obk restart`, `obk bot create`
|
|
151
|
+
and `obk init` are not in the set, and keep your yes
|
|
152
|
+
([ADR 0041](docs/adr/0041-permission-changes-and-risky-operations-have-commands-of-their-own.md)).
|
|
153
|
+
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 `obk restart` or `obk bot create`
|
|
155
|
+
rule, which kit 0.26.0 gave every bot, keeps it: `obk rules build` and `obk up`
|
|
156
|
+
name it, with the `obk permission disallow` command that takes it back.
|
|
142
157
|
|
|
143
158
|
Neither command touches Orca. `obk up` is what makes it all real there: an Orca
|
|
144
159
|
project per bot, a tab per session, each started at the bot home with its own
|
|
@@ -443,6 +458,11 @@ Bot Father does these for you when you ask; these are the commands it runs.
|
|
|
443
458
|
|
|
444
459
|
```sh
|
|
445
460
|
obk bot change --bots /path/to/my-bots --bot api-bot --charter "…"
|
|
461
|
+
obk bot change --bots /path/to/my-bots --bot api-bot --role-cap reviewer=2
|
|
462
|
+
obk permission allow --bots /path/to/my-bots --bot api-bot --rule 'Bash(gh pr merge:*)'
|
|
463
|
+
obk permission disallow --bots /path/to/my-bots --bot api-bot --rule 'Bash(gh pr merge:*)'
|
|
464
|
+
obk permission approval --bots /path/to/my-bots --bot api-bot --session daily --approval ask
|
|
465
|
+
obk permission approval --bots /path/to/my-bots --bot api-bot --temps --approval dangerously-skip
|
|
446
466
|
obk session change --bots /path/to/my-bots --bot api-bot --session daily --model opus
|
|
447
467
|
obk skills remove --bots /path/to/my-bots --bot api-bot --skill kit:obk-tdd
|
|
448
468
|
obk pause --bots /path/to/my-bots --bot api-bot [--session daily]
|
|
@@ -660,6 +680,8 @@ except the mailbox Runs Orca gives no way to delete, which the run names; it
|
|
|
660
680
|
never closes a tab it did not open. The trust its harnesses write into your
|
|
661
681
|
`~/.codex/config.toml` and `~/.claude.json` for the run's own throwaway folders
|
|
662
682
|
is taken out again after the run, and named; nothing else in them is changed.
|
|
683
|
+
An Orca project the run leaves in one of its own throwaway folders is named,
|
|
684
|
+
with its setup id, and fails the run; the command removes none itself.
|
|
663
685
|
|
|
664
686
|
They run on the machine the command is typed on, so the command will not start
|
|
665
687
|
them by itself. On its own it says what it is about to drive — whose machine,
|
package/SETUP.md
CHANGED
|
@@ -128,11 +128,12 @@ tab, which is a plain shell for work across the whole fleet.
|
|
|
128
128
|
Read everything it prints. Three parts of it matter to them:
|
|
129
129
|
|
|
130
130
|
- **what it created** — say it in a sentence, not as a file listing;
|
|
131
|
-
- **the permission rules it
|
|
132
|
-
|
|
133
|
-
bots folder, and its commits
|
|
134
|
-
|
|
135
|
-
`
|
|
131
|
+
- **the permission rules it wrote**: the kit's default set, so auto mode does
|
|
132
|
+
not stop Bot Father for the kit's own commands, reading a long message
|
|
133
|
+
beside the bots folder, and its commits. The kit writes this set for every
|
|
134
|
+
bot, with nobody asked. Say in a sentence what it allows, and that
|
|
135
|
+
`obk permission`, `obk retire`, `obk pause` and `obk init` are not in it and
|
|
136
|
+
keep their yes;
|
|
136
137
|
- **anything it says about Orca's own default launch arguments.** Orca adds
|
|
137
138
|
these to every agent it launches, relaunches and resumes. When they carry a
|
|
138
139
|
permission bypass, every session runs in that mode whatever approval level the
|
|
@@ -228,8 +229,8 @@ home. The kit never makes a git worktree, so
|
|
|
228
229
|
`2. New worktree` is never the answer.
|
|
229
230
|
|
|
230
231
|
A bot's own temporary sessions are answered by their maker, through the kit, so
|
|
231
|
-
that
|
|
232
|
-
tab. On Codex's hooks review, the maker runs
|
|
232
|
+
that a narrow permission rule of the kit's default set allows it rather than a
|
|
233
|
+
rule to type into any tab. On Codex's hooks review, the maker runs
|
|
233
234
|
`obk temp trust-hooks --bots <their path> --name <session>`. On Claude Code's
|
|
234
235
|
`Teach auto mode` screen, it runs `obk temp answer --bots <their path> --name <session>`.
|
|
235
236
|
That command reads the screen and answers only when it holds one of the two
|
|
@@ -237,17 +238,16 @@ shapes in the table exactly as the kit knows it: Esc for the 2.1.283 form, and
|
|
|
237
238
|
**2. Not now** for the 2.1.289 list. On the list it sends the arrows, reads the
|
|
238
239
|
screen again, and sends the return only with the selection on **2. Not now**.
|
|
239
240
|
It refuses any other screen, types nothing then, and prints what it saw.
|
|
240
|
-
Afterwards it checks that the screen has gone.
|
|
241
|
-
|
|
242
|
-
`<kit>`, as the command's own
|
|
241
|
+
Afterwards it checks that the screen has gone. The kit's default set allows
|
|
242
|
+
both commands for every bot, so there is nothing to ask the user. The rules
|
|
243
|
+
are these, with the kit's full CLI path for `<kit>`, as the command's own
|
|
244
|
+
output prints it:
|
|
243
245
|
|
|
244
246
|
```text
|
|
245
|
-
Bash(<kit> temp answer
|
|
247
|
+
Bash(<kit> temp answer --bots <their path>:*)
|
|
248
|
+
Bash(<kit> temp trust-hooks --bots <their path>:*)
|
|
246
249
|
```
|
|
247
250
|
|
|
248
|
-
Ask the user, and only after their yes run
|
|
249
|
-
`obk bot change --bots <their path> --bot <bot> --allow 'Bash(<kit> temp answer:*)'`.
|
|
250
|
-
|
|
251
251
|
A long-lived session's two screens go through the kit as well, not through the
|
|
252
252
|
keys. On Codex's hooks review, run
|
|
253
253
|
`obk session trust-hooks --bots <their path> --bot <bot> --session <session>`.
|
package/package.json
CHANGED
package/rules/limits.md
CHANGED
|
@@ -17,6 +17,6 @@ publishing, releasing, spending money, anything another person receives.
|
|
|
17
17
|
When something you were told to use is missing (a model, a tool, a file), say
|
|
18
18
|
so and ask. Quietly using a different one hides the change.
|
|
19
19
|
|
|
20
|
-
Permission rules
|
|
21
|
-
after the user's yes to
|
|
22
|
-
|
|
20
|
+
Permission rules and approvals are the kit's to write: its default set at once,
|
|
21
|
+
anything else after the user's yes to it, by `"${OBK_CLI:-obk}" permission allow`,
|
|
22
|
+
`disallow` or `approval`. Never edit one in or out yourself, Bot Father included.
|
|
@@ -194,7 +194,7 @@ The kit's command line does the writing, and the files belong to the user:
|
|
|
194
194
|
source add` records where online skills come from and `obk skills fetch`
|
|
195
195
|
clones them, `obk skills add` puts one on a bot, `obk skills build` links
|
|
196
196
|
them, and `obk up` opens what is missing in Orca. `obk bot change` gives a
|
|
197
|
-
bot a new charter, `obk session change` changes a session's settings, and
|
|
197
|
+
bot a new charter or a role's cap, `obk session change` changes a session's settings, and
|
|
198
198
|
`obk skills remove` takes a skill off a list (then `obk skills build`). `obk
|
|
199
199
|
pause` stops a bot or a session for now and `obk unpause` brings it back;
|
|
200
200
|
`obk retire` ends one. After `obk up`, `obk restart` or `obk unpause`, read
|
|
@@ -206,16 +206,18 @@ Run `obk --help` for the flags of the version actually installed rather than
|
|
|
206
206
|
trusting a line you remember.
|
|
207
207
|
|
|
208
208
|
A bot needs a few permission rules so that auto mode, or Codex's sandbox and
|
|
209
|
-
reviewer, does not stop it for what the kit's own rules tell it to do:
|
|
210
|
-
|
|
211
|
-
its commits.
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
the
|
|
218
|
-
|
|
209
|
+
reviewer, does not stop it for what the kit's own rules tell it to do: the
|
|
210
|
+
kit's own commands, reading a long message kept beside the bots folder, and
|
|
211
|
+
its commits. The kit writes this default set itself, with nobody asked:
|
|
212
|
+
`obk bot create`, `obk up` and `obk rules build` add each default rule a bot
|
|
213
|
+
does not have yet to its `bot.yaml` and its settings, or for Codex its
|
|
214
|
+
`.codex/rules/obk.rules`, and say which rules they wrote. The set leaves out
|
|
215
|
+
the commands that keep the user's yes: `obk permission`, `obk retire`, `obk
|
|
216
|
+
pause`, `obk restart`, `obk bot create` and `obk init`. A bot that still holds
|
|
217
|
+
the `obk restart` or `obk bot create` rule from kit 0.26.0 keeps it, and `obk
|
|
218
|
+
rules build` and `obk up` name it with the `obk permission disallow` command
|
|
219
|
+
that takes it back; run that command only after the user's yes. Never write a
|
|
220
|
+
permission rule into a settings or rules file by hand.
|
|
219
221
|
|
|
220
222
|
The charter decides what else the bot needs. What it lets the bot do without
|
|
221
223
|
asking, such as merging pull requests, auto mode or Codex's sandbox can still
|
|
@@ -228,14 +230,14 @@ Spell them in Claude Code's form for a Codex bot too, `Bash(<command>:*)`: the
|
|
|
228
230
|
kit writes Codex's own form of each, and says which ones Codex has none for,
|
|
229
231
|
such as an `Edit` rule or one exact command.
|
|
230
232
|
Show the user the list word for word, with a line on what each lets the bot do,
|
|
231
|
-
and only after their yes run `obk
|
|
232
|
-
yes to. On a no, run nothing. `obk bot change --charter` names the rules the
|
|
233
|
+
and only after their yes run `obk permission allow --rule <rule>`, once per
|
|
234
|
+
rule they said yes to. On a no, run nothing. `obk bot change --charter` names the rules the
|
|
233
235
|
bot is allowed now; say which of them the new charter no longer grants, show
|
|
234
236
|
each word for word, and ask whether to take it back. Only after their yes run
|
|
235
|
-
`obk
|
|
237
|
+
`obk permission disallow --rule <rule>`, once per rule, which takes it out of `bot.yaml`
|
|
236
238
|
and the bot's settings or Codex rules; on a no, run nothing, and it stays
|
|
237
239
|
allowed. A rule the user added to a settings file by hand is theirs to take
|
|
238
|
-
out; the kit refuses it. Keep
|
|
240
|
+
out; the kit refuses it, and it refuses a rule of its own default set too. Keep
|
|
239
241
|
each rule narrow: a program and what it does. The kit refuses a rule that lets
|
|
240
242
|
the bot run any command, a program with any arguments, or a shell or
|
|
241
243
|
interpreter with any arguments; a user who wants one for a bot adds it to that
|
|
@@ -331,9 +333,8 @@ into any tab. The same goes for Claude Code's `Teach auto mode` screen on a
|
|
|
331
333
|
temporary session you made: answer it with `obk temp answer --bots <bots>
|
|
332
334
|
--name <session>`. It answers only a shape in the table exactly as the kit
|
|
333
335
|
knows it (Esc on the 2.1.283 form, 2. Not now on the 2.1.289 list, the return
|
|
334
|
-
only after a second look), refuses any other screen, and checks that it went.
|
|
335
|
-
|
|
336
|
-
command prints it; ask the user before `obk bot change --allow` adds it.
|
|
336
|
+
only after a second look), refuses any other screen, and checks that it went. The kit's
|
|
337
|
+
default set allows it, as it allows `obk temp trust-hooks`.
|
|
337
338
|
|
|
338
339
|
A long-lived session's two screens go through the kit too: Codex's hooks
|
|
339
340
|
review with `obk session trust-hooks --bots <bots> --bot <bot> --session
|
package/src/bot.js
CHANGED
|
@@ -11,7 +11,7 @@ import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSy
|
|
|
11
11
|
import os from 'node:os';
|
|
12
12
|
import path from 'node:path';
|
|
13
13
|
import { isDeepStrictEqual } from 'node:util';
|
|
14
|
-
import { parse, parseDocument, stringify } from 'yaml';
|
|
14
|
+
import { isSeq, parse, parseDocument, stringify } from 'yaml';
|
|
15
15
|
|
|
16
16
|
import { DEFAULT_APPROVAL, HARNESSES, harnessOf, sessionTrouble } from './launch.js';
|
|
17
17
|
|
|
@@ -161,6 +161,9 @@ export function readBot(home, name = path.basename(home)) {
|
|
|
161
161
|
allow: bot.allow,
|
|
162
162
|
// The roles its temporary sessions can be made in, judged by tempRoles.
|
|
163
163
|
...(bot.temp_roles === undefined ? {} : { temp_roles: bot.temp_roles }),
|
|
164
|
+
// The widest approval its temporary sessions may be made at, the user's yes
|
|
165
|
+
// kept by `permission approval --temps` (ADR 0041).
|
|
166
|
+
...(bot.temp_approval === undefined ? {} : { temp_approval: bot.temp_approval }),
|
|
164
167
|
// A paused bot is one `obk up` leaves closed; its sessions keep their book.
|
|
165
168
|
...(bot.paused === true ? { paused: true } : {}),
|
|
166
169
|
sessions: sessions.map((session) => {
|
|
@@ -278,10 +281,10 @@ export function changeBot(bots, bot, { charter }) {
|
|
|
278
281
|
/**
|
|
279
282
|
* The bot's `allow` list as it is, after refusing what `allowRules` or
|
|
280
283
|
* `disallowRules` would refuse: an empty rule, or a list already there that is
|
|
281
|
-
* not a list of rules. Asked before anything is written, so a refused
|
|
282
|
-
*
|
|
284
|
+
* not a list of rules. Asked before anything is written, so a refused
|
|
285
|
+
* `permission allow` or `permission disallow` changes nothing.
|
|
283
286
|
*/
|
|
284
|
-
export function allowedNow(bots, bot, rules, flag = '--
|
|
287
|
+
export function allowedNow(bots, bot, rules, flag = '--rule') {
|
|
285
288
|
if (rules.some((rule) => rule.trim() === '')) {
|
|
286
289
|
throw new Error(`${flag} is empty. Give it the exact permission rule the user said yes to, such as Bash(git add:*).`);
|
|
287
290
|
}
|
|
@@ -320,7 +323,7 @@ export function allowRules(bots, bot, rules, { write = true } = {}) {
|
|
|
320
323
|
* false it only refuses what the edit would refuse, and writes nothing.
|
|
321
324
|
*/
|
|
322
325
|
export function disallowRules(bots, bot, rules, { write = true } = {}) {
|
|
323
|
-
const { home, was } = allowedNow(bots, bot, rules
|
|
326
|
+
const { home, was } = allowedNow(bots, bot, rules);
|
|
324
327
|
const disallowed = [...new Set(rules)];
|
|
325
328
|
const allow = was.filter((rule) => !disallowed.includes(rule));
|
|
326
329
|
editBot(bots, bot, `take back ${disallowed.join(', ')} for ${bot}`, (doc) => doc.set('allow', allow), (before) => ({ ...before, allow }), write);
|
|
@@ -357,6 +360,58 @@ export function changeSession(bots, bot, name, settings) {
|
|
|
357
360
|
return { bot, home, session };
|
|
358
361
|
}
|
|
359
362
|
|
|
363
|
+
/**
|
|
364
|
+
* Record the widest approval the bot's temporary sessions may be made at,
|
|
365
|
+
* `level`, as `temp_approval` (ADR 0041). Returns { bot, home, level }.
|
|
366
|
+
*/
|
|
367
|
+
export function setTempApproval(bots, bot, level) {
|
|
368
|
+
editBot(bots, bot, `let ${bot}'s temporary sessions be made at ${level}`, (doc) => doc.set('temp_approval', level), (was) => ({ ...was, temp_approval: level }));
|
|
369
|
+
return { bot, home: botDir(bots, bot), level };
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Set the caps of roles in the bot's `temp_roles`: `caps` is a list of
|
|
374
|
+
* `[role, cap]`, a cap of undefined taking it off. A role written as a list of
|
|
375
|
+
* options becomes a mapping that holds that list as `options` beside its cap.
|
|
376
|
+
* A role the bot does not have is refused before anything is written. Returns
|
|
377
|
+
* { bot, home, caps }. With `write` false it only refuses what the edit would
|
|
378
|
+
* refuse, and writes nothing.
|
|
379
|
+
*/
|
|
380
|
+
export function setRoleCaps(bots, bot, caps, { write = true } = {}) {
|
|
381
|
+
const home = existingBot(bots, bot);
|
|
382
|
+
const roles = readBot(home, bot).temp_roles;
|
|
383
|
+
const file = path.join(home, BOT_YAML);
|
|
384
|
+
for (const [role] of caps) {
|
|
385
|
+
if (roles === null || typeof roles !== 'object' || Array.isArray(roles) || !Object.hasOwn(roles, role)) {
|
|
386
|
+
const has = roles !== null && typeof roles === 'object' && !Array.isArray(roles) ? Object.keys(roles) : [];
|
|
387
|
+
throw new Error(`${bot} has no temporary-session role called ${role} in ${file}${has.length === 0 ? ', and no temp_roles at all' : `. It has: ${has.join(', ')}`}. Nothing was changed.`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
const capped = (written, cap) => {
|
|
391
|
+
const { cap: was, ...rest } = Array.isArray(written) ? { options: written } : written;
|
|
392
|
+
return cap === undefined ? rest : { ...rest, cap };
|
|
393
|
+
};
|
|
394
|
+
editBot(bots, bot, `set the caps of ${caps.map(([role]) => role).join(', ')}`, (doc) => {
|
|
395
|
+
for (const [role, cap] of caps) {
|
|
396
|
+
const node = doc.getIn(['temp_roles', role], true);
|
|
397
|
+
if (isSeq(node)) {
|
|
398
|
+
doc.setIn(['temp_roles', role], doc.createNode(capped(node.toJSON(), cap)));
|
|
399
|
+
} else if (cap === undefined) {
|
|
400
|
+
doc.deleteIn(['temp_roles', role, 'cap']);
|
|
401
|
+
} else {
|
|
402
|
+
doc.setIn(['temp_roles', role, 'cap'], cap);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}, (was) => ({
|
|
406
|
+
...was,
|
|
407
|
+
temp_roles: Object.fromEntries(Object.entries(was.temp_roles).map(([role, written]) => {
|
|
408
|
+
const asked = caps.findLast(([one]) => one === role);
|
|
409
|
+
return [role, asked === undefined ? written : capped(written, asked[1])];
|
|
410
|
+
})),
|
|
411
|
+
}), write);
|
|
412
|
+
return { bot, home, caps };
|
|
413
|
+
}
|
|
414
|
+
|
|
360
415
|
/**
|
|
361
416
|
* Mark the bot `bot`, or its session `name`, paused or not. Returns whether
|
|
362
417
|
* the file changed. A paused bot or session is one `obk up` leaves closed.
|
|
@@ -497,7 +552,7 @@ export const SESSION_FIELDS = [
|
|
|
497
552
|
];
|
|
498
553
|
|
|
499
554
|
/** Everything the top of a bot's own file can hold. */
|
|
500
|
-
const BOT_FIELDS = ['name', 'harness', 'charter', 'rules', 'skills', 'allow', 'sessions', 'paused', 'temp_roles'];
|
|
555
|
+
const BOT_FIELDS = ['name', 'harness', 'charter', 'rules', 'skills', 'allow', 'sessions', 'paused', 'temp_roles', 'temp_approval'];
|
|
501
556
|
|
|
502
557
|
/**
|
|
503
558
|
* The keys in a bot's file that the kit does not know, and so that nothing
|