@assuredloop/orca-bot-kit 0.6.0 → 0.8.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 +30 -2
- package/SETUP.md +3 -2
- package/package.json +1 -1
- package/rules/commit.md +18 -0
- package/skills/obk-bot-building/SKILL.md +6 -2
- package/skills/obk-fleet-review/SKILL.md +2 -1
- package/skills/obk-grooming/NOTICE.md +21 -5
- package/skills/obk-grooming/SKILL.md +49 -12
- package/src/book.js +2 -4
- package/src/bot.js +1 -1
- package/src/cli.js +92 -25
- package/src/conversations.js +7 -1
- package/src/groom.js +263 -105
- package/src/health.js +29 -7
- package/src/hooks.js +3 -3
- package/src/message.js +8 -6
- package/src/orca-window.cjs +1 -1
- package/src/orca.js +79 -7
- package/src/record.js +25 -11
- package/src/skills.js +1 -1
- package/src/up.js +8 -5
package/README.md
CHANGED
|
@@ -340,7 +340,7 @@ with why that one ended and when
|
|
|
340
340
|
|
|
341
341
|
The book stays true to the harness through a hook `obk up` puts in the bot's own
|
|
342
342
|
folder — `.claude/settings.json` or `.codex/hooks.json`, never your user-level
|
|
343
|
-
settings ([ADR
|
|
343
|
+
settings ([ADR 0022](docs/adr/0022-kit-hooks-live-in-the-bot-folder.md)). Your own
|
|
344
344
|
settings in those files are kept. Codex asks you to trust a hooks file the first
|
|
345
345
|
time it sees one; answer its question in the tab.
|
|
346
346
|
|
|
@@ -431,6 +431,31 @@ retiring it again finishes the job. Bot
|
|
|
431
431
|
Father itself, and its management session `daily`, are never paused or
|
|
432
432
|
retired; its other sessions are like any bot's.
|
|
433
433
|
|
|
434
|
+
## Daily grooming
|
|
435
|
+
|
|
436
|
+
Grooming is an optional daily pass over the fleet: what each bot did, what it
|
|
437
|
+
cost, and what to improve, sent as one short report to Bot Father's management
|
|
438
|
+
session. It runs in a session of Bot Father's called `grooming`, on Claude
|
|
439
|
+
Code's own scheduler, at the model and effort you give that session.
|
|
440
|
+
|
|
441
|
+
```sh
|
|
442
|
+
obk session add --bots /path/to/my-bots --bot bot-father --name grooming --model sonnet --effort medium
|
|
443
|
+
obk up --bots /path/to/my-bots --bot bot-father
|
|
444
|
+
obk groom --bots /path/to/my-bots --now # run it once, by hand, and read it
|
|
445
|
+
obk groom --bots /path/to/my-bots --on --at 04:00 # then schedule it
|
|
446
|
+
obk groom --bots /path/to/my-bots # what is scheduled
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
It starts off, and Bot Father asks you before it turns it on, because it spends
|
|
450
|
+
tokens every day. It fires only while its tab is open in Orca and Claude Code is
|
|
451
|
+
idle there, up to half an hour after its time, and a day it misses is not made
|
|
452
|
+
up. Each run renews its own schedule, which Claude Code would otherwise end
|
|
453
|
+
after a week, and `--on --at` puts it back if it lapses. A `/clear` in its tab
|
|
454
|
+
leaves it running until the session is next restarted; after that, `--on --at`
|
|
455
|
+
puts it back.
|
|
456
|
+
`obk groom --compact` compacts its conversation between runs. Grooming on Codex
|
|
457
|
+
comes later: on a Codex Bot Father, add the session with `--harness claude`.
|
|
458
|
+
|
|
434
459
|
## When something is wrong
|
|
435
460
|
|
|
436
461
|
```sh
|
|
@@ -545,6 +570,7 @@ failing quietly.
|
|
|
545
570
|
npm test # the whole suite, in a few minutes
|
|
546
571
|
npm run test:system # what the system tests would drive, and nothing else
|
|
547
572
|
npm run test:system -- --yes # drive them, on this machine, for real
|
|
573
|
+
npm run test:system -- --yes test/system/groom.test.js # only the ones you name
|
|
548
574
|
npm run mutate # the mutation audit, on what your branch changed
|
|
549
575
|
```
|
|
550
576
|
|
|
@@ -570,7 +596,9 @@ which Orca, which files, and what they will do to it — and then stops without
|
|
|
570
596
|
driving any of it, answering non-zero so that a run which did not run them
|
|
571
597
|
cannot be read as one that passed. `-- --yes` is how you say you meant it. When
|
|
572
598
|
Orca is not answering, the command says which Orca it asked and skips, rather
|
|
573
|
-
than report a kit that is not broken.
|
|
599
|
+
than report a kit that is not broken. Name files after it to run only those;
|
|
600
|
+
each must be one of the `*.test.js` files under `test/system/`, links followed,
|
|
601
|
+
and a name that is not is refused before anything runs.
|
|
574
602
|
|
|
575
603
|
`npm run mutate` runs [StrykerJS](https://stryker-mutator.io) over the
|
|
576
604
|
JavaScript this branch changed against `main` — committed, still in the working
|
package/SETUP.md
CHANGED
|
@@ -102,8 +102,9 @@ Ask both in one message, then wait.
|
|
|
102
102
|
|
|
103
103
|
**Where the bots folder goes.** An absolute path. It becomes a git repository of
|
|
104
104
|
theirs holding every bot's configuration: charters, rules, session settings, the
|
|
105
|
-
book of sessions. It holds none of the kit's code.
|
|
106
|
-
|
|
105
|
+
book of sessions. It holds none of the kit's code. The bots commit their own
|
|
106
|
+
changes to it; the kit's commands never commit, and pushing it somewhere is
|
|
107
|
+
theirs to decide.
|
|
107
108
|
|
|
108
109
|
**Which harness Bot Father runs on**, `claude` or `codex`. There is no default
|
|
109
110
|
and the kit will not guess. It is Bot Father's own harness only; each bot they
|
package/package.json
CHANGED
package/rules/commit.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: commit
|
|
3
|
+
title: Commit your changes
|
|
4
|
+
applies: all
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Commit your own changes to the bots repo often, as each piece of work there is
|
|
8
|
+
done: what you changed, in whatever folder it is, and what the kit wrote in
|
|
9
|
+
your own bot's folder (its files there and your book of sessions). Stage each
|
|
10
|
+
file by name and name them in the commit too (`git commit -- <files>`), so
|
|
11
|
+
nothing another session staged goes in with yours. What no bot owns (init's
|
|
12
|
+
files, the defaults, the user's rules and skill lists) is Bot Father's to
|
|
13
|
+
commit. Commit yours even when the repo has no commit yet or holds files that
|
|
14
|
+
are not yours; leave those to their owner.
|
|
15
|
+
|
|
16
|
+
Committing is not pushing: pushing is the user's call. A clone in your work dir
|
|
17
|
+
follows its own repo's rules. When a commit fails, for example on another
|
|
18
|
+
session's index lock, try again, and say so if it still fails.
|
|
@@ -214,8 +214,12 @@ index load at every start. Codex's memories belong to the user and are shared
|
|
|
214
214
|
by every Codex session, so a Codex bot cannot have its own; say so before
|
|
215
215
|
turning them on for one. It is a hand edit, and say that you made it.
|
|
216
216
|
|
|
217
|
-
After each change, commit what it changed in the bots folder
|
|
218
|
-
|
|
217
|
+
After each change, commit what it changed in the bots folder, naming each
|
|
218
|
+
file when you stage it and again in the commit. Commit with it what no bot
|
|
219
|
+
owns and nobody has committed yet: `init`'s files, `defaults.yaml`,
|
|
220
|
+
`skills.yaml`, and the user's `rules/` and `skills/`. What it changed
|
|
221
|
+
includes the folder of a bot it made or changed. The kit never commits by
|
|
222
|
+
itself.
|
|
219
223
|
|
|
220
224
|
Do the reversible part and stop at the line. Making a bot, adding a session
|
|
221
225
|
and linking skills add things and are safe to run again; closing a tab,
|
|
@@ -251,6 +251,7 @@ identifiers, counts and paths that only show the work; where evidence matters,
|
|
|
251
251
|
say where it is.
|
|
252
252
|
|
|
253
253
|
Say what you did not look at. Where you changed anything in the bots folder,
|
|
254
|
-
commit it
|
|
254
|
+
commit it, naming each file when you stage it and again in the commit; the
|
|
255
|
+
kit never commits by itself.
|
|
255
256
|
|
|
256
257
|
Sources and licences: [NOTICE.md](NOTICE.md).
|
|
@@ -50,11 +50,13 @@ profile notes and that any bot may read them; and the routing, where something
|
|
|
50
50
|
the kit does wrong is filed as an issue in one step and something a bot does
|
|
51
51
|
wrong goes back to that bot's session as feedback.
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
the
|
|
53
|
+
Grooming runs in a session in the book, on Claude Code's own scheduler,
|
|
54
|
+
because the owner decided on 2026-09-24 (#223) that scheduled work does not use
|
|
55
|
+
an Orca automation: an automation cannot carry a model or an effort of its
|
|
56
|
+
own. Its runs share one long conversation, which the owner accepted in the
|
|
57
|
+
same decision ("just do /compact when needed, add to the skill"). What a run
|
|
58
|
+
must remember still lives in files, because a compacted conversation keeps
|
|
59
|
+
only a summary. The facts about Claude Code's scheduler are in the tech notes.
|
|
58
60
|
|
|
59
61
|
## Ideas taken from material with no licence
|
|
60
62
|
|
|
@@ -109,3 +111,17 @@ Sources and licences in full: [LICENSES.md](LICENSES.md).
|
|
|
109
111
|
expected). The load limit and where the memory lives are the tech notes'
|
|
110
112
|
facts, verified live on Claude Code 2.1.280; Codex's store is the user's and
|
|
111
113
|
is left alone.
|
|
114
|
+
|
|
115
|
+
## What moving to Claude Code's scheduler changed (#237)
|
|
116
|
+
|
|
117
|
+
- Where it remembers: the runs share one conversation now, so the files are
|
|
118
|
+
kept because a compact leaves a summary and a `/clear` leaves nothing, rather
|
|
119
|
+
than because each run starts fresh.
|
|
120
|
+
- Turning it on: a `grooming` session in the book with its own model and
|
|
121
|
+
effort, a run by hand with `obk groom --now`, then `--on --at` after the yes.
|
|
122
|
+
That it fires only while its tab is up, the week-long limit each run renews,
|
|
123
|
+
and what a `/clear` does are Claude Code's documented behaviour, in the tech
|
|
124
|
+
notes.
|
|
125
|
+
- Compacting is new, from the owner's decision on #223. Who types `/compact`
|
|
126
|
+
(the management session, through the kit) is the architect's reading of it on
|
|
127
|
+
#237.
|
|
@@ -53,17 +53,20 @@ say what was and was not looked at rather than taking the report's word for it.
|
|
|
53
53
|
|
|
54
54
|
## Where it remembers
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
The runs share one long conversation, and a compacted conversation keeps a
|
|
57
|
+
summary of the earlier runs, not their detail; a `/clear` keeps nothing. So
|
|
58
|
+
everything a run needs from the last one is in files in the bots folder: the
|
|
59
|
+
window and the open findings in a `grooming/` folder unless the project already
|
|
60
|
+
keeps them elsewhere, and the profile notes in `profiles/`, where the fleet's
|
|
61
|
+
rules tell every bot to look. Say at the top of each what it is. Trust the
|
|
62
|
+
files over what the conversation seems to remember. A run that cannot find
|
|
61
63
|
them says so and reads less, not everything.
|
|
62
64
|
|
|
63
65
|
The open findings are what stops the same thing being reported every day:
|
|
64
66
|
before filing an issue or sending feedback, look there, and where it is already
|
|
65
67
|
open, add to it rather than raising it again. Take a finding off when it is
|
|
66
|
-
fixed or no longer true. Commit these
|
|
68
|
+
fixed or no longer true. Commit these at the end of each run, naming each
|
|
69
|
+
file when you stage it and again in the commit.
|
|
67
70
|
|
|
68
71
|
## Keep the run bounded
|
|
69
72
|
|
|
@@ -212,11 +215,45 @@ places and sending one to the other loses it.
|
|
|
212
215
|
|
|
213
216
|
## Turning it on
|
|
214
217
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
218
|
+
The pass runs in a session of Bot Father's own, `grooming`, on Claude Code's
|
|
219
|
+
own scheduler. Add it like any session, with the model and effort it is to run
|
|
220
|
+
at: `obk session add --bots <folder> --bot bot-father --name grooming --model
|
|
221
|
+
<m> --effort <e>`, with `--harness claude` where Bot Father runs on Codex, then
|
|
222
|
+
`obk up`. Its launch line carries them, so they are what every run uses.
|
|
223
|
+
|
|
224
|
+
Do not schedule a pass nobody has watched. `obk groom --now` runs it once in its
|
|
225
|
+
tab; read what it produced, and fix the run rather than the report. Then, after
|
|
226
|
+
one explicit yes from the user who has read a run, `obk groom --on --at <HH:MM>`
|
|
227
|
+
schedules it. Tell them what it will cost them each day before they say it.
|
|
228
|
+
Schedule it through that command rather than by hand, so there is one job and
|
|
229
|
+
the kit can see it. `obk groom` says what is scheduled, and says so plainly
|
|
230
|
+
when there is none or more than one.
|
|
231
|
+
|
|
232
|
+
It fires only while its tab is open in Orca and Claude Code is idle in it, up to
|
|
233
|
+
half an hour after its time, and a day it misses is not made up. Tell the user
|
|
234
|
+
that before they rely on it. Claude Code ends a recurring job a week after it
|
|
235
|
+
was made, so each run renews its own; a week with no run ends it, and then
|
|
236
|
+
`obk groom --on --at <HH:MM>` puts back what the user agreed to. A `/clear` in
|
|
237
|
+
its tab leaves it running: the job belongs to the running Claude Code, not to
|
|
238
|
+
one conversation. It keeps running until the session is next restarted or
|
|
239
|
+
Orca restores its tab: a resume brings back only the jobs of the conversation
|
|
240
|
+
it resumes, so after a `/clear` and then a restart the job is gone. `obk groom`
|
|
241
|
+
says so, and `--on --at` puts it back.
|
|
242
|
+
|
|
243
|
+
## Compacting
|
|
244
|
+
|
|
245
|
+
The runs share one conversation and each adds to it, so each run carries more
|
|
246
|
+
than the last and costs more. A compact shrinks it to a summary. The run cannot
|
|
247
|
+
do it: a scheduled run cannot type a built-in command, and a session cannot
|
|
248
|
+
compact itself. So the report says when one is due, and the management session
|
|
249
|
+
does it between runs with `obk groom --compact`.
|
|
250
|
+
|
|
251
|
+
One is due when the run's calls carry about half the model's context window.
|
|
252
|
+
`obk usage --bots <folder> --bot bot-father --session grooming --since <this
|
|
253
|
+
run's start>` gives the run's calls and tokens: input, cache read and cache
|
|
254
|
+
write together, divided by the calls, is roughly what each call carried.
|
|
255
|
+
Claude Code also compacts on its own near the limit. That is the backstop, not
|
|
256
|
+
the plan: it comes when the conversation is full, most likely in the middle of
|
|
257
|
+
a run.
|
|
221
258
|
|
|
222
259
|
Sources and licences: [NOTICE.md](NOTICE.md).
|
package/src/book.js
CHANGED
|
@@ -122,9 +122,7 @@ export async function updateBook(home, change) {
|
|
|
122
122
|
/**
|
|
123
123
|
* The right to change what belongs to one bot, held from before it is read until
|
|
124
124
|
* after it is written — a SQLite write transaction on a file of its own beside
|
|
125
|
-
* the book.
|
|
126
|
-
* for the one thing Orca will not do for us: look for an automation and make one
|
|
127
|
-
* if it is missing, without a second run doing the same between the two.
|
|
125
|
+
* the book.
|
|
128
126
|
*
|
|
129
127
|
* SQLite is in Node itself and does this with the operating system's own file
|
|
130
128
|
* locks. That matters for one reason: the lock belongs to the process, so it is
|
|
@@ -142,7 +140,7 @@ export async function updateBook(home, change) {
|
|
|
142
140
|
* The lock file holds no data — it is opened, locked and closed — so it stays
|
|
143
141
|
* empty and leaves no journal beside it.
|
|
144
142
|
*/
|
|
145
|
-
|
|
143
|
+
function takeLock(home) {
|
|
146
144
|
const file = lockFile(home);
|
|
147
145
|
mkdirSync(path.dirname(file), { recursive: true });
|
|
148
146
|
|
package/src/bot.js
CHANGED
|
@@ -59,7 +59,7 @@ export function botNames(bots) {
|
|
|
59
59
|
* link the user made takes it out of that folder; undefined when it stays in.
|
|
60
60
|
*
|
|
61
61
|
* The kit writes only in the bot folder, never in user-level settings (PRD 6.3,
|
|
62
|
-
* ADR
|
|
62
|
+
* ADR 0022), and a link would make the one the other without a word. So the
|
|
63
63
|
* answer is the file system's, not the spelling's: see `whereItLeads`.
|
|
64
64
|
*/
|
|
65
65
|
export function leadsOutside(home, target) {
|
package/src/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
13
13
|
import { parseArgs } from 'node:util';
|
|
14
14
|
|
|
15
15
|
import { addSession, changeBot, changeSession, createBot, leadsOutside, readBot, SESSION_FIELDS } from './bot.js';
|
|
16
|
-
import { grooming } from './groom.js';
|
|
16
|
+
import { addCommand, groomCommand, grooming, upCommand } from './groom.js';
|
|
17
17
|
import { checkHealth, orcaSettingFindings } from './health.js';
|
|
18
18
|
import { initBots } from './init.js';
|
|
19
19
|
import { APPROVALS, HARNESSES, ownCli, shellWord, workDirOf } from './launch.js';
|
|
@@ -164,13 +164,18 @@ Usage:
|
|
|
164
164
|
with its settings, its tab and the conversation it
|
|
165
165
|
is in. It reads your files and reports them as they
|
|
166
166
|
stand; what to make of them is yours.
|
|
167
|
-
obk groom --bots <path> [--at <HH:MM>]
|
|
168
|
-
Say
|
|
169
|
-
and
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
167
|
+
obk groom --bots <path> [--on [--at <HH:MM>] | --at <HH:MM> | --off | --now | --compact]
|
|
168
|
+
Say what the daily grooming is: Bot Father's session
|
|
169
|
+
grooming, and the job scheduled in it on Claude
|
|
170
|
+
Code's own scheduler. Add that session like any
|
|
171
|
+
other, with the model and effort each run is to use.
|
|
172
|
+
--now asks it to run the grooming once. --on --at
|
|
173
|
+
asks it to schedule it daily, and each run renews
|
|
174
|
+
it; it starts off, because it spends tokens every
|
|
175
|
+
day, so run it by hand once and read it first. --at
|
|
176
|
+
moves it, --off unschedules it, and --compact
|
|
177
|
+
compacts its conversation between runs. It fires
|
|
178
|
+
only while its tab is up in Orca.
|
|
174
179
|
obk usage --bots <path> [--bot <bot>] [--session <name>] [--since <time>] [--until <time>]
|
|
175
180
|
Say what your sessions have used: the conversations
|
|
176
181
|
each one had, their calls and tokens, the models and
|
|
@@ -281,6 +286,8 @@ async function run(argv) {
|
|
|
281
286
|
at: { type: 'string' },
|
|
282
287
|
on: { type: 'boolean' },
|
|
283
288
|
off: { type: 'boolean' },
|
|
289
|
+
now: { type: 'boolean' },
|
|
290
|
+
compact: { type: 'boolean' },
|
|
284
291
|
skill: { type: 'string' },
|
|
285
292
|
repo: { type: 'string' },
|
|
286
293
|
ref: { type: 'string' },
|
|
@@ -355,7 +362,7 @@ async function run(argv) {
|
|
|
355
362
|
/**
|
|
356
363
|
* The bots folder as the file system knows it. A folder reached through a
|
|
357
364
|
* symlink is the same fleet: the harnesses file their transcripts under the real
|
|
358
|
-
* path,
|
|
365
|
+
* path, and what the kit keeps
|
|
359
366
|
* beside the folder (the skill sources, the start prompts, the messages) is
|
|
360
367
|
* named after it. Asked by one spelling and then another, a fleet would
|
|
361
368
|
* otherwise have two of each, and a pin set through one is undone through the
|
|
@@ -414,7 +421,7 @@ const RECORD = 'session record';
|
|
|
414
421
|
* A hook runs inside the user's own session, so this one stays out of the way:
|
|
415
422
|
* it writes on standard output only what the harness is to read as JSON, and
|
|
416
423
|
* whatever goes wrong, it goes wrong quietly. A book left stale is a thing the
|
|
417
|
-
* health check finds later; a session disturbed is the user's work (ADR
|
|
424
|
+
* health check finds later; a session disturbed is the user's work (ADR 0022).
|
|
418
425
|
*/
|
|
419
426
|
async function record(bots, bot) {
|
|
420
427
|
try {
|
|
@@ -658,11 +665,16 @@ const commands = {
|
|
|
658
665
|
|
|
659
666
|
groom(bots, values) {
|
|
660
667
|
refuseWhenOrcaIsDown();
|
|
661
|
-
|
|
662
|
-
|
|
668
|
+
const asks = ['on', 'off', 'now', 'compact'].filter((flag) => values[flag] === true);
|
|
669
|
+
if (asks.length > 1) {
|
|
670
|
+
throw new Error(`groom asks for one thing at a time, and got ${asks.map((flag) => `--${flag}`).join(' and ')}. Say which one you want.`);
|
|
663
671
|
}
|
|
664
|
-
|
|
665
|
-
|
|
672
|
+
if (values.at !== undefined && asks.length === 1 && asks[0] !== 'on') {
|
|
673
|
+
throw new Error(`--at is the time grooming runs at, and goes with --on, not with --${asks[0]}.`);
|
|
674
|
+
}
|
|
675
|
+
// --at on its own moves grooming that is on to another time.
|
|
676
|
+
const ask = asks[0] ?? (values.at === undefined ? undefined : 'move');
|
|
677
|
+
const groom = grooming(bots, { at: values.at, ask });
|
|
666
678
|
return { answer: { bots, groom }, lines: groomLines(groom, bots) };
|
|
667
679
|
},
|
|
668
680
|
|
|
@@ -1023,26 +1035,81 @@ function conversationLine(one) {
|
|
|
1023
1035
|
}
|
|
1024
1036
|
|
|
1025
1037
|
/**
|
|
1026
|
-
* What there is to say about the daily grooming:
|
|
1027
|
-
*
|
|
1028
|
-
* says what is there and what the next step would be
|
|
1038
|
+
* What there is to say about the daily grooming: the session it runs in, the
|
|
1039
|
+
* jobs scheduled there, and what this run asked of it. Grooming that is off is
|
|
1040
|
+
* not a fault, so this says what is there and what the next step would be;
|
|
1041
|
+
* no job where one was expected, or more than one, is said plainly, because
|
|
1042
|
+
* either means the fleet is groomed a different number of times a day than
|
|
1043
|
+
* anyone decided.
|
|
1029
1044
|
*/
|
|
1030
|
-
function groomLines(
|
|
1031
|
-
|
|
1045
|
+
function groomLines({ session, jobs, asked }, bots) {
|
|
1046
|
+
const head = 'groom'.padEnd(9);
|
|
1047
|
+
const more = ' '.repeat(11);
|
|
1048
|
+
const said = asked === null ? [] : [ASKED[asked]];
|
|
1049
|
+
|
|
1050
|
+
if (session === null) {
|
|
1051
|
+
return [
|
|
1052
|
+
...said,
|
|
1053
|
+
`${head} off: Bot Father has no session called grooming, which is where it runs`,
|
|
1054
|
+
`${more}It runs in that session, on Claude Code's own scheduler. Give the session`,
|
|
1055
|
+
`${more}--model and --effort for what each run is to use.`,
|
|
1056
|
+
`Add it: ${addCommand(bots)}`,
|
|
1057
|
+
`Then: ${upCommand(bots)}`,
|
|
1058
|
+
];
|
|
1059
|
+
}
|
|
1060
|
+
if (session.harness !== 'claude') {
|
|
1032
1061
|
return [
|
|
1033
|
-
|
|
1034
|
-
|
|
1062
|
+
...said,
|
|
1063
|
+
`${head} off: Bot Father's grooming session runs on ${session.harness}, and grooming runs`,
|
|
1064
|
+
`${more}on Claude Code's own scheduler, so it has to be a Claude Code session: retire`,
|
|
1065
|
+
`${more}it and add it again with --harness claude.`,
|
|
1035
1066
|
];
|
|
1036
1067
|
}
|
|
1037
1068
|
|
|
1069
|
+
const lines = [...said];
|
|
1070
|
+
if (!session.up) {
|
|
1071
|
+
lines.push(`${head} Bot Father's grooming session is not up, so nothing fires`);
|
|
1072
|
+
lines.push(`Bring it up: ${upCommand(bots)}`);
|
|
1073
|
+
}
|
|
1074
|
+
if (asked === null) lines.push(...jobLines(jobs, bots, session.up ? head : more));
|
|
1075
|
+
lines.push(`${more}It fires only while its tab is up in Orca and Claude Code is idle there, up to`);
|
|
1076
|
+
lines.push(`${more}half an hour after its time, and a run it misses is not made up.`);
|
|
1077
|
+
return lines;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
/** What is scheduled in the grooming session's conversation. */
|
|
1081
|
+
function jobLines(jobs, bots, head) {
|
|
1082
|
+
const more = ' '.repeat(11);
|
|
1083
|
+
if (jobs.length === 0) {
|
|
1084
|
+
return [
|
|
1085
|
+
`${head} off: its conversation has no grooming job`,
|
|
1086
|
+
`${more}It spends tokens every day. Run it by hand first: ${groomCommand(bots)} --now`,
|
|
1087
|
+
`Turn it on, once the user has read a run and said yes: ${groomCommand(bots)} --on --at 04:00`,
|
|
1088
|
+
];
|
|
1089
|
+
}
|
|
1090
|
+
if (jobs.length === 1) {
|
|
1091
|
+
const [job] = jobs;
|
|
1092
|
+
return [
|
|
1093
|
+
`${head} on, daily at ${job.at ?? job.cron} job ${job.id}`,
|
|
1094
|
+
`${more}Each run renews it; with no run, Claude Code ends it at ${job.expires}.`,
|
|
1095
|
+
`Turn it off: ${groomCommand(bots)} --off`,
|
|
1096
|
+
];
|
|
1097
|
+
}
|
|
1098
|
+
const times = [...new Set(jobs.map((job) => job.at))];
|
|
1038
1099
|
return [
|
|
1039
|
-
`${
|
|
1040
|
-
|
|
1041
|
-
? `It runs every day at ${groom.at} and spends tokens each time. Turn it off with: ${shellWord(ownCli())} groom --bots ${shellWord(bots)} --off`
|
|
1042
|
-
: `It is not running yet. Try it by hand, read what it gives you, then: ${shellWord(ownCli())} groom --bots ${shellWord(bots)} --on`,
|
|
1100
|
+
`${head} ${jobs.length} grooming jobs, so the fleet is groomed ${jobs.length} times a day: ${jobs.map((job) => `${job.id} at ${job.at ?? job.cron}`).join(', ')}`,
|
|
1101
|
+
`Back to one: ${groomCommand(bots)} --on${times.length === 1 && times[0] !== undefined ? '' : ' --at <HH:MM>'}`,
|
|
1043
1102
|
];
|
|
1044
1103
|
}
|
|
1045
1104
|
|
|
1105
|
+
/** What each ask typed into the grooming tab, as the line that says so. */
|
|
1106
|
+
const ASKED = {
|
|
1107
|
+
on: `${'asked'.padEnd(9)} the grooming session to schedule the grooming, in a line typed into its tab. It is listed once the session has done it.`,
|
|
1108
|
+
off: `${'asked'.padEnd(9)} the grooming session to delete its grooming jobs, in a line typed into its tab.`,
|
|
1109
|
+
now: `${'asked'.padEnd(9)} the grooming session to run the grooming once, now. Its report goes to Bot Father's management session.`,
|
|
1110
|
+
compact: `${'asked'.padEnd(9)} the grooming session to compact its conversation: /compact was typed into its tab.`,
|
|
1111
|
+
};
|
|
1112
|
+
|
|
1046
1113
|
/** The settings a session carries, in the order a session is written down. */
|
|
1047
1114
|
const SHOWN = SESSION_FIELDS.filter((field) => field !== 'name' && field !== 'harness');
|
|
1048
1115
|
|
package/src/conversations.js
CHANGED
|
@@ -49,10 +49,16 @@ export function conversationsIn(harness, home, since) {
|
|
|
49
49
|
* is opened, however many conversations the machine has.
|
|
50
50
|
*/
|
|
51
51
|
export function hasConversation(harness, home, id) {
|
|
52
|
-
if (harness === 'claude') return existsSync(
|
|
52
|
+
if (harness === 'claude') return existsSync(claudeTranscript(home, id));
|
|
53
53
|
return rollouts(codexDir()).some((file) => path.basename(file).endsWith(`-${id}.jsonl`));
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Where Claude Code keeps the conversation `id` of this bot home, whether or
|
|
58
|
+
* not it has written it yet.
|
|
59
|
+
*/
|
|
60
|
+
export const claudeTranscript = (home, id) => path.join(claudeDir(home), `${id}.jsonl`);
|
|
61
|
+
|
|
56
62
|
/**
|
|
57
63
|
* The same conversations, each with the file the harness keeps it in, for a
|
|
58
64
|
* caller that has to read what is inside one rather than only know it is there,
|