@assuredloop/orca-bot-kit 0.33.0 → 0.34.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/SETUP.md +3 -1
- package/package.json +1 -1
- package/src/cli.js +7 -2
- package/src/message.js +39 -8
- package/src/orca.js +31 -0
- package/src/up.js +15 -3
package/SETUP.md
CHANGED
|
@@ -126,7 +126,9 @@ default and the kit will not guess. It is Bot Father's own harness only; each
|
|
|
126
126
|
bot they make later chooses its own. If only one harness is installed, say so
|
|
127
127
|
and confirm it rather than asking as though the choice were open. If they choose
|
|
128
128
|
`pi`, they sign in to Pi themselves first: the kit never handles Pi's
|
|
129
|
-
credentials, and `obk init` starts nothing while Pi can use no model.
|
|
129
|
+
credentials, and `obk init` starts nothing while Pi can use no model. When you
|
|
130
|
+
ask or confirm, tell them: "On Pi, Bot Father acts without asking first, because
|
|
131
|
+
Pi has no approval step."
|
|
130
132
|
|
|
131
133
|
## 4. Make the fleet
|
|
132
134
|
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -12,7 +12,7 @@ import path from 'node:path';
|
|
|
12
12
|
import { fileURLToPath } from 'node:url';
|
|
13
13
|
import { parseArgs } from 'node:util';
|
|
14
14
|
|
|
15
|
-
import { addSession, allowedNow, allowRules, botDir, changeBot, changeSession, createBot, disallowRules, leadsOutside, readBot, refuseCodexOutside, SESSION_FIELDS, setRoleCaps, setTempApproval } from './bot.js';
|
|
15
|
+
import { addSession, allowedNow, allowRules, botDir, changeBot, changeSession, createBot, disallowRules, displayName, leadsOutside, readBot, refuseCodexOutside, SESSION_FIELDS, setRoleCaps, setTempApproval } from './bot.js';
|
|
16
16
|
import { COMMANDS } from './commands.js';
|
|
17
17
|
import { addCommand, groomCommand, grooming, upCommand } from './groom.js';
|
|
18
18
|
import { checkHealth, orcaSettingFindings } from './health.js';
|
|
@@ -1932,12 +1932,17 @@ function tabLines({ bots, created, completed, rules, skills, permissions = [], t
|
|
|
1932
1932
|
const back = one.session === undefined ? '' : ` --session ${one.session}`;
|
|
1933
1933
|
lines.push(`${'paused'.padEnd(9)} ${what} left closed. Bring it back: ${shellWord(ownCli())} unpause --bots ${shellWord(bots)} --bot ${one.bot}${back}`);
|
|
1934
1934
|
}
|
|
1935
|
+
// A window that has not loaded a bot's project gives its tabs no notice of
|
|
1936
|
+
// mail (#676).
|
|
1937
|
+
for (const one of projects.filter((project) => project.windowTrouble !== undefined)) {
|
|
1938
|
+
lines.push(`${'window'.padEnd(9)} ${displayName(one.bot)} its tabs may get no fleet mail until Orca's window loads its project: ${one.windowTrouble}. Try again: ${shellWord(ownCli())} up --bots ${shellWord(bots)} --bot ${one.bot}`);
|
|
1939
|
+
}
|
|
1935
1940
|
|
|
1936
1941
|
// Last before the summary, because what a check found is about the setup the
|
|
1937
1942
|
// run has just left behind rather than about any one thing it did.
|
|
1938
1943
|
lines.push(...foundLines(found));
|
|
1939
1944
|
// Hedged, because nothing here can see whether the window was told (#224).
|
|
1940
|
-
if (projects.
|
|
1945
|
+
if (projects.some((project) => project.change !== undefined)) lines.push(RELOAD_LINE);
|
|
1941
1946
|
lines.push(summary);
|
|
1942
1947
|
return lines;
|
|
1943
1948
|
}
|
package/src/message.js
CHANGED
|
@@ -18,11 +18,12 @@
|
|
|
18
18
|
|
|
19
19
|
import { mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
20
20
|
import path from 'node:path';
|
|
21
|
+
import { setTimeout as pause } from 'node:timers/promises';
|
|
21
22
|
|
|
22
23
|
import { MAILBOX_WAIT_MS, readBook, takeLineTurn, takeMailboxTurn, TYPING_HELD, TYPING_WAIT_MS } from './book.js';
|
|
23
24
|
import { botDir, botNames, readBot } from './bot.js';
|
|
24
25
|
import { harnessOf, isAddressOf, kitFolders, ownCli, reachesMail, SHELL_ENV, shellWord } from './launch.js';
|
|
25
|
-
import { ackMailbox, coordinatorOf, postMessage, pressEscape, readMailbox, tabs, tabToTypeInto, TERMINAL_ENV, TIMED_OUT, useMailbox } from './orca.js';
|
|
26
|
+
import { ackMailbox, coordinatorOf, listedOrphaned, loadInWindow, postMessage, pressEscape, readMailbox, tabs, tabToTypeInto, TERMINAL_ENV, TIMED_OUT, useMailbox } from './orca.js';
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* How much of a message travels as itself. Above this it is written to a file
|
|
@@ -161,7 +162,7 @@ export async function sendMessage(bots, { to: target, from: sender, tab, subject
|
|
|
161
162
|
|
|
162
163
|
const written = bodyOf(bots, { from, to, subject, text, textFile });
|
|
163
164
|
// Looked at before the post, which is the send's last call to Orca (PRD 6.9).
|
|
164
|
-
const shown = loaded(to);
|
|
165
|
+
const shown = await loaded(to);
|
|
165
166
|
// The Escape goes first, so the turn has ended when the letter is in.
|
|
166
167
|
const interrupted = interrupt ? interruptBusy(to) : {};
|
|
167
168
|
const message = postMessage({
|
|
@@ -189,20 +190,50 @@ export async function sendMessage(bots, { to: target, from: sender, tab, subject
|
|
|
189
190
|
* and lists one it has not as orphaned, as after a window restart (#579; tech
|
|
190
191
|
* notes, section 1). So an orphaned tab is said, with its handle, and a
|
|
191
192
|
* listing that cannot be read is said too, rather than taken for a loaded tab.
|
|
192
|
-
*
|
|
193
|
+
* An orphaned tab of a project the window has never fetched is loaded by
|
|
194
|
+
* `loadInWindow`, which changes nothing on screen, so the kit asks for that
|
|
195
|
+
* and looks again until LOOK_MS after the ask (#676). A tab it does not load,
|
|
196
|
+
* as after a window restart (#579), is said as before. This is all before the
|
|
197
|
+
* send: a tab orphaned after it is not seen here.
|
|
193
198
|
*/
|
|
194
|
-
function loaded(to) {
|
|
199
|
+
async function loaded(to) {
|
|
195
200
|
if (to.tab === undefined) return {};
|
|
196
|
-
let
|
|
201
|
+
let tab;
|
|
197
202
|
try {
|
|
198
|
-
|
|
203
|
+
tab = tabs(to.home, { timeoutMs: LOOK_MS }).find((one) => one.tabId === to.tab);
|
|
199
204
|
} catch (error) {
|
|
200
205
|
return { loadTrouble: error.message };
|
|
201
206
|
}
|
|
202
|
-
|
|
203
|
-
return
|
|
207
|
+
if (tab?.orphaned !== true) return {};
|
|
208
|
+
return (await loadedNow(to.home, tab.handle)) ? {} : { orphaned: true, handle: tab.handle };
|
|
204
209
|
}
|
|
205
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Whether the window loads the orphaned tab `handle` at `home` once asked to.
|
|
213
|
+
* The ask and every look after it keep to one deadline, LOOK_MS from the ask,
|
|
214
|
+
* and each Orca call gets only the time that is left, so a slow Orca cannot
|
|
215
|
+
* hold the send up for longer (#676). A look that fails or runs out counts as
|
|
216
|
+
* a tab still orphaned.
|
|
217
|
+
*/
|
|
218
|
+
async function loadedNow(home, handle) {
|
|
219
|
+
const until = Date.now() + LOOK_MS;
|
|
220
|
+
const left = () => until - Date.now();
|
|
221
|
+
loadInWindow(home, { timeoutMs: LOOK_MS });
|
|
222
|
+
try {
|
|
223
|
+
for (let ms = left(); ms > 0; ms = left()) {
|
|
224
|
+
if (!listedOrphaned(home, handle, { timeoutMs: ms })) return true;
|
|
225
|
+
const wait = Math.min(RELOOK_MS, left());
|
|
226
|
+
if (wait > 0) await pause(wait);
|
|
227
|
+
}
|
|
228
|
+
} catch {
|
|
229
|
+
// A look that fails or runs out leaves the tab said as orphaned.
|
|
230
|
+
}
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** How long the kit waits between two looks at a tab it asked the window to load: it was loaded within 0.4 s, live (#676). */
|
|
235
|
+
const RELOOK_MS = 200;
|
|
236
|
+
|
|
206
237
|
/**
|
|
207
238
|
* What is waiting for a session, and the reading of it.
|
|
208
239
|
*
|
package/src/orca.js
CHANGED
|
@@ -347,6 +347,29 @@ const CLIENT_KILL_MS = 3000;
|
|
|
347
347
|
*/
|
|
348
348
|
export const tellWindow = (projectId) => askRuntime('project.update', { projectId, updates: {} }) !== undefined;
|
|
349
349
|
|
|
350
|
+
/**
|
|
351
|
+
* Have Orca's window load the tabs of the Orca project at `home`: undefined
|
|
352
|
+
* when Orca took the call, or what went wrong.
|
|
353
|
+
*
|
|
354
|
+
* The window loads a project's tabs only once it has fetched the project's
|
|
355
|
+
* worktrees, and nothing fetches them for a project the kit made, so its tabs
|
|
356
|
+
* stay orphaned and get no notice of mail (#676, tech notes). `worktree set`
|
|
357
|
+
* with no field writes an empty change and has Orca send the window the event
|
|
358
|
+
* that fetches them: nothing the user sees changes. What went wrong is said
|
|
359
|
+
* without Orca's own words, which name worktrees (PRD KIT-22: the kit says
|
|
360
|
+
* "Orca project"). `options` are `orca`'s.
|
|
361
|
+
*/
|
|
362
|
+
export function loadInWindow(home, options) {
|
|
363
|
+
try {
|
|
364
|
+
orca(['worktree', 'set', '--worktree', `path:${home}`], options);
|
|
365
|
+
return undefined;
|
|
366
|
+
} catch (error) {
|
|
367
|
+
return typeof error.code === 'string' && !/worktree/i.test(error.code)
|
|
368
|
+
? `Orca refused the call (${error.code})`
|
|
369
|
+
: 'Orca gave no answer the kit could read';
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
350
373
|
/**
|
|
351
374
|
* One call to Orca's runtime for a method its CLI does not offer, and the
|
|
352
375
|
* runtime's answer, or undefined when there is none to be had.
|
|
@@ -433,6 +456,14 @@ export const tabs = (home, options) => orca(['terminal', 'list', '--worktree', `
|
|
|
433
456
|
? { ...tab, tabId: orca(['terminal', 'show', '--terminal', tab.handle], options).terminal.tabId }
|
|
434
457
|
: tab));
|
|
435
458
|
|
|
459
|
+
/**
|
|
460
|
+
* Whether Orca lists the terminal `handle` at `home` as orphaned, from one
|
|
461
|
+
* listing and no `terminal show`, so a caller with a deadline can give the one
|
|
462
|
+
* call all the time it has left (#676).
|
|
463
|
+
*/
|
|
464
|
+
export const listedOrphaned = (home, handle, options) => orca(['terminal', 'list', '--worktree', `path:${home}`], options).terminals
|
|
465
|
+
.some((tab) => tab.handle === handle && tab.orphaned === true);
|
|
466
|
+
|
|
436
467
|
/**
|
|
437
468
|
* Open a tab in the Orca project at `home`, titled `title`.
|
|
438
469
|
*
|
package/src/up.js
CHANGED
|
@@ -16,7 +16,7 @@ import { dropRemovedHooks, installHook } from './hooks.js';
|
|
|
16
16
|
import { writePermissions } from './permissions.js';
|
|
17
17
|
import { piTrouble } from './pi.js';
|
|
18
18
|
import { addressOf, HARNESSES, harnessOf, isAddressOf, isShortPrompt, kitFolders, launchCommand, mailboxStep, reachesMail, sessionTrouble, startPrompt, workDirOf } from './launch.js';
|
|
19
|
-
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';
|
|
19
|
+
import { asFolderProject, coordinatorOf, findProject, harnessInTab, loadInWindow, makeMailbox, makeProject, openTab, QUESTION_ON_SCREEN, retitleTab, runMissing, screenRows, shellInTab, tabs, tabToTypeInto, TERMINAL_ENV, TIMED_OUT, tellWindow, typeIntoTab, useMailbox } from './orca.js';
|
|
20
20
|
import { TAB_ENV } from './record.js';
|
|
21
21
|
import { buildAgents, rulesStamp } from './rules.js';
|
|
22
22
|
import { linkSkills } from './skills.js';
|
|
@@ -40,7 +40,9 @@ const SECOND_LOOK_MS = 2000;
|
|
|
40
40
|
* Bring bots up in Orca. Returns `{ tabs, rules }`: one tab entry per tab it
|
|
41
41
|
* looked at — the sessions the book knows, and, for Bot Father, whatever else
|
|
42
42
|
* is open in its project — and one rules entry and one skills entry per bot.
|
|
43
|
-
* `projects` has one entry per Orca project this run made or renamed
|
|
43
|
+
* `projects` has one entry per Orca project this run made or renamed, and per
|
|
44
|
+
* project Orca's window may not have loaded, with what went wrong as
|
|
45
|
+
* `windowTrouble` (#676).
|
|
44
46
|
*
|
|
45
47
|
* With no name it is every bot in the folder, in name order. `bot` brings up
|
|
46
48
|
* one bot and `session` one of its sessions, for a caller that wants one thing
|
|
@@ -238,6 +240,10 @@ async function bringUpBot(bots, home, bot, onlySession) {
|
|
|
238
240
|
// Orca's window does not see a project made or renamed on the command line
|
|
239
241
|
// until it is told (#224).
|
|
240
242
|
if (change !== undefined) tellWindow(orca.project);
|
|
243
|
+
// Its tabs get no notice of mail until the window loads them, and the window
|
|
244
|
+
// does not load a project it has never fetched (#676). Every run asks, so a
|
|
245
|
+
// project made before this is loaded too; the call changes nothing on screen.
|
|
246
|
+
const windowTrouble = loadInWindow(home);
|
|
241
247
|
|
|
242
248
|
const live = new Map(tabs(home).map((tab) => [tab.tabId, tab]));
|
|
243
249
|
const report = [];
|
|
@@ -256,7 +262,13 @@ async function bringUpBot(bots, home, bot, onlySession) {
|
|
|
256
262
|
: spare.map((tab) => entry(tab, { bot: name, name: null, created: false }))));
|
|
257
263
|
}
|
|
258
264
|
|
|
259
|
-
|
|
265
|
+
const project = {
|
|
266
|
+
bot: name,
|
|
267
|
+
project: orca.project,
|
|
268
|
+
...(change === undefined ? {} : { change }),
|
|
269
|
+
...(windowTrouble === undefined ? {} : { windowTrouble }),
|
|
270
|
+
};
|
|
271
|
+
return { tabs: report, project: change === undefined && windowTrouble === undefined ? undefined : project };
|
|
260
272
|
}
|
|
261
273
|
|
|
262
274
|
async function bringUpSession(bots, home, live, session, bot, title) {
|