@assuredloop/orca-bot-kit 0.25.3 → 0.25.4
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/SETUP.md +1 -1
- package/package.json +1 -1
- package/src/clear.js +25 -19
- package/src/cli.js +1 -0
- package/src/health.js +16 -1
- package/src/name.js +4 -3
- package/src/orca.js +16 -0
- package/src/up.js +32 -11
package/SETUP.md
CHANGED
|
@@ -201,7 +201,7 @@ waiting for:
|
|
|
201
201
|
| Claude Code's folder trust list | `\x1b[B\r` | down, then return: its selection starts on **No, exit** | Claude Code 2.1.283 |
|
|
202
202
|
| Codex's directory trust, `1. Trust and continue` (older: `1. Yes, continue`) | `\r` | return: its selection starts on **Trust and continue** | Codex 0.160.0 |
|
|
203
203
|
| 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.160.0 |
|
|
204
|
-
| Codex's `/new`: `Where should the new conversation run?` | `\r` | return: its selection starts on **Current checkout** (bot home) | Codex 0.160.0 |
|
|
204
|
+
| Codex's `/new`: `Where should the new conversation run?` | `\r` | return: its selection starts on **Current checkout** (bot home); on 0.162.0 it is called **1. Use current Git worktree** (Keep using the current working directory). Never **Create new Git worktree** | Codex 0.160.0; Codex 0.162.0 (#516) |
|
|
205
205
|
| Codex's update offer, `1. Update now` | `\r` | return: its selection starts on **Update now**, which accepts it | Codex 0.156.1; seen on 0.157.1 (offering 0.158.0) with the selection on **Update now**, not pressed; 0.158.0 offered 0.160.0, taken on 2026-10-02 by Bot Father's restarts |
|
|
206
206
|
| `[oh-my-zsh] Would you like to update?` | `n` | they update their own shell | oh-my-zsh bf77e35 |
|
|
207
207
|
| Claude Code's `Teach auto mode about your environment?` | `\x1b` | Esc, which cancels it (**Not now**). On 2.1.283 it is a form, not a list: `How you use Claude here`, `Also scan shell history`, `Also scan your other repos`, `Continue`, and `Enter to continue · Esc to cancel`. **Never send a return**: Enter is **Continue**, which starts a scan of the project, recent Claude sessions and, by default, the machine's shell history | Not yet proven: Esc cancels in Claude Code 2.1.283's code, not yet seen live (#370); the form was seen live twice on 2.1.283, in #261's live test and #239's live run 1 (#416); 2.1.278 showed a numbered list, answered with 2. Not now |
|
package/package.json
CHANGED
package/src/clear.js
CHANGED
|
@@ -38,9 +38,13 @@ const POINTER = { claude: '❯', codex: '›' };
|
|
|
38
38
|
*/
|
|
39
39
|
const RECORD_LINE = 'obk: this conversation was started by obk session clear, and this line is only so the kit can record it. Reply "ok"; nothing else is asked.';
|
|
40
40
|
|
|
41
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* What Codex asks after `/new`, and the one answer the kit gives it: the bot
|
|
43
|
+
* home it runs in. Codex 0.162.0 calls it by another name, and its whole row
|
|
44
|
+
* has to be as captured, but for the padding before its description (#516).
|
|
45
|
+
*/
|
|
42
46
|
const WHERE_TO_RUN = 'Where should the new conversation run?';
|
|
43
|
-
const CURRENT_CHECKOUT = /^ *› +1\. Current checkout\b
|
|
47
|
+
const CURRENT_CHECKOUT = /^ *› +1\. Current checkout\b|^ *› 1\. Use current Git worktree\s+Keep using the current working directory *$/;
|
|
44
48
|
|
|
45
49
|
/** Codex's empty input line: its pointer alone, or with its placeholder. */
|
|
46
50
|
const CODEX_EMPTY = ['›', '› Ask Codex to do anything'];
|
|
@@ -51,7 +55,8 @@ const CODEX_EMPTY = ['›', '› Ask Codex to do anything'];
|
|
|
51
55
|
* selected row stands for what Return will run: with the menu open, Return
|
|
52
56
|
* runs the selected command, and the menu is narrowed to what was typed
|
|
53
57
|
* (codex-rs/tui/src/bottom_pane/chat_composer.rs, rust-v0.160.0). The
|
|
54
|
-
* architect's ruling on #391, after live run 4.
|
|
58
|
+
* architect's ruling on #391, after live run 4. Only where Orca gives no
|
|
59
|
+
* draft: a draft is the line's text, and has to be the command (#516).
|
|
55
60
|
*/
|
|
56
61
|
const CODEX_BARE_LINE = '0.160.0';
|
|
57
62
|
|
|
@@ -221,14 +226,13 @@ async function enter(it, verb, before = () => {}) {
|
|
|
221
226
|
* when it was typed.
|
|
222
227
|
*/
|
|
223
228
|
export async function typeCommand(it, command, { before = () => {}, check = menuWrong, cannot = () => '', deadline = Infinity } = {}) {
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
//
|
|
229
|
+
// Nothing may be in the input line before: no draft for the command to join.
|
|
230
|
+
// Orca gives a draft in the line beside the screen, and the screen's line
|
|
231
|
+
// then reads the pointer alone (#510, #516). Where it gives none, Codex's
|
|
232
|
+
// line on the screen has to be empty.
|
|
227
233
|
const emptyLine = ({ rows, draft }) => {
|
|
228
|
-
if (
|
|
229
|
-
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
234
|
+
if (draft !== undefined) throw new Error(`${it.name}: nothing was typed, because its input line holds a draft: it reads "${draft}".${shownEnd(rows)}`);
|
|
235
|
+
if (it.harness !== 'codex') return;
|
|
232
236
|
const line = rows.findLast((row) => /^ *›/.test(row))?.trim();
|
|
233
237
|
if (!CODEX_EMPTY.includes(line)) {
|
|
234
238
|
throw new Error(`${it.name}: nothing was typed, because its input line is not empty: it reads "${line ?? 'nothing'}".${shownEnd(rows)}`);
|
|
@@ -415,10 +419,11 @@ function menuWrong(rows, harness, command, version, draft) {
|
|
|
415
419
|
if (at < 0) return { why: 'its screen shows no input line' };
|
|
416
420
|
// Claude Code 2.1.288 puts a non-breaking space after its pointer (live run 4).
|
|
417
421
|
const line = rows[at].replaceAll('\u00a0', ' ').trim();
|
|
418
|
-
|
|
419
|
-
//
|
|
420
|
-
//
|
|
422
|
+
// Orca 1.4.223 gives the line's text as the draft, and the line on the
|
|
423
|
+
// screen then reads the pointer alone: Claude Code 2.1.296's (#510, probe 4)
|
|
424
|
+
// and Codex 0.162.0's (#516).
|
|
421
425
|
const text = draft === undefined ? line : `${POINTER[harness]} ${draft}`;
|
|
426
|
+
if (harness === 'codex') return codexMenuWrong(rows, at, text, command, version);
|
|
422
427
|
if (text !== `${POINTER[harness]} ${command}`) return { why: `its input line reads "${text}"` };
|
|
423
428
|
return claudeMenuWrong(rows, at, command);
|
|
424
429
|
}
|
|
@@ -445,11 +450,12 @@ function claudeMenuWrong(rows, at, command) {
|
|
|
445
450
|
|
|
446
451
|
/**
|
|
447
452
|
* Codex draws its slash menu above the input line: a row per command, the
|
|
448
|
-
* selected one starting with its pointer, then a blank row
|
|
449
|
-
* line (its own snapshot tests, chat_composer
|
|
450
|
-
* rust-v0.160.0; live run 4). The menu has
|
|
451
|
-
* selected one, naming the command. The input
|
|
452
|
-
*
|
|
453
|
+
* selected one starting with its pointer, then a blank row on 0.160.0 and none
|
|
454
|
+
* on 0.162.0, then the input line (its own snapshot tests, chat_composer
|
|
455
|
+
* slash_popup_*.snap, rust-v0.160.0; live run 4; #516's probe). The menu has
|
|
456
|
+
* to hold one command row, the selected one, naming the command. The input
|
|
457
|
+
* line, Orca's draft where it gives one, reads the command, or, with no draft
|
|
458
|
+
* on the Codex whose line Orca shows bare, its pointer alone.
|
|
453
459
|
*/
|
|
454
460
|
function codexMenuWrong(rows, at, line, command, version) {
|
|
455
461
|
const bare = line === '›' && version === CODEX_BARE_LINE;
|
|
@@ -494,7 +500,7 @@ async function answerWhereToRun(it, handle, command) {
|
|
|
494
500
|
const pointer = rows.findLast((row) => /^ *›/.test(row));
|
|
495
501
|
if (pointer === undefined || !CURRENT_CHECKOUT.test(pointer)) {
|
|
496
502
|
send(handle, '\x1b');
|
|
497
|
-
throw new Error(`${it.name}: after ${command}, Codex asked "${WHERE_TO_RUN}" with its selection on "${pointer?.trim() ?? 'nothing'}" rather than "1. Current checkout", so it was backed out of with Esc and nothing else was typed. Look at its tab.`);
|
|
503
|
+
throw new Error(`${it.name}: after ${command}, Codex asked "${WHERE_TO_RUN}" with its selection on "${pointer?.trim() ?? 'nothing'}" rather than "1. Current checkout" (on Codex 0.162.0, "1. Use current Git worktree"), so it was backed out of with Esc and nothing else was typed. Look at its tab.`);
|
|
498
504
|
}
|
|
499
505
|
send(handle, '\r');
|
|
500
506
|
return;
|
package/src/cli.js
CHANGED
|
@@ -1203,6 +1203,7 @@ const commands = {
|
|
|
1203
1203
|
const said = {
|
|
1204
1204
|
made: `${who} has its mailbox ${answer.mailbox}, made in this tab.`,
|
|
1205
1205
|
bound: `${who}'s mailbox ${answer.mailbox} is bound to this tab.`,
|
|
1206
|
+
replaced: `${who}'s mailbox ${answer.replaced} is not one this Orca has, as when the book was written on another machine, so it has a new one, ${answer.mailbox}, made in this tab and written into the book.`,
|
|
1206
1207
|
none: `${who} gets no mailbox: it is a Codex session with its sandbox switch off, which could not read one.`,
|
|
1207
1208
|
};
|
|
1208
1209
|
return { answer, lines: [said[answer.change]] };
|
package/src/health.js
CHANGED
|
@@ -26,7 +26,7 @@ import { botDir, botNames, botsDir, readBot, tempRoles, unknownKeys } from './bo
|
|
|
26
26
|
import { transcriptsIn } from './conversations.js';
|
|
27
27
|
import { hookTrouble } from './hooks.js';
|
|
28
28
|
import { bypassFlags, harnessOf, HARNESSES, isAddressOf, ownCli, sessionTrouble, SHELL_ENV, shellWord } from './launch.js';
|
|
29
|
-
import { frontOfTab, orcaDefaultArgs, projects, tabs, wordsOfProcess } from './orca.js';
|
|
29
|
+
import { frontOfTab, orcaDefaultArgs, projects, runMissing, tabs, wordsOfProcess } from './orca.js';
|
|
30
30
|
import { permissionsTrouble } from './permissions.js';
|
|
31
31
|
import { TAB_ENV } from './record.js';
|
|
32
32
|
import { agentsTrouble, rulesStamp } from './rules.js';
|
|
@@ -387,6 +387,21 @@ function inOrca(bots, home, bot, setups, sessions) {
|
|
|
387
387
|
found.push(finding('session', entry.tab, `${bot.name}'s session ${name} is in the book with tab ${entry.tab}, and Orca has no tab of that id: the tab was closed, or the machine was restarted. The conversation is in the book, and obk up opens a tab and brings it back.`, bot.name));
|
|
388
388
|
}
|
|
389
389
|
|
|
390
|
+
// A Run Orca does not have, as when the book was written on another
|
|
391
|
+
// machine: mail sent to it is refused. The session's next start makes it
|
|
392
|
+
// a new one (#508).
|
|
393
|
+
if (typeof entry?.mailbox === 'string' && runMissing(entry.mailbox)) {
|
|
394
|
+
// The bot's unpause leaves a session paused on its own as it is, so a
|
|
395
|
+
// session under both marks needs both commands.
|
|
396
|
+
const unpause = `${shellWord(ownCli())} unpause --bots ${shellWord(bots)} --bot ${bot.name}`;
|
|
397
|
+
const steps = [
|
|
398
|
+
...(bot.paused === true ? [unpause] : []),
|
|
399
|
+
...(bot.sessions.some((session) => session.name === name && session.paused === true) ? [`${unpause} --session ${name}`] : []),
|
|
400
|
+
];
|
|
401
|
+
const fix = steps.length > 0 ? steps.join(', then ') : `${shellWord(ownCli())} restart --bots ${shellWord(bots)} --bot ${bot.name} --session ${name}`;
|
|
402
|
+
found.push(finding('session', bookFile(home), `${bot.name}'s session ${name} has the mailbox ${entry.mailbox} in the book, and Orca has no Run of that id: it was made by another Orca, as on another machine, so mail sent to it is refused. The session's next start makes it a new one: ${fix}`, bot.name));
|
|
403
|
+
}
|
|
404
|
+
|
|
390
405
|
const unclaimed = (Array.isArray(entry?.unclaimed) ? entry.unclaimed : []).filter((id) => !helpers.has(id));
|
|
391
406
|
if (unclaimed.length > 0) {
|
|
392
407
|
found.push(finding('leftover', bookFile(home), `The book notes conversations under ${bot.name}'s session ${name} that no session of this bot claims: ${unclaimed.join(', ')}. They ran in this bot's folder, and the kit will not say whose they were. To give one back, write it into ${bookFile(home)} under ${name} as session: <id> and run obk up again.`, bot.name));
|
package/src/name.js
CHANGED
|
@@ -125,12 +125,13 @@ function nameOf(thread) {
|
|
|
125
125
|
* pointer and the command exactly, and no row of its slash menu stands above
|
|
126
126
|
* it. With words after the command Codex has closed its menu (chat_composer.rs
|
|
127
127
|
* `sync_command_popup`, rust-v0.160.0), so a menu still open means the line is
|
|
128
|
-
* not what it seems.
|
|
128
|
+
* not what it seems. Where Orca gives a draft, the draft is the line's text:
|
|
129
|
+
* Codex 0.162.0's line on the screen then reads its pointer alone (#516).
|
|
129
130
|
*/
|
|
130
|
-
function renameWrong(rows, _harness, command) {
|
|
131
|
+
function renameWrong(rows, _harness, command, _version, draft) {
|
|
131
132
|
const at = rows.findLastIndex((row) => /^ *›/.test(row));
|
|
132
133
|
if (at < 0) return { why: 'its screen shows no input line' };
|
|
133
|
-
const line = rows[at].replaceAll(' ', ' ').trim()
|
|
134
|
+
const line = draft === undefined ? rows[at].replaceAll(' ', ' ').trim() : `› ${draft}`;
|
|
134
135
|
if (line !== `› ${command}`) return { why: `its input line reads "${line}"` };
|
|
135
136
|
const above = rows.slice(0, at).findLast((row) => row.trim() !== '');
|
|
136
137
|
if (above !== undefined && /^ *(?:› +)?\/\S/.test(above)) return { why: `a menu of commands stands above its input line: "${above.trim()}"` };
|
package/src/orca.js
CHANGED
|
@@ -1017,6 +1017,22 @@ export const useMailbox = (id, handle, options) =>
|
|
|
1017
1017
|
export const coordinatorOf = (id, options) =>
|
|
1018
1018
|
orca(['orchestration', 'run-show', '--id', id], options).run.coordinator_handle ?? undefined;
|
|
1019
1019
|
|
|
1020
|
+
/**
|
|
1021
|
+
* Whether this Orca has no Run of that id at all, as when the book was written
|
|
1022
|
+
* on another machine (#508). Only `run-show` refused `run_not_found` says so:
|
|
1023
|
+
* `run-use` gives a legacy Run the same code and words, and a Run shown, a
|
|
1024
|
+
* time-out or any other refusal is not a Run known to be missing (tech notes,
|
|
1025
|
+
* section 1).
|
|
1026
|
+
*/
|
|
1027
|
+
export function runMissing(id, options) {
|
|
1028
|
+
try {
|
|
1029
|
+
orca(['orchestration', 'run-show', '--id', id], options);
|
|
1030
|
+
return false;
|
|
1031
|
+
} catch (error) {
|
|
1032
|
+
return error.code === 'run_not_found';
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1020
1036
|
/** Queue one message. `to` and `from` are mailboxes, written `run:<id>`. */
|
|
1021
1037
|
export function postMessage({ to, from, subject, body, type = 'status', thread }) {
|
|
1022
1038
|
const args = ['orchestration', 'send', '--to', to, '--subject', subject, '--body', body, '--type', type];
|
package/src/up.js
CHANGED
|
@@ -14,7 +14,7 @@ import { conversationsIn, hasConversation, heldAsUserTurn, transcriptsIn } from
|
|
|
14
14
|
import { installHook } from './hooks.js';
|
|
15
15
|
import { writePermissions } from './permissions.js';
|
|
16
16
|
import { addressOf, harnessOf, isAddressOf, isShortPrompt, launchCommand, mailboxStep, reachesMail, sessionTrouble, startPrompt, workDirOf } from './launch.js';
|
|
17
|
-
import { asFolderProject, coordinatorOf, findProject, harnessInTab, makeMailbox, makeProject, openTab, QUESTION_ON_SCREEN, retitleTab, screenRows, shellInTab, tabs, tabToTypeInto, TERMINAL_ENV, TIMED_OUT, tellWindow, typeIntoTab, useMailbox } from './orca.js';
|
|
17
|
+
import { asFolderProject, coordinatorOf, findProject, harnessInTab, makeMailbox, makeProject, openTab, QUESTION_ON_SCREEN, retitleTab, runMissing, screenRows, shellInTab, tabs, tabToTypeInto, TERMINAL_ENV, TIMED_OUT, tellWindow, typeIntoTab, useMailbox } from './orca.js';
|
|
18
18
|
import { TAB_ENV } from './record.js';
|
|
19
19
|
import { buildAgents, rulesStamp } from './rules.js';
|
|
20
20
|
import { linkSkills } from './skills.js';
|
|
@@ -648,7 +648,8 @@ async function writeAddress(home, session, address) {
|
|
|
648
648
|
* a restart (tech notes, section 1). Orca offers no way to delete one, so this
|
|
649
649
|
* makes one for a session whose book names none, and otherwise binds the one
|
|
650
650
|
* the book names to this tab: after a restart, or a closed tab brought back,
|
|
651
|
-
* it would stay with the tab that is gone (review of PR #248, finding 1).
|
|
651
|
+
* it would stay with the tab that is gone (review of PR #248, finding 1). A
|
|
652
|
+
* Run the book names and this Orca does not have at all is replaced (#508).
|
|
652
653
|
*
|
|
653
654
|
* Only inside an Orca tab. Asked from anywhere else, Orca picks a terminal
|
|
654
655
|
* itself, and seen live that was not the asker's (tech notes, section 1), so
|
|
@@ -705,20 +706,34 @@ async function mailboxInTurn(home, botName, sessionName, who) {
|
|
|
705
706
|
if (error.code === TIMED_OUT) {
|
|
706
707
|
throw new Error(`${error.message}\n${who}'s mailbox ${held} may now be bound to this tab, or may still be bound where it was: Orca did not say which.`);
|
|
707
708
|
}
|
|
708
|
-
|
|
709
|
+
// A Run this Orca has never had, as when the book was written on another
|
|
710
|
+
// machine, can never be bound, so every start would fail the same way.
|
|
711
|
+
// It is replaced as a session with none would be given one (#508).
|
|
712
|
+
if (!runMissing(held, STEP_WAIT)) {
|
|
713
|
+
throw new Error(`${error.message}\n${who}'s mailbox ${held} is unchanged: it is still bound to ${boundTo(held)}.`);
|
|
714
|
+
}
|
|
715
|
+
return newMailbox(home, botName, sessionName, who, here, held);
|
|
709
716
|
}
|
|
710
717
|
return { bot: botName, session: sessionName, mailbox: held, change: 'bound' };
|
|
711
718
|
}
|
|
719
|
+
return newMailbox(home, botName, sessionName, who, here);
|
|
720
|
+
}
|
|
712
721
|
|
|
722
|
+
/**
|
|
723
|
+
* Make a mailbox in this tab and write it into the book, for a session whose
|
|
724
|
+
* book names none, or names `stale`, a Run this Orca does not have.
|
|
725
|
+
*/
|
|
726
|
+
async function newMailbox(home, botName, sessionName, who, here, stale) {
|
|
713
727
|
// Made outside the book's lock, which is held for one read and one write
|
|
714
728
|
// (book.js).
|
|
715
729
|
let made;
|
|
716
730
|
try {
|
|
717
731
|
made = makeMailbox(who, STEP_WAIT);
|
|
718
732
|
} catch (error) {
|
|
733
|
+
const written = stale === undefined ? 'none is written down' : `the book still names ${stale}, which this Orca does not have`;
|
|
719
734
|
const left = error.code === TIMED_OUT
|
|
720
|
-
? `
|
|
721
|
-
: `No mailbox was made for ${who}, and
|
|
735
|
+
? `For ${who}, ${written}; a Run Orca made after the kit stopped waiting is left unused.`
|
|
736
|
+
: `No mailbox was made for ${who}, and ${written}.`;
|
|
722
737
|
throw new Error(`${error.message}\n${left} It gets one the next time the kit starts it.`);
|
|
723
738
|
}
|
|
724
739
|
|
|
@@ -734,10 +749,11 @@ async function mailboxInTurn(home, botName, sessionName, who) {
|
|
|
734
749
|
movedTo = now.tab ?? 'none';
|
|
735
750
|
return undefined;
|
|
736
751
|
}
|
|
737
|
-
// Under the lock, and only if the book still has none
|
|
738
|
-
// would each have made one, and a session
|
|
739
|
-
// half its mail never reaches. The
|
|
740
|
-
|
|
752
|
+
// Under the lock, and only if the book still has none, or still the
|
|
753
|
+
// stale one: two runs at once would each have made one, and a session
|
|
754
|
+
// with two mailboxes is a session half its mail never reaches. The
|
|
755
|
+
// loser's Run is left unused.
|
|
756
|
+
if (typeof now.mailbox !== 'string' || now.mailbox === stale) now.mailbox = made;
|
|
741
757
|
kept = now.mailbox;
|
|
742
758
|
current.sessions[sessionName] = now;
|
|
743
759
|
return undefined;
|
|
@@ -750,8 +766,13 @@ async function mailboxInTurn(home, botName, sessionName, who) {
|
|
|
750
766
|
}
|
|
751
767
|
// One terminal holds one Run, so making the loser took this tab off the one
|
|
752
768
|
// the book kept. It goes back.
|
|
753
|
-
if (kept !== made)
|
|
754
|
-
|
|
769
|
+
if (kept !== made) {
|
|
770
|
+
useMailbox(kept, undefined, STEP_WAIT);
|
|
771
|
+
return { bot: botName, session: sessionName, mailbox: kept, change: 'bound' };
|
|
772
|
+
}
|
|
773
|
+
return stale === undefined
|
|
774
|
+
? { bot: botName, session: sessionName, mailbox: made, change: 'made' }
|
|
775
|
+
: { bot: botName, session: sessionName, mailbox: made, change: 'replaced', replaced: stale };
|
|
755
776
|
}
|
|
756
777
|
|
|
757
778
|
/**
|