@assuredloop/orca-bot-kit 0.9.0 → 0.11.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 +8 -0
- package/SETUP.md +22 -16
- package/package.json +1 -1
- package/rules/limits.md +4 -0
- package/rules/mail.md +1 -2
- package/skills/obk-bot-building/SKILL.md +54 -13
- package/src/book.js +60 -4
- package/src/bot.js +40 -1
- package/src/cli.js +161 -34
- package/src/health.js +2 -0
- package/src/hooks.js +2 -2
- package/src/message.js +83 -11
- package/src/orca-reload.applescript +45 -0
- package/src/orca-runtime.cjs +28 -0
- package/src/orca.js +123 -26
- package/src/permissions.js +485 -0
- package/src/retire.js +12 -25
- package/src/rules.js +16 -1
- package/src/up.js +59 -18
- package/src/orca-window.cjs +0 -21
package/README.md
CHANGED
|
@@ -543,6 +543,14 @@ runs `obk session mailbox` before the harness. For the same reason a session's
|
|
|
543
543
|
mail is read in its own tab. `message check` from any other tab reads nothing,
|
|
544
544
|
binds nothing, and says so; the mail waits.
|
|
545
545
|
|
|
546
|
+
For one session, that step, a `message check`, and `up` writing a new tab for it
|
|
547
|
+
into the book take turns. So two starts of the same session at once leave its
|
|
548
|
+
mailbox bound to the tab the book names, and no mail is read in the other one.
|
|
549
|
+
Each waits a minute at most for its turn. A step that waits it out leaves the
|
|
550
|
+
mailbox where it was and says so, and the harness starts all the same. A check
|
|
551
|
+
holds its turn for 40 seconds at most: with more mail than it reads in that
|
|
552
|
+
time, it shows what it read and says the rest is still waiting.
|
|
553
|
+
|
|
546
554
|
Nothing in a mailbox wakes anybody, so `message send` also types one line into
|
|
547
555
|
the receiver's tab telling it to look. Both harnesses take a typed line as the
|
|
548
556
|
next thing to do rather than cutting into what they are doing, which is the
|
package/SETUP.md
CHANGED
|
@@ -125,9 +125,14 @@ This makes the folder a git repository, seeds it, writes Bot Father, and opens
|
|
|
125
125
|
its two tabs in Orca: the management tab where they will talk to it, and an ops
|
|
126
126
|
tab, which is a plain shell for work across the whole fleet.
|
|
127
127
|
|
|
128
|
-
Read everything it prints.
|
|
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 says wait for their yes**, when Bot Father runs on
|
|
132
|
+
Claude Code: its mail through the kit, reading a long message beside the
|
|
133
|
+
bots folder, and its commits, so auto mode does not stop it for those. Show
|
|
134
|
+
them the rules word for word and ask. Only on a yes, run the one command
|
|
135
|
+
`init` printed for them; on a no, run nothing, and nothing is written;
|
|
131
136
|
- **anything it says about Orca's own default launch arguments.** Orca adds
|
|
132
137
|
these to every agent it launches, relaunches and resumes. When they carry a
|
|
133
138
|
permission bypass, every session runs in that mode whatever approval level the
|
|
@@ -186,19 +191,21 @@ What you will see, and the usual answer. Each goes as one
|
|
|
186
191
|
text and **no** `--enter`, because a menu takes a return as the keypress it is
|
|
187
192
|
waiting for:
|
|
188
193
|
|
|
189
|
-
| On screen | Send | Which is |
|
|
190
|
-
|
|
191
|
-
| Claude Code's folder trust list | `\x1b[B\r` | down, then return: its selection starts on **No, exit** |
|
|
192
|
-
| Codex's directory trust, `1. Trust and continue` (older: `1. Yes, continue`) |
|
|
193
|
-
| Codex's `Hooks need review` |
|
|
194
|
-
| Codex's `/new`: `Where should the new conversation run?` |
|
|
195
|
-
| Codex's update offer, `1. Update now` |
|
|
196
|
-
| `[oh-my-zsh] Would you like to update?` | `n` | they update their own shell |
|
|
197
|
-
| Claude Code's `Teach auto mode about your environment?` | `
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
194
|
+
| On screen | Send | Which is | Proven on |
|
|
195
|
+
|---|---|---|---|
|
|
196
|
+
| Claude Code's folder trust list | `\x1b[B\r` | down, then return: its selection starts on **No, exit** | Claude Code 2.1.283 |
|
|
197
|
+
| Codex's directory trust, `1. Trust and continue` (older: `1. Yes, continue`) | `\r` | return: its selection starts on **Trust and continue** | Codex 0.157.1 |
|
|
198
|
+
| Codex's `Hooks need review` | `\x1b[B\r` | down, then return, to **Trust all and continue**; if the selection already starts there, return alone | Codex 0.157.1 |
|
|
199
|
+
| Codex's `/new`: `Where should the new conversation run?` | `\r` | return: its selection starts on **Current checkout** (bot home) | Codex 0.157.1 |
|
|
200
|
+
| Codex's update offer, `1. Update now` | `\r` | return: its selection starts on **Update now**, which accepts it | Codex 0.156.1; 0.157.1 has had no newer release to offer |
|
|
201
|
+
| `[oh-my-zsh] Would you like to update?` | `n` | they update their own shell | oh-my-zsh bf77e35 |
|
|
202
|
+
| Claude Code's `Teach auto mode about your environment?` | `\x1b` | Esc, which is **Not now**: it would write settings of theirs | Not yet proven: Esc is Not now in Claude Code 2.1.283's code; last seen live on 2.1.278, answered with 2. Not now |
|
|
203
|
+
|
|
204
|
+
Each key counts on where the selection starts, as it did on the version named.
|
|
205
|
+
Read the screen in front of you before you send: if the selection is somewhere
|
|
206
|
+
else, or the options have moved, go to the answer with the arrows and then
|
|
207
|
+
return. Never send a digit and then return: on Codex's update offer that took
|
|
208
|
+
the highlighted **Update now**, whatever the digit was.
|
|
202
209
|
|
|
203
210
|
Three things to know rather than guess at.
|
|
204
211
|
|
|
@@ -210,8 +217,7 @@ empty book and nothing says why.
|
|
|
210
217
|
Codex's trust question applies to the **repository root**, which for a bot means
|
|
211
218
|
the whole bots folder rather than the one bot. That is what they are agreeing to.
|
|
212
219
|
|
|
213
|
-
Codex's `/new` question (seen on 0.156.1
|
|
214
|
-
seen so far) is always answered with the current checkout, which is the bot
|
|
220
|
+
Codex's `/new` question (seen on 0.156.1 and 0.157.1) is always answered with the current checkout, which is the bot
|
|
215
221
|
home. The kit never makes a git worktree, so
|
|
216
222
|
`2. New worktree` is never the answer.
|
|
217
223
|
|
package/package.json
CHANGED
package/rules/limits.md
CHANGED
|
@@ -16,3 +16,7 @@ publishing, releasing, spending money, anything another person receives.
|
|
|
16
16
|
|
|
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
|
+
|
|
20
|
+
Permission rules in a harness's settings or rules files are the kit's to write,
|
|
21
|
+
after the user's yes to the exact rules: `obk bot change --allow <rule>` records
|
|
22
|
+
and writes them. Never edit them into such a file yourself, Bot Father included.
|
package/rules/mail.md
CHANGED
|
@@ -6,8 +6,7 @@ applies: all
|
|
|
6
6
|
|
|
7
7
|
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
|
-
answers with the address and the command that carries it.
|
|
10
|
-
two levels above the folder you start in.
|
|
9
|
+
answers with the address and the command that carries it.
|
|
11
10
|
|
|
12
11
|
Mail is queued, not an interruption. Read it when you finish what you are on.
|
|
13
12
|
|
|
@@ -201,6 +201,39 @@ say that you edited it by hand.
|
|
|
201
201
|
Run `obk --help` for the flags of the version actually installed rather than
|
|
202
202
|
trusting a line you remember.
|
|
203
203
|
|
|
204
|
+
A bot needs a few permission rules so that auto mode, or Codex's sandbox and
|
|
205
|
+
reviewer, does not stop it for what the kit's own rules tell it to do: its
|
|
206
|
+
mail through the kit, reading a long message kept beside the bots folder, and
|
|
207
|
+
its commits. `obk bot create`, `obk up` and `obk rules build` list the rules
|
|
208
|
+
still waiting for a bot and give one command that allows them. Show the user
|
|
209
|
+
those rules word for word and say in a line what each lets the bot do. Run the
|
|
210
|
+
command only after their yes: it records the yes in the bot's `bot.yaml` and
|
|
211
|
+
writes the rules into its settings, or for Codex into its
|
|
212
|
+
`.codex/rules/obk.rules`. On a no, run nothing, and nothing is written. When
|
|
213
|
+
the kit lists new rules for a bot later, ask again. Never write a permission
|
|
214
|
+
rule into a settings or rules file by hand.
|
|
215
|
+
|
|
216
|
+
The charter decides what else the bot needs. What it lets the bot do without
|
|
217
|
+
asking, such as merging pull requests, auto mode or Codex's sandbox can still
|
|
218
|
+
stop, so each grant needs its own rule. When the charter is written, and again
|
|
219
|
+
whenever it changes, list the exact rules it grants: `Bash(gh pr merge:*)` for
|
|
220
|
+
a bot that merges pull requests, `Bash(gh issue close:*)` for one that closes
|
|
221
|
+
issues. Spell each command as the bot will run it, the kit by the path the
|
|
222
|
+
bot's rules give it and every folder absolute, or the rule will not match.
|
|
223
|
+
Spell them in Claude Code's form for a Codex bot too, `Bash(<command>:*)`: the
|
|
224
|
+
kit writes Codex's own form of each, and says which ones Codex has none for,
|
|
225
|
+
such as an `Edit` rule or one exact command.
|
|
226
|
+
Show the user the list word for word, with a line on what each lets the bot do,
|
|
227
|
+
and only after their yes run `obk bot change --allow`, once per rule they said
|
|
228
|
+
yes to. On a no, run nothing. `obk bot change --charter` names the rules the
|
|
229
|
+
bot is allowed now; say which of them the new charter no longer grants, and
|
|
230
|
+
that they stay allowed until the user takes them out, since the kit takes none
|
|
231
|
+
out. Keep
|
|
232
|
+
each rule narrow: a program and what it does. The kit refuses a rule that lets
|
|
233
|
+
the bot run any command, a program with any arguments, or a shell or
|
|
234
|
+
interpreter with any arguments; a user who wants one for a bot adds it to that
|
|
235
|
+
bot's settings themselves. Propose no rule for what the charter does not grant.
|
|
236
|
+
|
|
204
237
|
A bot that should learn across its sessions can use its harness's own memory,
|
|
205
238
|
turned on for that bot alone, whatever the user's own setting is for everything
|
|
206
239
|
else. The harness writes it, loads it and keeps it tidy itself. On Claude Code
|
|
@@ -245,23 +278,31 @@ otherwise `/Applications/Orca.app/Contents/Resources/bin/orca`. A bare
|
|
|
245
278
|
`orca` can fail. Send each answer as one `<orca> terminal send --terminal
|
|
246
279
|
<handle> --text …`, with the return inside the text and no `--enter`:
|
|
247
280
|
|
|
248
|
-
| On screen | Send | Which is |
|
|
249
|
-
|
|
250
|
-
| Claude Code's
|
|
251
|
-
| Codex's `Trust this folder
|
|
252
|
-
| Codex's `Hooks need review` |
|
|
253
|
-
| Codex's
|
|
254
|
-
| Codex's update offer, `1. Update now` |
|
|
255
|
-
| `[oh-my-zsh] Would you like to update?` | `n` | no: the
|
|
256
|
-
| Claude Code's `Teach auto mode
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
281
|
+
| On screen | Send | Which is | Proven on |
|
|
282
|
+
|---|---|---|---|
|
|
283
|
+
| Claude Code's trust list | `\x1b[B\r` | down, return: off **No, exit** | Claude Code 2.1.283 |
|
|
284
|
+
| Codex's `Trust this folder?` | `\r` | return: it starts on yes | Codex 0.157.1 |
|
|
285
|
+
| Codex's `Hooks need review` | `\x1b[B\r` | **Trust all and continue** | Codex 0.157.1 |
|
|
286
|
+
| Codex's `Where should the new conversation run?` | `\r` | **Current checkout** | Codex 0.157.1 |
|
|
287
|
+
| Codex's update offer, `1. Update now` | `\r` | **Update now**, where it starts | Codex 0.156.1 |
|
|
288
|
+
| `[oh-my-zsh] Would you like to update?` | `n` | no: the user updates it | oh-my-zsh bf77e35 |
|
|
289
|
+
| Claude Code's `Teach auto mode…` | `\x1b` | Esc, **Not now** | Not yet proven, below |
|
|
290
|
+
|
|
291
|
+
Each key counts on where the selection starts on the version named. Read the
|
|
292
|
+
screen before you send: if the selection is elsewhere or the options have
|
|
293
|
+
moved, go to the answer with the arrows, then return. Never a digit and then
|
|
294
|
+
return: on Codex's update offer that took the highlighted **Update now**,
|
|
295
|
+
whatever the digit was. Codex's trust said `1. Yes, continue` on older
|
|
296
|
+
versions. Its hooks review sometimes starts on `2` already: then return alone.
|
|
297
|
+
0.157.1 has had no newer release to offer, so its update offer was proven on
|
|
298
|
+
0.156.1. Claude Code's `Teach auto mode` has not been seen live on 2.1.283:
|
|
299
|
+
Esc is Not now in its code, and 2.1.278 was answered with `2. Not now`.
|
|
300
|
+
Not now, because Yes writes the user's settings. Codex's hooks
|
|
260
301
|
question matters most. The kit's hook is how the book learns which
|
|
261
302
|
conversation the session is running, and until it is answered the
|
|
262
303
|
conversation has not started. Codex's trust applies to the repository root,
|
|
263
304
|
which is the whole bots folder. Codex's `/new` may ask where the new
|
|
264
|
-
conversation runs (0.156.1
|
|
305
|
+
conversation runs (0.156.1 and 0.157.1 both did); the answer is
|
|
265
306
|
always the current checkout, the bot home, and never `2. New worktree`:
|
|
266
307
|
the kit never makes a git worktree.
|
|
267
308
|
|
package/src/book.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
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 { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, writeFileSync } from 'node:fs';
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import { DatabaseSync } from 'node:sqlite';
|
|
12
12
|
import { isDeepStrictEqual } from 'node:util';
|
|
@@ -141,18 +141,26 @@ export async function updateBook(home, change) {
|
|
|
141
141
|
* empty and leaves no journal beside it.
|
|
142
142
|
*/
|
|
143
143
|
function takeLock(home) {
|
|
144
|
-
|
|
144
|
+
try {
|
|
145
|
+
return lockOn(lockFile(home), WAIT_MS);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
throw waitedTooLong(home, error);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** The lock on `file`, waited for `waitMs` at most: see `takeLock` for why it is this one. */
|
|
152
|
+
function lockOn(file, waitMs) {
|
|
145
153
|
mkdirSync(path.dirname(file), { recursive: true });
|
|
146
154
|
|
|
147
155
|
const db = new DatabaseSync(file);
|
|
148
156
|
try {
|
|
149
157
|
// A writer that arrives while another is working waits for it rather than
|
|
150
158
|
// failing at once, and gives up saying so rather than waiting for ever.
|
|
151
|
-
db.exec(`PRAGMA busy_timeout = ${
|
|
159
|
+
db.exec(`PRAGMA busy_timeout = ${waitMs}`);
|
|
152
160
|
db.exec('BEGIN IMMEDIATE');
|
|
153
161
|
} catch (error) {
|
|
154
162
|
db.close();
|
|
155
|
-
throw
|
|
163
|
+
throw error;
|
|
156
164
|
}
|
|
157
165
|
|
|
158
166
|
return {
|
|
@@ -190,6 +198,54 @@ const lockFile = (home) => path.join(
|
|
|
190
198
|
`${encodeURIComponent(path.basename(home))}.lock`,
|
|
191
199
|
);
|
|
192
200
|
|
|
201
|
+
/**
|
|
202
|
+
* One session's turn at its mailbox (#321): held by `session mailbox` for its
|
|
203
|
+
* whole step, by `message check` in the session's own tab for its bind, read and
|
|
204
|
+
* ack, and by `up` while it writes a new tab for the session into the book. So
|
|
205
|
+
* the book never moves to another tab while a step or a check is at work, and a
|
|
206
|
+
* step or check that waited reads the book again once it has its turn: two
|
|
207
|
+
* starts of one session at once leave its mailbox bound to the tab the book
|
|
208
|
+
* names, and nothing is read from the one it no longer does.
|
|
209
|
+
*
|
|
210
|
+
* A lock of its own, and not the book's: the book's is held for one read and one
|
|
211
|
+
* write, and a session's hook must never wait behind Orca for it. Taken before
|
|
212
|
+
* the book's and never while holding it, so neither waits on the other.
|
|
213
|
+
*
|
|
214
|
+
* Returns `{ release }`, or undefined when the turn did not come in
|
|
215
|
+
* `MAILBOX_WAIT_MS`: the caller says what it left, and a launch line goes on
|
|
216
|
+
* to its harness whatever happened here.
|
|
217
|
+
*/
|
|
218
|
+
export function takeMailboxTurn(home, session) {
|
|
219
|
+
try {
|
|
220
|
+
return lockOn(mailboxLockFile(home, session), MAILBOX_WAIT_MS);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
if (error.errcode === SQLITE_BUSY) return undefined;
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* How long a step, a check or an `up` waits for a session's turn. Longer than a
|
|
229
|
+
* step can hold it — three Orca calls of twenty seconds each at most — and short
|
|
230
|
+
* enough that a harness behind a stuck one still starts.
|
|
231
|
+
*/
|
|
232
|
+
export const MAILBOX_WAIT_MS = 60_000;
|
|
233
|
+
|
|
234
|
+
/** SQLite's own code for a lock another connection holds. */
|
|
235
|
+
const SQLITE_BUSY = 5;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Beside the book's lock, one file per session. Names are lower-case letters,
|
|
239
|
+
* digits and hyphens, so a dot between the bot's and the session's cannot be
|
|
240
|
+
* read two ways. Resolved first, because the same bot is reached through a
|
|
241
|
+
* link by one command and by its real path by another, and two paths would be
|
|
242
|
+
* two turns.
|
|
243
|
+
*/
|
|
244
|
+
const mailboxLockFile = (home, session) => {
|
|
245
|
+
const real = realpathSync(home);
|
|
246
|
+
return path.join(`${path.dirname(path.dirname(real))}.locks`, `${path.basename(real)}.${session}.mailbox.lock`);
|
|
247
|
+
};
|
|
248
|
+
|
|
193
249
|
/**
|
|
194
250
|
* Something else has been writing the book for longer than this run is prepared
|
|
195
251
|
* to wait. Every writer holds its turn for one read and one write, so this is a
|
package/src/bot.js
CHANGED
|
@@ -136,6 +136,9 @@ export function readBot(home, name = path.basename(home)) {
|
|
|
136
136
|
// and a list the build cannot follow is that build's to report (PRD 6.6).
|
|
137
137
|
charter: bot.charter,
|
|
138
138
|
rules: bot.rules,
|
|
139
|
+
// The permission rules the user said yes to, judged where they are written
|
|
140
|
+
// (src/permissions.js), as `rules` is judged by the build.
|
|
141
|
+
allow: bot.allow,
|
|
139
142
|
// A paused bot is one `obk up` leaves closed; its sessions keep their book.
|
|
140
143
|
...(bot.paused === true ? { paused: true } : {}),
|
|
141
144
|
sessions: sessions.map((session) => {
|
|
@@ -250,6 +253,42 @@ export function changeBot(bots, bot, { charter }) {
|
|
|
250
253
|
return { bot, home: botDir(bots, bot), charter: text };
|
|
251
254
|
}
|
|
252
255
|
|
|
256
|
+
/**
|
|
257
|
+
* The bot's `allow` list as it is, after refusing what `allowRules` would refuse:
|
|
258
|
+
* an empty rule, or a list already there that is not a list of rules. Asked
|
|
259
|
+
* before anything is written, so a refused `bot change` changes nothing, its
|
|
260
|
+
* charter included.
|
|
261
|
+
*/
|
|
262
|
+
export function allowedNow(bots, bot, rules) {
|
|
263
|
+
if (rules.some((rule) => rule.trim() === '')) {
|
|
264
|
+
throw new Error('--allow is empty. Give it the exact permission rule the user said yes to, such as Bash(git add:*).');
|
|
265
|
+
}
|
|
266
|
+
const home = existingBot(bots, bot);
|
|
267
|
+
const was = readBot(home, bot).allow ?? [];
|
|
268
|
+
if (!Array.isArray(was) || was.some((rule) => typeof rule !== 'string' || rule.trim() === '')) {
|
|
269
|
+
throw new Error(`the allow entry in ${path.join(home, BOT_YAML)} is not a list of permission rules, so nothing was written. Fix it, then run the command again.`);
|
|
270
|
+
}
|
|
271
|
+
return { home, was };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Add the permission rules the user said yes to, `rules`, to the bot's `allow`,
|
|
276
|
+
* after the ones already there and never twice. Returns { bot, home, allow,
|
|
277
|
+
* added }: the whole list now, and what this call put in it.
|
|
278
|
+
*
|
|
279
|
+
* Only the list: the rules are written into the harness's settings by the
|
|
280
|
+
* caller, as the rules build is run by the caller of `changeBot`.
|
|
281
|
+
*/
|
|
282
|
+
export function allowRules(bots, bot, rules) {
|
|
283
|
+
const { home, was } = allowedNow(bots, bot, rules);
|
|
284
|
+
const added = [...new Set(rules)].filter((rule) => !was.includes(rule));
|
|
285
|
+
const allow = [...was, ...added];
|
|
286
|
+
if (added.length > 0) {
|
|
287
|
+
editBot(bots, bot, `allow ${added.join(', ')} for ${bot}`, (doc) => doc.set('allow', allow), (before) => ({ ...before, allow }));
|
|
288
|
+
}
|
|
289
|
+
return { bot, home, allow, added };
|
|
290
|
+
}
|
|
291
|
+
|
|
253
292
|
/**
|
|
254
293
|
* Change the settings of the session `name` of the bot `bot`. Returns
|
|
255
294
|
* { bot, home, session } — the session as it now reads.
|
|
@@ -414,7 +453,7 @@ export const SESSION_FIELDS = [
|
|
|
414
453
|
];
|
|
415
454
|
|
|
416
455
|
/** Everything the top of a bot's own file can hold. */
|
|
417
|
-
const BOT_FIELDS = ['name', 'harness', 'charter', 'rules', 'skills', 'sessions', 'paused'];
|
|
456
|
+
const BOT_FIELDS = ['name', 'harness', 'charter', 'rules', 'skills', 'allow', 'sessions', 'paused'];
|
|
418
457
|
|
|
419
458
|
/**
|
|
420
459
|
* The keys in a bot's file that the kit does not know, and so that nothing
|