@assuredloop/orca-bot-kit 0.18.0 → 0.19.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 +19 -2
- package/package.json +1 -1
- package/skills/obk-arch/NOTICE.md +45 -0
- package/skills/obk-bot-building/NOTICE.md +5 -0
- package/skills/obk-bot-building/SKILL.md +7 -0
- package/skills/obk-debugging/NOTICE.md +28 -0
- package/skills/obk-decision-memo/NOTICE.md +21 -0
- package/skills/obk-fleet-review/NOTICE.md +8 -0
- package/skills/obk-grilling/NOTICE.md +11 -5
- package/skills/obk-grooming/NOTICE.md +11 -0
- package/skills/obk-grooming/SKILL.md +10 -0
- package/skills/obk-handoff/NOTICE.md +6 -0
- package/skills/obk-personal-facilitation/NOTICE.md +9 -0
- package/skills/obk-recall/NOTICE.md +3 -0
- package/skills/obk-researching/NOTICE.md +27 -6
- package/skills/obk-reviewing/NOTICE.md +19 -0
- package/skills/obk-tdd/NOTICE.md +35 -4
- package/skills/obk-teach/NOTICE.md +3 -0
- package/skills/obk-why/NOTICE.md +3 -0
- package/skills/obk-writing/NOTICE.md +24 -0
- package/src/cli.js +43 -8
- package/src/groom.js +144 -11
- package/src/health.js +20 -1
- package/src/orca.js +13 -3
- package/src/temp.js +99 -10
|
@@ -16,6 +16,9 @@ noted, with thanks:
|
|
|
16
16
|
has no caller, premature abstraction, "I would have done it differently",
|
|
17
17
|
about five items, and showing what was set aside. Its `poteto-mode`
|
|
18
18
|
bugbot-triage reference gave fix, dismiss or ask, and "when in doubt, ask".
|
|
19
|
+
Revision: cursor/plugins@6ed0f7a9504f577d7529064103cecce9be7dfc5e
|
|
20
|
+
(reconstructed: the research pack's 2026-09-20 clone the skills were built
|
|
21
|
+
from; the revision actually read was not recorded)
|
|
19
22
|
- **obra/superpowers**, `requesting-code-review`, `receiving-code-review` and
|
|
20
23
|
`subagent-driven-development/task-reviewer-prompt.md` (the last for bounded
|
|
21
24
|
reading, "declined to judge", the read-only rule and one focused test only
|
|
@@ -27,6 +30,9 @@ noted, with thanks:
|
|
|
27
30
|
pattern: read, restate, verify, evaluate, respond, implement, with the stop
|
|
28
31
|
on an unclear item, the check for who calls it, the order of fixes, and
|
|
29
32
|
correcting your own pushback without a speech.
|
|
33
|
+
Revision: obra/superpowers@5bf4e78011075bcfc0dc295f0724994cd123ee71
|
|
34
|
+
(reconstructed: the research pack's 2026-09-20 clone the skills were built
|
|
35
|
+
from; the revision actually read was not recorded)
|
|
30
36
|
- **mattpocock/skills**, `code-review`: the smell baseline in full, with its
|
|
31
37
|
own guards: every entry a labelled judgement call, the repo's documented
|
|
32
38
|
standard overriding it, and anything tooling enforces skipped. Also pinning
|
|
@@ -34,17 +40,26 @@ noted, with thanks:
|
|
|
34
40
|
missing, unasked-for and misunderstood, quoting the rule for a standards
|
|
35
41
|
breach, and the two axes staying separate so neither hides the other. The
|
|
36
42
|
smells are Fowler's, from *Refactoring* chapter 3.
|
|
43
|
+
Revision: mattpocock/skills@c55ee46073ed923f86ce59a5eb3b6d895095d1b7
|
|
44
|
+
(reconstructed: the research pack's 2026-09-20 clone the skills were built
|
|
45
|
+
from; the revision actually read was not recorded)
|
|
37
46
|
- **Cursor thermos**, with `thermo-nuclear-review`: scope held to what the
|
|
38
47
|
change touches, not spending the author's time on a breakage the change
|
|
39
48
|
intends, what over-reporting costs you,
|
|
40
49
|
reading the change before the discussion, and never presenting a finding with
|
|
41
50
|
the research unfinished when the answer was there to be read.
|
|
51
|
+
Revision: cursor/plugins@6ed0f7a9504f577d7529064103cecce9be7dfc5e
|
|
52
|
+
(reconstructed: the research pack's 2026-09-20 clone the skills were built
|
|
53
|
+
from; the revision actually read was not recorded)
|
|
42
54
|
- **addyosmani/agent-skills**: passing the artefact and the contract without
|
|
43
55
|
the conclusion, the one structural problem outranking ten small ones, and,
|
|
44
56
|
from `doubt-driven-development`, sorting findings into misread, valid,
|
|
45
57
|
trade-off and noise in that order, with the brief fixed first for a misread.
|
|
46
58
|
Its `code-review-and-quality`: not softening a real issue, which pstack's
|
|
47
59
|
`code-quality-review` says too.
|
|
60
|
+
Revision: addyosmani/agent-skills@dc27a9c2e13721158157632de61b4106c6c2a2a1
|
|
61
|
+
(reconstructed: the research pack's 2026-09-20 clone the skills were built
|
|
62
|
+
from; the revision actually read was not recorded)
|
|
48
63
|
- **Anthropic's built-in review**, as behaviour observed rather than text
|
|
49
64
|
taken: the angles worth separating, naming the behaviour a deleted line held
|
|
50
65
|
up, following a change out to its callers, confirmed against plausible
|
|
@@ -53,6 +68,10 @@ noted, with thanks:
|
|
|
53
68
|
bug states its trigger, not collapsing finding into judging,
|
|
54
69
|
correctness outranking cleanup, and saying when a review was less than it was
|
|
55
70
|
meant to be.
|
|
71
|
+
Revision: none (Claude Code's built-in review, its behaviour observed rather
|
|
72
|
+
than a text read)
|
|
56
73
|
- **ECC's reviewer prompt**, through the research pack: the four questions
|
|
57
74
|
before a finding, a defensible severity, zero findings being expected,
|
|
58
75
|
consolidating repeats, and the list of findings that are usually wrong.
|
|
76
|
+
Revision: affaan-m/ECC@07756cee15788a54506031462794ad645719b028 (as recorded
|
|
77
|
+
in the research pack's source-book-2.md, read 2026-09-19)
|
package/skills/obk-tdd/NOTICE.md
CHANGED
|
@@ -14,6 +14,9 @@ thanks:
|
|
|
14
14
|
`to-spec`: the existing seam preferred, the highest one, and the fewer the
|
|
15
15
|
better. Its `diagnosing-bugs`: the correct seam for a bug test, and no
|
|
16
16
|
correct seam being the finding.
|
|
17
|
+
Revision: mattpocock/skills@c55ee46073ed923f86ce59a5eb3b6d895095d1b7
|
|
18
|
+
(reconstructed: the research pack's 2026-09-20 clone the skills were built
|
|
19
|
+
from; the revision actually read was not recorded)
|
|
17
20
|
- **obra/superpowers**, `test-driven-development` and its `writing-good-tests`:
|
|
18
21
|
naming the break a test catches, deriving the expected value by hand, change
|
|
19
22
|
detectors, behaviour rather than text, your contract rather than the
|
|
@@ -21,16 +24,25 @@ thanks:
|
|
|
21
24
|
the mutation classes. Its `verification-before-completion`: the
|
|
22
25
|
revert-the-fix proof. Its `writing-plans`: a requirement's silence not being
|
|
23
26
|
permission, and the few uncovered input classes most likely to bite.
|
|
27
|
+
Revision: obra/superpowers@5bf4e78011075bcfc0dc295f0724994cd123ee71
|
|
28
|
+
(reconstructed: the research pack's 2026-09-20 clone the skills were built
|
|
29
|
+
from; the revision actually read was not recorded)
|
|
24
30
|
- **Cursor pstack**, `tdd` and `principle-test-behavior-not-implementation`:
|
|
25
31
|
the five shapes that observe no behaviour and the fix for each (asserting
|
|
26
32
|
what a stand-in received rather than that it was called, and the relation
|
|
27
33
|
across rows of data that is kept), "prefer no
|
|
28
34
|
new test over a bad test", the honest exit when a test is impractical, and
|
|
29
35
|
the report that names the failing-before and passing-after runs.
|
|
36
|
+
Revision: cursor/plugins@6ed0f7a9504f577d7529064103cecce9be7dfc5e
|
|
37
|
+
(reconstructed: the research pack's 2026-09-20 clone the skills were built
|
|
38
|
+
from; the revision actually read was not recorded)
|
|
30
39
|
- **addyosmani/agent-skills**, `test-driven-development` and
|
|
31
40
|
`constraint-driven-development`: finding out how the project tests before
|
|
32
41
|
writing anything, repetition being no fault in a test, a subagent for the
|
|
33
42
|
reproduction test, and the cheap roads to green.
|
|
43
|
+
Revision: addyosmani/agent-skills@dc27a9c2e13721158157632de61b4106c6c2a2a1
|
|
44
|
+
(reconstructed: the research pack's 2026-09-20 clone the skills were built
|
|
45
|
+
from; the revision actually read was not recorded)
|
|
34
46
|
- **citypaul/.dotfiles**, `tdd` and `mutation-testing` with its
|
|
35
47
|
`mutator-rules`: the mutation loop, survivor triage into killed, equivalent
|
|
36
48
|
or named and deferred, the equivalence question, the high-value logic worth
|
|
@@ -38,15 +50,34 @@ thanks:
|
|
|
38
50
|
mixed true and false, no identity values), the tool-run mechanics in
|
|
39
51
|
`mutation-tools.md`, keeping the harness out of the inner loop, triangulating
|
|
40
52
|
after a fake, and refusing to manufacture a red.
|
|
53
|
+
Revision: citypaul/.dotfiles@a109f9972bb46671c624fc05752031523e1cf6fc
|
|
54
|
+
(reconstructed: the research pack's 2026-09-20 clone the skills were built
|
|
55
|
+
from; the revision actually read was not recorded)
|
|
41
56
|
- **Kent Beck's own rules file**: one test at a time, the smallest code that
|
|
42
57
|
passes, structure and behaviour kept apart, and the two-level test for a
|
|
43
58
|
defect.
|
|
59
|
+
Revision: KentBeck/BPlusTree3@ca80e4d85a99cd0af2effe717f709d43e80403bc
|
|
60
|
+
(reconstructed: the research pack's 2026-09-20 clone the skills were built
|
|
61
|
+
from; the revision actually read was not recorded)
|
|
44
62
|
- **nizos/tdd-guard**: the ladder to a clean red.
|
|
63
|
+
Revision: nizos/tdd-guard@2579ec1823fac5f4afb73be678d9c500b19885ac (as
|
|
64
|
+
recorded in the research pack's source-book-2.md, read 2026-09-19)
|
|
45
65
|
|
|
46
|
-
Ideas paraphrased, with no text taken:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
66
|
+
Ideas paraphrased, with no text taken:
|
|
67
|
+
|
|
68
|
+
- **Trail of Bits' mutation-testing skill**, in `trailofbits/skills` (CC BY-SA
|
|
69
|
+
4.0), on equivalent mutants.
|
|
70
|
+
Revision: trailofbits/skills@123037ec8aed26f0d86327cc39137ee5043e5deb (as
|
|
71
|
+
recorded in the research pack's source-book-2.md, read 2026-09-19)
|
|
72
|
+
- **Anthropic's Claude Code documentation**, "Claude Code: Best practices", on
|
|
73
|
+
separate test authorship.
|
|
74
|
+
Read: 2026-09-19 (read for the research pack's source-book-1.md, of that date)
|
|
75
|
+
- **alexop.dev**, "A Claude Code TDD Skill: Forcing Red-Green-Refactor", on why
|
|
76
|
+
one context cannot hold both halves.
|
|
77
|
+
Read: 2026-09-19 (read for the research pack's source-book-1.md, of that date)
|
|
78
|
+
- **The published work on mutation testing at scale**, Petrović and others,
|
|
79
|
+
"Practical Mutation Testing at Scale" (arXiv 2102.11378).
|
|
80
|
+
Read: 2026-09-19 (read for the research pack's source-book-1.md, of that date)
|
|
50
81
|
|
|
51
82
|
Our own, with no source behind them: the order to break things in, the red
|
|
52
83
|
from an archived baseline for behaviour that already existed, checking that an
|
|
@@ -45,6 +45,9 @@ thanks:
|
|
|
45
45
|
print, and the reply being the explanation itself rather than a report about
|
|
46
46
|
it). Its worked density example is not carried; the rules it illustrates
|
|
47
47
|
are.
|
|
48
|
+
Revision: cursor/plugins@6ed0f7a9504f577d7529064103cecce9be7dfc5e
|
|
49
|
+
(reconstructed: the research pack's 2026-09-20 clone the skills were built
|
|
50
|
+
from; the revision actually read was not recorded)
|
|
48
51
|
|
|
49
52
|
Made generic, which is what the owner asked for. The original is built on top
|
|
50
53
|
of two other skills it invokes by name, and a kit skill carries what it needs
|
package/skills/obk-why/NOTICE.md
CHANGED
|
@@ -37,6 +37,9 @@ The skill `obk-why` was consolidated for this kit from this, MIT, with thanks:
|
|
|
37
37
|
including one line per source consulted covering the empty and skipped ones;
|
|
38
38
|
and turning the findings into a preserve, change, avoid and risk constraint
|
|
39
39
|
set when the question precedes a change.
|
|
40
|
+
Revision: cursor/plugins@6ed0f7a9504f577d7529064103cecce9be7dfc5e
|
|
41
|
+
(reconstructed: the research pack's 2026-09-20 clone the skills were built
|
|
42
|
+
from; the revision actually read was not recorded)
|
|
40
43
|
- The same skill's `references/sources/code-archaeology.md`. Its pitfalls: the
|
|
41
44
|
squash merge that flattens a branch's steps, the commit message that calls a
|
|
42
45
|
behaviour change a small refactor so the diff has to be read instead, the
|
|
@@ -30,6 +30,9 @@ Each licence below was read from the file named beside it.
|
|
|
30
30
|
read machine-written when every sentence is clipped to the same length; its
|
|
31
31
|
heading rule, that a heading carries the point rather than the topic; its
|
|
32
32
|
list rules; and the instruction to call each thing by one name everywhere.
|
|
33
|
+
Revision: cursor/plugins@6ed0f7a9504f577d7529064103cecce9be7dfc5e
|
|
34
|
+
(reconstructed: the research pack's 2026-09-20 clone the skills were built
|
|
35
|
+
from; the revision actually read was not recorded)
|
|
33
36
|
- **Cursor `plugins`, `pstack/skills/unslop`**: same licence. Two of its rules
|
|
34
37
|
are load-bearing here. Say what it does rather than how it feels, with its
|
|
35
38
|
test: ask what the sentence tells the reader to do or know, restate it as a
|
|
@@ -44,6 +47,9 @@ Each licence below was read from the file named beside it.
|
|
|
44
47
|
named above are kept in their positive form, not every rule in the catalogue,
|
|
45
48
|
and the word list is left where it is, as something to review against rather
|
|
46
49
|
than to load.
|
|
50
|
+
Revision: cursor/plugins@6ed0f7a9504f577d7529064103cecce9be7dfc5e
|
|
51
|
+
(reconstructed: the research pack's 2026-09-20 clone the skills were built
|
|
52
|
+
from; the revision actually read was not recorded)
|
|
47
53
|
- **`citypaul/.dotfiles`, `claude/.claude/skills/technical-writing`**: MIT,
|
|
48
54
|
that skill's own nested `LICENSE`, "Copyright (c) 2025 Adam Bulmer", which
|
|
49
55
|
governs its directory rather than the repository root's MIT licence in
|
|
@@ -63,6 +69,9 @@ Each licence below was read from the file named beside it.
|
|
|
63
69
|
leads with the decision requested. And from `resources/formatting.md`, bold
|
|
64
70
|
for the one phrase a skimmer must not miss, and a numbered list claiming that
|
|
65
71
|
order matters.
|
|
72
|
+
Revision: citypaul/.dotfiles@a109f9972bb46671c624fc05752031523e1cf6fc
|
|
73
|
+
(reconstructed: the research pack's 2026-09-20 clone the skills were built
|
|
74
|
+
from; the revision actually read was not recorded)
|
|
66
75
|
- **`mattpocock/skills`, `skills/in-progress/writing-shape`**: MIT,
|
|
67
76
|
`mattpocock_skills/LICENSE`, "Copyright (c) 2026 Matt Pocock". The grounding
|
|
68
77
|
idea, which nothing else in the sources has: every concept a block leans on
|
|
@@ -73,6 +82,9 @@ Each licence below was read from the file named beside it.
|
|
|
73
82
|
drowns the opening. Its format arguments are also taken: prose carries an
|
|
74
83
|
argument and lists carry parallel items, a table where the same shape repeats
|
|
75
84
|
three times or more, and quoting where the original wording is the point.
|
|
85
|
+
Revision: mattpocock/skills@c55ee46073ed923f86ce59a5eb3b6d895095d1b7
|
|
86
|
+
(reconstructed: the research pack's 2026-09-20 clone the skills were built
|
|
87
|
+
from; the revision actually read was not recorded)
|
|
76
88
|
- **`anthropics/skills`, `skills/internal-comms`**: Apache 2.0, that skill's
|
|
77
89
|
own `LICENSE.txt`, "Copyright 2026 Anthropic, PBC." The only source here that
|
|
78
90
|
is explicitly about audience, platform and register for non-engineering
|
|
@@ -83,11 +95,17 @@ Each licence below was read from the file named beside it.
|
|
|
83
95
|
read is a constraint on the writing rather than a detail: something meant to
|
|
84
96
|
be read in under a minute by people with some but not much context is a
|
|
85
97
|
different piece from the same facts written to be landed on.
|
|
98
|
+
Revision: anthropics/skills@34040c9c568585f6929bedeaad110ad08f079624
|
|
99
|
+
(reconstructed: the research pack's 2026-09-20 clone the skills were built
|
|
100
|
+
from; the revision actually read was not recorded)
|
|
86
101
|
- **`garrytan/gstack`, `document-generate/SKILL.md`**: MIT,
|
|
87
102
|
`garrytan_gstack/LICENSE`, "Copyright (c) 2026 Garry Tan". Its voice section:
|
|
88
103
|
lead with the point, be concrete, name the real thing and the real number.
|
|
89
104
|
Its rule to gloss a piece of jargon on first use even where the user
|
|
90
105
|
introduced the term.
|
|
106
|
+
Revision: garrytan/gstack@a6b3a57512ca6d5c6aa5b68f74f736195021f96e
|
|
107
|
+
(reconstructed: the research pack's 2026-09-20 clone the skills were built
|
|
108
|
+
from; the revision actually read was not recorded)
|
|
91
109
|
- **`addyosmani/agent-skills`, `skills/doubt-driven-development`** and
|
|
92
110
|
**`citypaul/.dotfiles`, `claude/.claude/skills/double-check`**: MIT,
|
|
93
111
|
`addyosmani_agent-skills/LICENSE` ("Copyright (c) 2025 Addy Osmani") and
|
|
@@ -99,6 +117,12 @@ Each licence below was read from the file named beside it.
|
|
|
99
117
|
than deferring, since a fresh reader can be wrong for want of context; and
|
|
100
118
|
stop when a pass finds only small or known things, bounded at about three
|
|
101
119
|
passes, with a piece still failing then reported as not ready.
|
|
120
|
+
Revision: addyosmani/agent-skills@dc27a9c2e13721158157632de61b4106c6c2a2a1
|
|
121
|
+
(reconstructed: the research pack's 2026-09-20 clone the skills were built
|
|
122
|
+
from; the revision actually read was not recorded)
|
|
123
|
+
Revision: citypaul/.dotfiles@a109f9972bb46671c624fc05752031523e1cf6fc
|
|
124
|
+
(reconstructed: the research pack's 2026-09-20 clone the skills were built
|
|
125
|
+
from; the revision actually read was not recorded)
|
|
102
126
|
|
|
103
127
|
**Left out on a licence.** `anthropics/skills`' `doc-coauthoring` has the best
|
|
104
128
|
statement of reader testing in the corpus, but its directory holds no licence
|
package/src/cli.js
CHANGED
|
@@ -28,7 +28,7 @@ import { readRoster } from './roster.js';
|
|
|
28
28
|
import { buildAgents, buildRules, CODEX_CAP } from './rules.js';
|
|
29
29
|
import { addSkill, buildSkills, linkSkills, removeSkill } from './skills.js';
|
|
30
30
|
import { addSource, fetchSources } from './sources.js';
|
|
31
|
-
import { makeTemp, retireTemp } from './temp.js';
|
|
31
|
+
import { makeTemp, retireTemp, trustHooks } from './temp.js';
|
|
32
32
|
import { readUsage } from './usage.js';
|
|
33
33
|
import { BOT_FATHER, bringUp, ownMailbox } from './up.js';
|
|
34
34
|
|
|
@@ -99,18 +99,25 @@ Usage:
|
|
|
99
99
|
obk temp make --bots <path> --name <session>
|
|
100
100
|
(--prompt <text> | --prompt-file <path>) [--harness claude|codex]
|
|
101
101
|
[--model <m>] [--effort <e>] [--context <c>]
|
|
102
|
-
[--approval ${APPROVALS.join('|')}]
|
|
102
|
+
[--approval ${APPROVALS.join('|')}] [--extra-arg=<arg>]...
|
|
103
103
|
Run in a long-lived session's own tab: make a
|
|
104
104
|
temporary session of that session's bot for a piece
|
|
105
105
|
of work, and bring it up. It takes your harness,
|
|
106
106
|
model, effort, context and approval unless you say
|
|
107
|
-
otherwise
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
otherwise; on another harness than yours it takes
|
|
108
|
+
only your approval. It works in work/<session>, and
|
|
109
|
+
has the task as its start prompt. The book records
|
|
110
|
+
it as temporary, made by you.
|
|
110
111
|
obk temp retire --bots <path> --name <session>
|
|
111
112
|
Run in the maker's own tab: retire a temporary
|
|
112
113
|
session it made, as obk retire does. It refuses a
|
|
113
114
|
long-lived session, or one another session made.
|
|
115
|
+
obk temp trust-hooks --bots <path> --name <session>
|
|
116
|
+
Run in the maker's own tab: answer the hooks review
|
|
117
|
+
of a Codex temporary session it made with "Trust
|
|
118
|
+
all and continue", and check the review went. It
|
|
119
|
+
refuses anything else on that screen, and types
|
|
120
|
+
nothing then.
|
|
114
121
|
obk rules build --bots <path> [--bot <bot>]
|
|
115
122
|
Build every bot's AGENTS.md from its charter and
|
|
116
123
|
the rule units it carries, or just the one you
|
|
@@ -190,7 +197,8 @@ Usage:
|
|
|
190
197
|
with its settings, its tab and the conversation it
|
|
191
198
|
is in. It reads your files and reports them as they
|
|
192
199
|
stand; what to make of them is yours.
|
|
193
|
-
obk groom --bots <path> [--on [--at <HH:MM>]
|
|
200
|
+
obk groom --bots <path> [--on [--at <HH:MM>] [--run-on codex [--model <m>] [--effort <e>] [--extra-arg=<arg>]...]
|
|
201
|
+
| --at <HH:MM> | --off | --now | --compact]
|
|
194
202
|
Say what the daily grooming is: Bot Father's session
|
|
195
203
|
grooming, and the job scheduled in it on Claude
|
|
196
204
|
Code's own scheduler. Add that session like any
|
|
@@ -201,7 +209,11 @@ Usage:
|
|
|
201
209
|
day, so run it by hand once and read it first. --at
|
|
202
210
|
moves it, --off unschedules it, and --compact
|
|
203
211
|
compacts its conversation between runs. It fires
|
|
204
|
-
only while its tab is up in Orca.
|
|
212
|
+
only while its tab is up in Orca. --run-on codex
|
|
213
|
+
makes each run a temporary Codex session of the
|
|
214
|
+
grooming session's, launched with --model, --effort
|
|
215
|
+
and --extra-arg, which reports back by the kit's
|
|
216
|
+
mail and is retired.
|
|
205
217
|
obk usage --bots <path> [--bot <bot>] [--session <name>] [--since <time>] [--until <time>]
|
|
206
218
|
Say what your sessions have used: the conversations
|
|
207
219
|
each one had, their calls and tokens (their
|
|
@@ -260,6 +272,7 @@ const COMMANDS = {
|
|
|
260
272
|
'session mailbox': ['bots', 'bot', 'session'],
|
|
261
273
|
'temp make': ['bots', 'name'],
|
|
262
274
|
'temp retire': ['bots', 'name'],
|
|
275
|
+
'temp trust-hooks': ['bots', 'name'],
|
|
263
276
|
};
|
|
264
277
|
|
|
265
278
|
/** What each flag is for, in the sentence a caller reads when it is missing. */
|
|
@@ -333,6 +346,7 @@ async function run(argv) {
|
|
|
333
346
|
disallow: { type: 'string', multiple: true },
|
|
334
347
|
...Object.fromEntries(SETTINGS.map(([flag]) => [flag, { type: 'string' }])),
|
|
335
348
|
'extra-arg': { type: 'string', multiple: true },
|
|
349
|
+
'run-on': { type: 'string' },
|
|
336
350
|
json: { type: 'boolean' },
|
|
337
351
|
help: { type: 'boolean', short: 'h' },
|
|
338
352
|
version: { type: 'boolean' },
|
|
@@ -601,6 +615,7 @@ const commands = {
|
|
|
601
615
|
if (values[flag] !== undefined) given[key] = values[flag];
|
|
602
616
|
}
|
|
603
617
|
if (values.context !== undefined) given.context = asNumberOrText(values.context);
|
|
618
|
+
if (values['extra-arg'] !== undefined) given.extra_args = values['extra-arg'];
|
|
604
619
|
const made = await makeTemp(bots, { tab, ...given });
|
|
605
620
|
const { tabs, rules, skills, permissions, paused, projects } = made.up;
|
|
606
621
|
const answer = { bots, bot: made.bot, session: made.session, maker: made.maker, settings: made.settings, created: [], completed: [], rules, skills, permissions, tabs, paused, projects };
|
|
@@ -625,6 +640,16 @@ const commands = {
|
|
|
625
640
|
};
|
|
626
641
|
},
|
|
627
642
|
|
|
643
|
+
async 'temp trust-hooks'(bots, values) {
|
|
644
|
+
const tab = callerTab(bots, true);
|
|
645
|
+
refuseWhenOrcaIsDown();
|
|
646
|
+
const trusted = await trustHooks(bots, { tab, name: values.name });
|
|
647
|
+
return {
|
|
648
|
+
answer: { bots, ...trusted },
|
|
649
|
+
lines: [`trusted ${trusted.bot} ${trusted.session}'s hooks, a temporary session of ${trusted.maker}'s: chose "Trust all and continue" on its hooks review, and the review has gone`],
|
|
650
|
+
};
|
|
651
|
+
},
|
|
652
|
+
|
|
628
653
|
'bot create'(bots, values) {
|
|
629
654
|
const made = createBot(bots, { name: values.name, harness: values.harness, charter: values.charter });
|
|
630
655
|
// The bot's AGENTS.md is the rules build's, here as everywhere else, so
|
|
@@ -821,7 +846,16 @@ const commands = {
|
|
|
821
846
|
}
|
|
822
847
|
// --at on its own moves grooming that is on to another time.
|
|
823
848
|
const ask = asks[0] ?? (values.at === undefined ? undefined : 'move');
|
|
824
|
-
|
|
849
|
+
// What each run is launched with when it runs on Codex (#238): given only
|
|
850
|
+
// with --run-on, and refused by grooming() when it is not.
|
|
851
|
+
const given = ['run-on', 'model', 'effort', 'extra-arg'].some((flag) => values[flag] !== undefined);
|
|
852
|
+
const run = !given ? undefined : {
|
|
853
|
+
harness: values['run-on'],
|
|
854
|
+
...(values.model === undefined ? {} : { model: values.model }),
|
|
855
|
+
...(values.effort === undefined ? {} : { effort: values.effort }),
|
|
856
|
+
...(values['extra-arg'] === undefined ? {} : { extra_args: values['extra-arg'] }),
|
|
857
|
+
};
|
|
858
|
+
const groom = grooming(bots, { at: values.at, ask, run });
|
|
825
859
|
return { answer: { bots, groom }, lines: groomLines(groom, bots) };
|
|
826
860
|
},
|
|
827
861
|
|
|
@@ -1275,6 +1309,7 @@ function jobLines(jobs, bots, head) {
|
|
|
1275
1309
|
const [job] = jobs;
|
|
1276
1310
|
return [
|
|
1277
1311
|
`${head} on, daily at ${job.at ?? job.cron} job ${job.id}`,
|
|
1312
|
+
...(job.run === undefined ? [] : [`${more}Each run is a temporary ${job.run.harness} session: model ${job.run.model ?? `${job.run.harness}'s own`}, effort ${job.run.effort ?? `${job.run.harness}'s own`}.`]),
|
|
1278
1313
|
`${more}Each run renews it; with no run, Claude Code ends it at ${job.expires}.`,
|
|
1279
1314
|
`Turn it off: ${groomCommand(bots)} --off`,
|
|
1280
1315
|
];
|
package/src/groom.js
CHANGED
|
@@ -25,12 +25,20 @@
|
|
|
25
25
|
// asked for, or none. Run in any order, the last one typed is what holds
|
|
26
26
|
// (review of PR #322).
|
|
27
27
|
//
|
|
28
|
+
// A job can make its runs on Codex instead (#238, PRD 6.8): the session stays the
|
|
29
|
+
// Claude Code one, the clock and the reader, and at each fire it makes the run
|
|
30
|
+
// as a temporary Codex session of its own (`obk temp make`), with the job's
|
|
31
|
+
// model and effort on the run's launch line. The run does the grooming, sends
|
|
32
|
+
// its report back by the kit's mail and is retired by the session, which first
|
|
33
|
+
// retires any run an earlier fire left. What the runs are on is in the job's
|
|
34
|
+
// prompt and nowhere else, so the kit reads it back from there.
|
|
35
|
+
//
|
|
28
36
|
// Claude Code ends a recurring job a week after it was made, so the job's own
|
|
29
37
|
// prompt ends by renewing it. A renewal that fails either way shows here: no
|
|
30
38
|
// job means the fleet has quietly stopped being groomed, two mean it is groomed
|
|
31
39
|
// twice, and both are said plainly.
|
|
32
40
|
|
|
33
|
-
import { existsSync, realpathSync } from 'node:fs';
|
|
41
|
+
import { existsSync, mkdirSync, realpathSync, writeFileSync } from 'node:fs';
|
|
34
42
|
import path from 'node:path';
|
|
35
43
|
|
|
36
44
|
import { botDir, readBot, requireBotsFolder } from './bot.js';
|
|
@@ -38,7 +46,7 @@ import { readBook } from './book.js';
|
|
|
38
46
|
import { claudeTranscript } from './conversations.js';
|
|
39
47
|
import { harnessOf, ownCli, shellWord } from './launch.js';
|
|
40
48
|
import { tabs, tabToTypeInto, typeIntoTab } from './orca.js';
|
|
41
|
-
import { BOT_FATHER } from './up.js';
|
|
49
|
+
import { BOT_FATHER, promptPath } from './up.js';
|
|
42
50
|
import { lines } from './usage.js';
|
|
43
51
|
|
|
44
52
|
/** Bot Father's session the grooming runs in. */
|
|
@@ -50,6 +58,12 @@ const AT = /^([01]\d|2[0-3]):([0-5]\d)$/;
|
|
|
50
58
|
/** A daily cron as the kit writes one: minute, hour, every day. */
|
|
51
59
|
const DAILY = /^(\d{1,2}) (\d{1,2}) \* \* \*$/;
|
|
52
60
|
|
|
61
|
+
/** The harnesses a job can make its runs on, besides running them in the session itself (#238). */
|
|
62
|
+
const RUN_ON = ['codex'];
|
|
63
|
+
|
|
64
|
+
/** The name each run is given, filled in by the session at the fire it makes it. */
|
|
65
|
+
const RUN_NAME = 'groom-<YYYYMMDD-HHMM>';
|
|
66
|
+
|
|
53
67
|
/** How long Claude Code keeps a recurring job after it was made (tech notes, section 2). */
|
|
54
68
|
const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
|
|
55
69
|
|
|
@@ -79,8 +93,9 @@ export const markerOf = (bots) => `obk grooming for ${bots}`;
|
|
|
79
93
|
* (or null), the grooming jobs alive in its conversation as the command found
|
|
80
94
|
* them, before anything it typed was done, and what it typed (or null).
|
|
81
95
|
*/
|
|
82
|
-
export function grooming(bots, { at, ask } = {}) {
|
|
96
|
+
export function grooming(bots, { at, ask, run } = {}) {
|
|
83
97
|
requireBotsFolder(bots);
|
|
98
|
+
refuseRun(run, ask);
|
|
84
99
|
|
|
85
100
|
// The folder as the file system knows it: Claude Code files a transcript
|
|
86
101
|
// under the real path, and Orca knows a tab's project by it.
|
|
@@ -108,7 +123,7 @@ export function grooming(bots, { at, ask } = {}) {
|
|
|
108
123
|
? groomingJobs(ofThisProcess(home, entry), markerOf(bots), Date.now())
|
|
109
124
|
: [];
|
|
110
125
|
|
|
111
|
-
const line = ask === undefined ? undefined : lineFor(ask, { bots, session, jobs, at });
|
|
126
|
+
const line = ask === undefined ? undefined : lineFor(ask, { bots, session, jobs, at, run });
|
|
112
127
|
if (line !== undefined) typeInto(bots, home, session, tab, line);
|
|
113
128
|
|
|
114
129
|
// A move is turning it on at another time, and is typed as one.
|
|
@@ -120,7 +135,7 @@ export function grooming(bots, { at, ask } = {}) {
|
|
|
120
135
|
* nothing to ask. Everything that makes the ask impossible refuses here, before
|
|
121
136
|
* Orca is asked for anything.
|
|
122
137
|
*/
|
|
123
|
-
function lineFor(ask, { bots, session, jobs, at }) {
|
|
138
|
+
function lineFor(ask, { bots, session, jobs, at, run }) {
|
|
124
139
|
// Nothing to turn off is an answer, unless a session is there to be told: an
|
|
125
140
|
// `--on` typed a moment ago may still be waiting in its tab.
|
|
126
141
|
if (ask === 'off' && jobs.length === 0 && !(session?.harness === 'claude' && session.up)) return undefined;
|
|
@@ -147,7 +162,28 @@ function lineFor(ask, { bots, session, jobs, at }) {
|
|
|
147
162
|
? `grooming is off, so there is no time to keep. Say when: ${groomCommand(bots)} --on --at <HH:MM>.`
|
|
148
163
|
: `the grooming jobs there run at different times (${times.join(', ')}). Say which one to keep: ${groomCommand(bots)} --on --at <HH:MM>.`);
|
|
149
164
|
}
|
|
150
|
-
|
|
165
|
+
// A move keeps what the runs are on, when every job there says the same.
|
|
166
|
+
const runs = [...new Set(jobs.map((job) => JSON.stringify(job.run ?? null)))];
|
|
167
|
+
const keep = ask === 'move' && runs.length === 1 ? JSON.parse(runs[0]) ?? undefined : undefined;
|
|
168
|
+
return onLine(bots, cronOf(when), jobs, run ?? keep);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Refuse what the run was given before anything else is looked at: its flags
|
|
173
|
+
* go with --on, --run-on takes codex, and a model, an effort or an extra
|
|
174
|
+
* argument is the run's, so it goes with --run-on codex.
|
|
175
|
+
*/
|
|
176
|
+
function refuseRun(run, ask) {
|
|
177
|
+
if (run === undefined) return;
|
|
178
|
+
if (run.harness === undefined) {
|
|
179
|
+
throw new Error('--model, --effort and --extra-arg are what each grooming run is launched with when it runs on Codex, and go with --run-on codex. The grooming session\'s own runs use its own settings. Nothing was typed.');
|
|
180
|
+
}
|
|
181
|
+
if (!RUN_ON.includes(run.harness)) {
|
|
182
|
+
throw new Error(`--run-on takes codex: grooming runs either in its Claude Code session, as it does without --run-on, or as a temporary Codex session made at each fire. Got: ${run.harness === '' ? 'nothing' : run.harness}. Nothing was typed.`);
|
|
183
|
+
}
|
|
184
|
+
if (ask !== 'on') {
|
|
185
|
+
throw new Error('--run-on says what the grooming runs are on, and goes with --on. Nothing was typed.');
|
|
186
|
+
}
|
|
151
187
|
}
|
|
152
188
|
|
|
153
189
|
/**
|
|
@@ -230,7 +266,8 @@ export function groomingJobs(entries, marker, now) {
|
|
|
230
266
|
const { cron, prompt, recurring } = call.input ?? {};
|
|
231
267
|
const made = Date.parse(entry.timestamp ?? '');
|
|
232
268
|
if (recurring === false || typeof prompt !== 'string' || !prompt.startsWith(marker) || Number.isNaN(made)) continue;
|
|
233
|
-
|
|
269
|
+
const run = runIn(prompt);
|
|
270
|
+
alive.set(result.id, { id: result.id, at: timeOf(cron), cron, made, expires: made + WEEK_MS, ...(run === undefined ? {} : { run }) });
|
|
234
271
|
}
|
|
235
272
|
}
|
|
236
273
|
return [...alive.values()]
|
|
@@ -239,6 +276,32 @@ export function groomingJobs(entries, marker, now) {
|
|
|
239
276
|
.map((job) => ({ ...job, made: new Date(job.made).toISOString(), expires: new Date(job.expires).toISOString() }));
|
|
240
277
|
}
|
|
241
278
|
|
|
279
|
+
/**
|
|
280
|
+
* What a job's runs are on, read out of its prompt: the harness, model and
|
|
281
|
+
* effort its \`temp make\` is given, each left out when it was not; undefined
|
|
282
|
+
* for a job whose runs are the session's own.
|
|
283
|
+
*/
|
|
284
|
+
function runIn(prompt) {
|
|
285
|
+
const make = / temp make ([^`]*)`/.exec(prompt);
|
|
286
|
+
if (make === null) return undefined;
|
|
287
|
+
const value = (flag) => {
|
|
288
|
+
const found = new RegExp(`(?:^| )--${flag}(?:=| )('[^']*'|\\S+)`).exec(make[1]);
|
|
289
|
+
return found === null ? undefined : found[1].replace(/^'(.*)'$/, '$1');
|
|
290
|
+
};
|
|
291
|
+
const run = { harness: value('harness') };
|
|
292
|
+
if (run.harness === undefined) return undefined;
|
|
293
|
+
for (const field of ['model', 'effort']) {
|
|
294
|
+
const got = value(field);
|
|
295
|
+
if (got !== undefined) run[field] = got;
|
|
296
|
+
}
|
|
297
|
+
// Each extra argument as the job wrote it, one shell word, read back to what
|
|
298
|
+
// it was given as, so a move keeps them all (review of PR #439).
|
|
299
|
+
const extra = [...make[1].matchAll(/(?:^| )--extra-arg=('(?:[^']|'\\'')*'|[A-Za-z0-9,._+:@%/=-]+)/g)]
|
|
300
|
+
.map((found) => (found[1].startsWith('\'') ? found[1].slice(1, -1).replaceAll(`'\\''`, '\'') : found[1]));
|
|
301
|
+
if (extra.length > 0) run.extra_args = extra;
|
|
302
|
+
return run;
|
|
303
|
+
}
|
|
304
|
+
|
|
242
305
|
/** `HH:MM` as the daily cron Claude Code takes: `04:00` is `0 4 * * *`. */
|
|
243
306
|
const cronOf = (at) => {
|
|
244
307
|
const [, hour, minute] = AT.exec(at);
|
|
@@ -268,11 +331,81 @@ const run = (bots) =>
|
|
|
268
331
|
* fired with, so the job carries itself on; the one that fired is then the
|
|
269
332
|
* older of the two.
|
|
270
333
|
*/
|
|
271
|
-
const jobPrompt = (bots, cron) =>
|
|
272
|
-
`${run(bots)} When the report is sent,
|
|
334
|
+
const jobPrompt = (bots, cron, onRun) => (onRun === undefined
|
|
335
|
+
? `${run(bots)} When the report is sent, ${renewal(cron)}`
|
|
336
|
+
// A run on Codex reports in a later turn, and may never: the job renews at
|
|
337
|
+
// the fire, once the run is made, so a run that never reports does not end
|
|
338
|
+
// the schedule with it (review of PR #439).
|
|
339
|
+
: `${runElsewhere(bots, onRun)} Then ${renewal(cron)} Then end your turn. ${readReport(bots)}`);
|
|
340
|
+
|
|
341
|
+
/** The part of a job that renews it, which Claude Code would otherwise end a week after it was made. */
|
|
342
|
+
const renewal = (cron) =>
|
|
343
|
+
'renew this schedule, which Claude Code ends a week after it was made: '
|
|
273
344
|
+ `call CronCreate with cron "${cron}", recurring true, and this whole prompt, word for word, as its prompt; `
|
|
274
345
|
+ `then call CronList and CronDelete every other job there whose prompt starts with "${PREFIX}".`;
|
|
275
346
|
|
|
347
|
+
/**
|
|
348
|
+
* The part of a job whose runs are on Codex (#238): clear what an earlier run
|
|
349
|
+
* left, make this fire's run, and read its report when it comes. Each command
|
|
350
|
+
* is the kit by its own path, as the run above names it, because a Claude Code
|
|
351
|
+
* allow rule matches a command as written (#344). The run's task goes in a file
|
|
352
|
+
* the kit writes now, beside the bots folder, as it leaves every prompt.
|
|
353
|
+
*/
|
|
354
|
+
function runElsewhere(bots, onRun) {
|
|
355
|
+
const kit = shellWord(ownCli());
|
|
356
|
+
const folder = shellWord(bots);
|
|
357
|
+
const retire = `\`${kit} temp retire --bots ${folder} --name ${RUN_NAME}\``;
|
|
358
|
+
const make = [
|
|
359
|
+
`${kit} temp make --bots ${folder} --name ${RUN_NAME} --harness ${onRun.harness}`,
|
|
360
|
+
...(onRun.model === undefined ? [] : [`--model ${shellWord(onRun.model)}`]),
|
|
361
|
+
...(onRun.effort === undefined ? [] : [`--effort ${shellWord(onRun.effort)}`]),
|
|
362
|
+
...(onRun.extra_args ?? []).map((arg) => `--extra-arg=${shellWord(arg)}`),
|
|
363
|
+
`--prompt-file ${shellWord(writeRunTask(bots))}`,
|
|
364
|
+
].join(' ');
|
|
365
|
+
return `${markerOf(bots)}: the daily grooming for this fleet, run on ${onRun.harness} as a temporary session of yours `
|
|
366
|
+
+ `that does the work and reports back to you. First, if any of your temporary sessions whose names start with groom- `
|
|
367
|
+
+ `is still there (${BOT_FATHER}'s bot.yaml lists them), a run an earlier fire left, retire each with ${retire}, `
|
|
368
|
+
+ `its own name in place of ${RUN_NAME}. Then make this fire's run with \`${make}\`, `
|
|
369
|
+
+ `where ${RUN_NAME} is this fire's date and time. Its tab can stop on Codex's first-run screens: answer its folder trust `
|
|
370
|
+
+ 'yourself, as the make\'s output and your rules say, and the review of its hooks with '
|
|
371
|
+
+ `\`${kit} temp trust-hooks --bots ${folder} --name ${RUN_NAME}\`, its name in place of ${RUN_NAME}.`;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** The part of a Codex job for the later turn its run's report comes in: read it, send it on, retire the run. */
|
|
375
|
+
function readReport(bots) {
|
|
376
|
+
const retire = `\`${shellWord(ownCli())} temp retire --bots ${shellWord(bots)} --name ${RUN_NAME}\``;
|
|
377
|
+
const road = `\`${shellWord(ownCli())} message to --bots ${shellWord(bots)} --to ${BOT_FATHER}/<that session>\``;
|
|
378
|
+
// Claude Code's own messaging reaches every session on the machine, so a
|
|
379
|
+
// name that only looks right can be another fleet's session (#450).
|
|
380
|
+
return `When a line comes saying fleet mail from that run is waiting, read the mail the way the line says: it is the grooming report. `
|
|
381
|
+
+ `Send it on, as it is, to ${BOT_FATHER}'s management session, as a grooming run's report goes: to exactly the address or mailbox `
|
|
382
|
+
+ `${road} answers, and to no other. If your messaging does not list that address, send the report nowhere else, and say so. `
|
|
383
|
+
+ `Then retire the run with ${retire}, its name in place of ${RUN_NAME}. Schedule nothing then: the job renewed itself when it fired.`;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Write the task a Codex run is started with, and answer where: do the daily
|
|
388
|
+
* grooming with the obk-grooming skill, send the report to the grooming session
|
|
389
|
+
* by the kit's mail, and stop. It schedules nothing: the schedule is the
|
|
390
|
+
* grooming session's.
|
|
391
|
+
*/
|
|
392
|
+
function writeRunTask(bots) {
|
|
393
|
+
const real = realHome(bots);
|
|
394
|
+
const kit = shellWord(ownCli());
|
|
395
|
+
const file = path.resolve(promptPath(real, BOT_FATHER, `${GROOMING}-run`));
|
|
396
|
+
const task = `You are one run of the daily grooming of the fleet in ${real}, made by ${BOT_FATHER}'s ${GROOMING} session. `
|
|
397
|
+
+ 'Run the daily grooming for this fleet, with the obk-grooming skill. Fix this run\'s end now and read what has happened '
|
|
398
|
+
+ `since the last run's end up to it, counting with ${kit} usage --since <last end> --until <this end>, and write this end down for the next run. `
|
|
399
|
+
+ 'Work out what it cost with obk-finops, and keep each bot\'s profile notes. '
|
|
400
|
+
+ `The kit here is ${kit}: run it wherever a skill says obk. The bots folder is ${real}. `
|
|
401
|
+
+ `When the grooming is done, send one short report to ${BOT_FATHER}/${GROOMING} by the kit's mail: `
|
|
402
|
+
+ `ask for the road with ${kit} message to --bots ${shellWord(real)} --to ${BOT_FATHER}/${GROOMING}, then send it with the message send command its answer names. `
|
|
403
|
+
+ 'Then stop: schedule nothing, make no session, and say nothing more.\n';
|
|
404
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
405
|
+
writeFileSync(file, task);
|
|
406
|
+
return file;
|
|
407
|
+
}
|
|
408
|
+
|
|
276
409
|
/**
|
|
277
410
|
* The part of a line that clears the grooming jobs the session has when it runs
|
|
278
411
|
* the line, which is not always what the kit saw when it typed it.
|
|
@@ -281,11 +414,11 @@ const clearing = (jobs) =>
|
|
|
281
414
|
`call CronList, and call CronDelete on every job there whose prompt starts with "${PREFIX}"`
|
|
282
415
|
+ (jobs.length === 0 ? '' : ` (when this was typed they were ${jobs.map((job) => job.id).join(', ')})`);
|
|
283
416
|
|
|
284
|
-
const onLine = (bots, cron, jobs) =>
|
|
417
|
+
const onLine = (bots, cron, jobs, onRun) =>
|
|
285
418
|
`obk groom asks you to schedule the daily grooming. First ${clearing(jobs)}. `
|
|
286
419
|
+ `Then call CronCreate once, with cron "${cron}", recurring true, and as its prompt everything after "Prompt:" `
|
|
287
420
|
+ 'at the end of this line, word for word. Do not run the grooming now; say in one line what you scheduled. '
|
|
288
|
-
+ `Prompt: ${jobPrompt(bots, cron)}`;
|
|
421
|
+
+ `Prompt: ${jobPrompt(bots, cron, onRun)}`;
|
|
289
422
|
|
|
290
423
|
const offLine = (jobs) =>
|
|
291
424
|
`obk groom asks you to turn the daily grooming off: ${clearing(jobs)}. `
|
package/src/health.js
CHANGED
|
@@ -25,7 +25,7 @@ import { bookFile, readBook, sessionIdsIn, tabIdsIn } from './book.js';
|
|
|
25
25
|
import { botDir, botNames, botsDir, readBot, unknownKeys } from './bot.js';
|
|
26
26
|
import { transcriptsIn } from './conversations.js';
|
|
27
27
|
import { hookTrouble } from './hooks.js';
|
|
28
|
-
import { bypassFlags, harnessOf, HARNESSES, ownCli, sessionTrouble, SHELL_ENV, shellWord } from './launch.js';
|
|
28
|
+
import { bypassFlags, harnessOf, HARNESSES, isAddressOf, ownCli, sessionTrouble, SHELL_ENV, shellWord } from './launch.js';
|
|
29
29
|
import { frontOfTab, orcaDefaultArgs, projects, tabs, wordsOfProcess } from './orca.js';
|
|
30
30
|
import { permissionsTrouble } from './permissions.js';
|
|
31
31
|
import { TAB_ENV } from './record.js';
|
|
@@ -259,7 +259,26 @@ function runningOn(bots, home, bot, book, handles, sessions) {
|
|
|
259
259
|
// that is not its own harness, or a front that cannot be read, leaves it
|
|
260
260
|
// unsaid, and nothing unsaid is a finding.
|
|
261
261
|
const restart = `${shellWord(ownCli())} restart --bots ${shellWord(bots)} --bot ${bot.name} --session ${session.name}`;
|
|
262
|
+
// A Claude session is written to by its name, and Claude Code's own
|
|
263
|
+
// messaging reaches every session on the machine. One under a name the kit
|
|
264
|
+
// did not make, such as the bare <bot>.<session> every fleet had before
|
|
265
|
+
// #286, can be written to by another fleet's session (#450). Its next start
|
|
266
|
+
// through the kit gives it a name of its own; one that Orca brought back,
|
|
267
|
+
// or that has run since, keeps the old one until then.
|
|
262
268
|
const front = frontOfTab(handle);
|
|
269
|
+
const address = entry.address;
|
|
270
|
+
if (harnessOf(session, bot.harness) === 'claude' && typeof address === 'string' && address !== '' && !isAddressOf(bot.name, session.name, address)) {
|
|
271
|
+
const shared = address === `${bot.name}.${session.name}`
|
|
272
|
+
? `the name every fleet's ${bot.name}/${session.name} was given before #286, so it is shared: a Claude session of another fleet with that bot and session can write to it, thinking it is its own`
|
|
273
|
+
: `not one the kit made for it, so it may not be this session's alone: another session, in this fleet or another, can go by it too`;
|
|
274
|
+
// A restart closes the tab, and with no conversation in the book it
|
|
275
|
+
// refuses rather than end one that is running, so the fix starts there.
|
|
276
|
+
// With only the shell in front there is nothing to end, and it goes.
|
|
277
|
+
const fix = typeof entry.session === 'string' || front.front === 'shell'
|
|
278
|
+
? `${restart} starts it on an address of its own, <bot>.<session> and a token.`
|
|
279
|
+
: `The book does not say which conversation it is running, and a restart refuses until it does: write the id into ${bookFile(home)} under ${session.name} as session: <id>, then ${restart} starts it on an address of its own, <bot>.<session> and a token.`;
|
|
280
|
+
found.push(finding('session', bookFile(home), `${bot.name}'s session ${session.name} goes by the address ${address}, ${shared}. ${fix}`, bot.name));
|
|
281
|
+
}
|
|
263
282
|
if (front.front === 'shell') {
|
|
264
283
|
found.push(finding('session', entry.tab, `${bot.name}'s session ${session.name} is not running: its tab ${entry.tab} is open with only the tab's shell in front, so its harness quit or crashed. obk up finds the tab open and types nothing into it, so it does not bring the session back; ${restart} does.`, bot.name));
|
|
265
284
|
continue;
|