@assuredloop/orca-bot-kit 0.26.0 → 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 CHANGED
@@ -141,15 +141,19 @@ 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 0037](docs/adr/0037-permission-changes-and-risky-operations-have-commands-of-their-own.md)).
144
+ ([ADR 0041](docs/adr/0041-permission-changes-and-risky-operations-have-commands-of-their-own.md)).
145
145
 
146
146
  Every bot gets the kit's default permission rules, written by the kit with
147
147
  nobody asked: its own commands for this bots folder, reading a long message,
148
148
  `git add` and `git commit`, and Orca's `orca orchestration check --run`
149
149
  ([ADR 0036](docs/adr/0036-the-kit-writes-every-bots-default-permission-rules.md)).
150
- `obk permission`, `obk retire`, `obk pause` and `obk init` are not in the set,
151
- and keep your yes. A rule beyond the set is written by `obk permission allow`,
152
- after your yes to the exact rule.
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.
153
157
 
154
158
  Neither command touches Orca. `obk up` is what makes it all real there: an Orca
155
159
  project per bot, a tab per session, each started at the bot home with its own
@@ -676,6 +680,8 @@ except the mailbox Runs Orca gives no way to delete, which the run names; it
676
680
  never closes a tab it did not open. The trust its harnesses write into your
677
681
  `~/.codex/config.toml` and `~/.claude.json` for the run's own throwaway folders
678
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.
679
685
 
680
686
  They run on the machine the command is typed on, so the command will not start
681
687
  them by itself. On its own it says what it is about to drive — whose machine,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assuredloop/orca-bot-kit",
3
- "version": "0.26.0",
3
+ "version": "0.26.1",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -213,8 +213,11 @@ 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` and `obk init`. Never write a permission rule into a settings or rules
217
- file by hand.
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.
218
221
 
219
222
  The charter decides what else the bot needs. What it lets the bot do without
220
223
  asking, such as merging pull requests, auto mode or Codex's sandbox can still
package/src/bot.js CHANGED
@@ -162,7 +162,7 @@ export function readBot(home, name = path.basename(home)) {
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
164
  // The widest approval its temporary sessions may be made at, the user's yes
165
- // kept by `permission approval --temps` (ADR 0037).
165
+ // kept by `permission approval --temps` (ADR 0041).
166
166
  ...(bot.temp_approval === undefined ? {} : { temp_approval: bot.temp_approval }),
167
167
  // A paused bot is one `obk up` leaves closed; its sessions keep their book.
168
168
  ...(bot.paused === true ? { paused: true } : {}),
@@ -362,7 +362,7 @@ export function changeSession(bots, bot, name, settings) {
362
362
 
363
363
  /**
364
364
  * Record the widest approval the bot's temporary sessions may be made at,
365
- * `level`, as `temp_approval` (ADR 0037). Returns { bot, home, level }.
365
+ * `level`, as `temp_approval` (ADR 0041). Returns { bot, home, level }.
366
366
  */
367
367
  export function setTempApproval(bots, bot, level) {
368
368
  editBot(bots, bot, `let ${bot}'s temporary sessions be made at ${level}`, (doc) => doc.set('temp_approval', level), (was) => ({ ...was, temp_approval: level }));
package/src/cli.js CHANGED
@@ -909,7 +909,7 @@ const commands = {
909
909
  throw new Error(`bot change does not change a bot's harness: its sessions' conversations belong to the harness they ran on. To move to ${values.harness}, give it a session on ${values.harness} with obk session add, or retire the bot with obk retire and create a new one.`);
910
910
  }
911
911
  // Permission changes have commands of their own, which keep the user's yes
912
- // (ADR 0037): the old spelling refuses, and changes nothing.
912
+ // (ADR 0041): the old spelling refuses, and changes nothing.
913
913
  const moved = [['allow', 'permission allow'], ['disallow', 'permission disallow']].filter(([flag]) => values[flag] !== undefined);
914
914
  if (moved.length > 0) {
915
915
  throw new Error(`bot change no longer takes ${moved.map(([flag]) => `--${flag}`).join(' or ')}: permission rules change through ${moved.map(([, now]) => `${shellWord(ownCli())} ${now} --bots ${shellWord(bots)} --bot ${values.bot} --rule <rule>`).join(' and ')}, after the user's yes. Nothing was changed.`);
@@ -1116,7 +1116,7 @@ const commands = {
1116
1116
  ...(values.effort === undefined ? {} : { effort: values.effort }),
1117
1117
  ...(values['extra-arg'] === undefined ? {} : { extra_args: values['extra-arg'] }),
1118
1118
  };
1119
- // A run's launch may not widen its approval (ADR 0037).
1119
+ // A run's launch may not widen its approval (ADR 0041).
1120
1120
  if (values['run-on'] === 'codex') refuseApprovalArgs('codex', values['extra-arg']);
1121
1121
  const groom = grooming(bots, { at: values.at, ask, run });
1122
1122
  return { answer: { bots, groom }, lines: groomLines(groom, bots) };
@@ -1407,7 +1407,7 @@ function workDirFound(values, bot, home, session) {
1407
1407
 
1408
1408
  /**
1409
1409
  * Refuse what would change a session's approval through `session add` or
1410
- * `session change` (ADR 0037): `--approval`, and an extra argument that sets
1410
+ * `session change` (ADR 0041): `--approval`, and an extra argument that sets
1411
1411
  * it on the session's harness. Only `permission approval` changes it.
1412
1412
  */
1413
1413
  function refuseApproval(bots, values, command) {
@@ -1787,17 +1787,22 @@ function charterRulesLines(bots, bot, rules) {
1787
1787
 
1788
1788
  /**
1789
1789
  * What became of each bot's permission rules: each of the kit's default rules
1790
- * the run added to the bot, word for word (ADR 0036), and the file the allowed
1791
- * ones were written into. Nothing for a bot with nothing added or written.
1790
+ * the run added to the bot, word for word (ADR 0036), each rule of a command no
1791
+ * longer in the set that the bot still holds, with the command that takes it
1792
+ * back (ADR 0041), and the file the allowed ones were written into. Nothing for
1793
+ * a bot with nothing added, held or written.
1792
1794
  */
1793
1795
  function permissionsLines(permissions, bots) {
1794
1796
  // A bot on both harnesses has an entry for each file, with the same
1795
- // defaults: they are listed once, with the first.
1797
+ // defaults and held rules: they are listed once, with the first.
1796
1798
  const listed = new Set();
1799
+ const disallow = (bot, rule) => `${shellWord(ownCli())} permission disallow --bots ${shellWord(bots)} --bot ${shellWord(bot)} --rule ${shellWord(rule)}`;
1797
1800
  return permissions.flatMap((entry) => [
1798
1801
  ...(entry.trouble === undefined ? [] : [`${'refused'.padEnd(9)} ${entry.trouble}`]),
1799
- ...((entry.defaults ?? []).length === 0 || listed.has(entry.bot) || !listed.add(entry.bot) ? []
1800
- : entry.defaults.map((rule) => `${'allowed'.padEnd(9)} ${entry.bot} ${rule}`)),
1802
+ ...(listed.has(entry.bot) || !listed.add(entry.bot) ? [] : [
1803
+ ...(entry.defaults ?? []).map((rule) => `${'allowed'.padEnd(9)} ${entry.bot} ${rule}`),
1804
+ ...(entry.held ?? []).map((rule) => `${'holds'.padEnd(9)} ${entry.bot} ${rule}, which is no longer one of the kit's defaults. To take it back: ${disallow(entry.bot, rule)}`),
1805
+ ]),
1801
1806
  ...(entry.written.length === 0
1802
1807
  ? []
1803
1808
  : [`${'wrote'.padEnd(9)} ${path.relative(bots, entry.file)} ${entry.written.length} permission rule${entry.written.length === 1 ? '' : 's'} from its allow list`]),
package/src/commands.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // The kit's commands, each with the flags it cannot do without, which `obk`
2
2
  // reads to parse a command line; and the two lists every command is on one of:
3
3
  // those every bot is allowed by default (#527, ADR 0036), and those kept back
4
- // for the user's yes (ADR 0037). A new command is on neither until someone
4
+ // for the user's yes (ADR 0041). A new command is on neither until someone
5
5
  // puts it on one, and a test holds every command to that choice.
6
6
 
7
7
  /** The commands, and the flags each one cannot do without. */
@@ -56,8 +56,8 @@ export const COMMANDS = {
56
56
  * runs whatever its arguments say never is.
57
57
  */
58
58
  export const DEFAULT_COMMANDS = [
59
- 'up', 'restart', 'unpause', 'health', 'groom', 'roster', 'usage',
60
- 'bot create', 'bot change', 'rules build',
59
+ 'up', 'unpause', 'health', 'groom', 'roster', 'usage',
60
+ 'bot change', 'rules build',
61
61
  'skills add', 'skills remove', 'skills build', 'skills fetch', 'skills update', 'source add',
62
62
  'session add', 'session change', 'session clear', 'session compact',
63
63
  'message to', 'message send', 'message check',
@@ -68,7 +68,17 @@ export const DEFAULT_COMMANDS = [
68
68
 
69
69
  /**
70
70
  * The commands kept out of the default permission set, which keep the user's
71
- * yes (ADR 0037): the ones that change permissions, close a long-lived
72
- * session, or make the bots folder.
71
+ * yes (ADR 0041): the ones that change permissions, close a long-lived
72
+ * session, make the bots folder, or make a bot, which the owner leaves to Bot
73
+ * Father (#548).
73
74
  */
74
- export const KEPT_BACK = ['init', 'retire', 'pause', 'permission allow', 'permission disallow', 'permission approval'];
75
+ export const KEPT_BACK = [
76
+ 'init', 'retire', 'pause', 'restart', 'bot create',
77
+ 'permission allow', 'permission disallow', 'permission approval',
78
+ ];
79
+
80
+ /**
81
+ * The commands kit 0.26.0 gave every bot by default and that are kept back now
82
+ * (#548). A bot may still hold their rules, which the kit names and leaves.
83
+ */
84
+ export const NO_LONGER_DEFAULT = ['restart', 'bot create'];
package/src/health.js CHANGED
@@ -213,7 +213,7 @@ function sessionSettings(home, bot) {
213
213
  /** What is wrong with the roles the bot's temporary sessions are made in (#465). */
214
214
  function rolesTrouble(home, bot) {
215
215
  const where = path.join(home, 'bot.yaml');
216
- // The widest approval they may be made at, the user's yes (ADR 0037).
216
+ // The widest approval they may be made at, the user's yes (ADR 0041).
217
217
  const level = bot.temp_approval;
218
218
  const approval = level === undefined || APPROVALS.includes(level) ? [] : [{
219
219
  where,
package/src/launch.js CHANGED
@@ -45,7 +45,7 @@ export const approvalRank = (level) => ['ask', 'auto', 'dangerously-skip'].index
45
45
 
46
46
  /**
47
47
  * The harness flags that set approval or permissions, which only
48
- * `obk permission approval` may change, not an extra argument (ADR 0037).
48
+ * `obk permission approval` may change, not an extra argument (ADR 0041).
49
49
  * Codex's `-a` and `-s` take their value glued on too.
50
50
  */
51
51
  const APPROVAL_FLAGS = {
@@ -24,7 +24,7 @@ import { accessSync, constants, existsSync, mkdirSync, readdirSync, readFileSync
24
24
  import path from 'node:path';
25
25
 
26
26
  import { allowRules, leadsOutside } from './bot.js';
27
- import { DEFAULT_COMMANDS } from './commands.js';
27
+ import { DEFAULT_COMMANDS, NO_LONGER_DEFAULT } from './commands.js';
28
28
  import { readSettings } from './hooks.js';
29
29
  import { harnessOf, ownCli, shellWord } from './launch.js';
30
30
 
@@ -37,7 +37,7 @@ const CODEX_FILE = `${CODEX_RULES}/obk.rules`;
37
37
 
38
38
  /**
39
39
  * The rules every bot is given (ADR 0036): each of the kit's default commands,
40
- * and none kept back for the user's yes (ADR 0037), narrowed to this bots folder and
40
+ * and none kept back for the user's yes (ADR 0041), narrowed to this bots folder and
41
41
  * spelled as the kit prints them, which is what a bot runs; reading a long
42
42
  * message; committing; and the check Orca's mail notice tells a session to
43
43
  * run. `//` is Claude's form for an absolute path.
@@ -54,6 +54,18 @@ export function defaultRules(bots, cli = ownCli()) {
54
54
  ];
55
55
  }
56
56
 
57
+ /**
58
+ * The rules of the commands that were defaults once and are kept back now
59
+ * (#548), spelled as `defaultRules` spelled them, that the bot's `allow`
60
+ * holds. The kit cannot tell its own old entry from a user's yes to the same
61
+ * rule, so it names them and leaves them (ADR 0041).
62
+ */
63
+ export function heldRules(bots, home, bot, cli = ownCli()) {
64
+ const allowed = allowOf(home, bot);
65
+ return NO_LONGER_DEFAULT.map((command) => `Bash(${shellWord(cli)} ${command} --bots ${shellWord(bots)}:*)`)
66
+ .filter((rule) => allowed.includes(rule));
67
+ }
68
+
57
69
  /** Command wrappers: programs that run the command their arguments name. */
58
70
  const WRAPPERS = new Set(['env', 'xargs', 'sudo', 'eval', 'exec', 'nohup', 'timeout', 'nice', 'command', 'time', 'watch']);
59
71
 
@@ -261,26 +273,30 @@ export function refuseNoCodexForm(bot, rules) {
261
273
  * Add the default rules the bot is not allowed yet to its `allow` (ADR 0036),
262
274
  * then write what it is allowed into the files of the harnesses it runs on:
263
275
  * one entry per file, Claude's first, each with `defaults`, the rules this run
264
- * added. The harness files are written first and bot.yaml last, as
276
+ * added, and `held`, the rules of commands no longer in the set that the bot
277
+ * still holds (#548). The harness files are written first and bot.yaml last, as
265
278
  * `permission allow` writes them (#383). With `keepGoing`, a file the kit may
266
279
  * not write is an entry with its `trouble` rather than a throw.
267
280
  */
268
281
  export function writePermissions(bots, home, bot, { keepGoing = false } = {}) {
269
282
  const writers = [[runsOnClaude, FILE], [runsOnCodex, CODEX_FILE]].filter(([runs]) => runs(bot));
270
283
  let defaults = [];
284
+ // Read before anything is written, so a file's trouble still names them.
285
+ let held = [];
271
286
  try {
287
+ held = heldRules(bots, home, bot);
272
288
  defaults = writers.length === 0 ? [] : missingDefaults(bots, home, bot);
273
289
  if (defaults.length > 0) {
274
290
  const { allow } = allowRules(bots, bot.name, defaults, { write: false });
275
291
  const entries = allowIn(bots, home, { ...bot, allow })();
276
292
  allowRules(bots, bot.name, defaults);
277
- return entries.map((entry) => ({ ...entry, defaults }));
293
+ return entries.map((entry) => ({ ...entry, defaults, held }));
278
294
  }
279
295
  return writers.map(([runs]) => (runs === runsOnClaude ? planClaude : planCodex)(bots, home, bot).write())
280
- .map((entry) => ({ ...entry, defaults }));
296
+ .map((entry) => ({ ...entry, defaults, held }));
281
297
  } catch (error) {
282
298
  if (!keepGoing) throw error;
283
- return writers.map(([, file]) => ({ bot: bot.name, file: path.join(home, file), written: [], defaults: [], trouble: error.message }));
299
+ return writers.map(([, file]) => ({ bot: bot.name, file: path.join(home, file), written: [], defaults: [], held, trouble: error.message }));
284
300
  }
285
301
  }
286
302
 
package/src/temp.js CHANGED
@@ -178,7 +178,7 @@ function settingsFor(bot, maker, option, given) {
178
178
  /**
179
179
  * Refuse an approval wider than the widest of the maker's own and the bot's
180
180
  * `temp_approval`, which only the user's yes through `permission approval
181
- * --temps` widens (ADR 0037).
181
+ * --temps` widens (ADR 0041).
182
182
  */
183
183
  function refuseWiderApproval(bots, caller, bot, maker, asked) {
184
184
  if (asked === undefined) return;