@assuredloop/orca-bot-kit 0.15.0 → 0.16.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 CHANGED
@@ -194,10 +194,10 @@ waiting for:
194
194
  | On screen | Send | Which is | Proven on |
195
195
  |---|---|---|---|
196
196
  | Claude Code's folder trust list | `\x1b[B\r` | down, then return: its selection starts on **No, exit** | Claude Code 2.1.283 |
197
- | Codex's directory trust, `1. Trust and continue` (older: `1. Yes, continue`) | `\r` | return: its selection starts on **Trust and continue** | Codex 0.157.1 |
198
- | Codex's `Hooks need review` | `\x1b[B\r` | down, then return, to **Trust all and continue**; if the selection already starts there, return alone | Codex 0.157.1 |
199
- | Codex's `/new`: `Where should the new conversation run?` | `\r` | return: its selection starts on **Current checkout** (bot home) | Codex 0.157.1 |
200
- | Codex's update offer, `1. Update now` | `\r` | return: its selection starts on **Update now**, which accepts it | Codex 0.156.1; 0.157.1 has had no newer release to offer |
197
+ | Codex's directory trust, `1. Trust and continue` (older: `1. Yes, continue`) | `\r` | return: its selection starts on **Trust and continue** | Codex 0.158.0 |
198
+ | Codex's `Hooks need review` | `\x1b[B\r` | down, then return, to **Trust all and continue**; if the selection already starts there, return alone | Codex 0.158.0 |
199
+ | Codex's `/new`: `Where should the new conversation run?` | `\r` | return: its selection starts on **Current checkout** (bot home) | Codex 0.158.0 |
200
+ | 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 has had no newer release to offer |
201
201
  | `[oh-my-zsh] Would you like to update?` | `n` | they update their own shell | oh-my-zsh bf77e35 |
202
202
  | Claude Code's `Teach auto mode about your environment?` | `\x1b` | Esc, which cancels it. 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 on 2.1.283 (#416); 2.1.278 showed a numbered list, answered with 2. Not now |
203
203
 
@@ -217,7 +217,7 @@ empty book and nothing says why.
217
217
  Codex's trust question applies to the **repository root**, which for a bot means
218
218
  the whole bots folder rather than the one bot. That is what they are agreeing to.
219
219
 
220
- Codex's `/new` question (seen on 0.156.1 and 0.157.1) is always answered with the current checkout, which is the bot
220
+ Codex's `/new` question (seen on 0.156.1, 0.157.1 and 0.158.0) is always answered with the current checkout, which is the bot
221
221
  home. The kit never makes a git worktree, so
222
222
  `2. New worktree` is never the answer.
223
223
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assuredloop/orca-bot-kit",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -284,9 +284,9 @@ otherwise `/Applications/Orca.app/Contents/Resources/bin/orca`. A bare
284
284
  | On screen | Send | Which is | Proven on |
285
285
  |---|---|---|---|
286
286
  | Claude Code's trust list | `\x1b[B\r` | down, return: off **No, exit** | Claude Code 2.1.283 |
287
- | Codex's `Trust this folder?` | `\r` | return: it starts on yes | Codex 0.157.1 |
288
- | Codex's `Hooks need review` | `\x1b[B\r` | **Trust all and continue** | Codex 0.157.1 |
289
- | Codex's `Where should the new conversation run?` | `\r` | **Current checkout** | Codex 0.157.1 |
287
+ | Codex's `Trust this folder?` | `\r` | return: it starts on yes | Codex 0.158.0 |
288
+ | Codex's `Hooks need review` | `\x1b[B\r` | **Trust all and continue** | Codex 0.158.0 |
289
+ | Codex's `Where should the new conversation run?` | `\r` | **Current checkout** | Codex 0.158.0 |
290
290
  | Codex's update offer, `1. Update now` | `\r` | **Update now**, where it starts | Codex 0.156.1 |
291
291
  | `[oh-my-zsh] Would you like to update?` | `n` | no: the user updates it | oh-my-zsh bf77e35 |
292
292
  | Claude Code's `Teach auto mode…` | `\x1b` | Esc cancels; no return | Not yet proven, below |
@@ -297,9 +297,10 @@ moved, go to the answer with the arrows, then return. Never a digit and then
297
297
  return: on Codex's update offer that took the highlighted **Update now**,
298
298
  whatever the digit was. Codex's trust said `1. Yes, continue` on older
299
299
  versions. Its hooks review sometimes starts on `2` already: then return alone.
300
- 0.157.1 has had no newer release to offer, so its update offer was proven on
301
- 0.156.1. Claude Code's `Teach auto mode` on 2.1.283 is a form, not a list,
302
- ending `Enter to continue · Esc to cancel`: never send it a return, since
300
+ Its update offer was proven on 0.156.1; 0.157.1 showed it with the selection on
301
+ Update now, not pressed, and 0.158.0 has had no newer release to offer.
302
+ Claude Code's `Teach auto mode` on 2.1.283 is a form, not a list, ending
303
+ `Enter to continue · Esc to cancel`: never send it a return, since
303
304
  Enter is Continue, which scans the project, recent sessions and the machine's
304
305
  shell history. Esc cancels in its code; that is not yet proven live. On 2.1.278
305
306
  it was a numbered list, answered with `2. Not now`. Codex's hooks
@@ -307,7 +308,7 @@ question matters most. The kit's hook is how the book learns which
307
308
  conversation the session is running, and until it is answered the
308
309
  conversation has not started. Codex's trust applies to the repository root,
309
310
  which is the whole bots folder. Codex's `/new` may ask where the new
310
- conversation runs (0.156.1 and 0.157.1 both did); the answer is
311
+ conversation runs (0.156.1, 0.157.1 and 0.158.0 all did); the answer is
311
312
  always the current checkout, the bot home, and never `2. New worktree`:
312
313
  the kit never makes a git worktree.
313
314
 
package/src/cli.js CHANGED
@@ -1574,11 +1574,21 @@ function harnessLines(tab, bots) {
1574
1574
  ...ANSWER_IT,
1575
1575
  ];
1576
1576
 
1577
- lines.push(...promptLines(tab));
1577
+ lines.push(...promptLines(tab), ...listLines(tab));
1578
1578
  if (tab.unclaimed !== undefined) lines.push(...unclaimedLines(tab, bots));
1579
1579
  return lines;
1580
1580
  }
1581
1581
 
1582
+ /**
1583
+ * Whether a resumed Codex session was typed its one line so Orca lists it among
1584
+ * its agents, which it does only from a session's first turn (#226).
1585
+ */
1586
+ function listLines(tab) {
1587
+ if (tab.listLine === true) return [' it was typed one line so Orca lists it among its agents: Codex tells Orca only at a first turn.'];
1588
+ if (tab.listLine === false) return [` it was not typed the line that makes Orca list it (${tab.listLineTrouble}): Orca lists it at its next turn.`];
1589
+ return [];
1590
+ }
1591
+
1582
1592
  /**
1583
1593
  * Whether the session got its start prompt, as its own record has it (#274):
1584
1594
  * received, or not confirmed yet. Nothing for a session that was told nothing.
package/src/orca.js CHANGED
@@ -326,9 +326,11 @@ export const retitleTab = (handle, title) =>
326
326
  * the screen, as `questionIn` answers, and `screenUnreadable` says why there
327
327
  * was none.
328
328
  */
329
- export function harnessInTab(handle, timeoutMs) {
329
+ export function harnessInTab(handle, timeoutMs, readMs) {
330
330
  const args = ['terminal', 'wait', '--terminal', handle, '--for', 'tui-idle', '--timeout-ms', String(timeoutMs)];
331
- const answer = ask(args);
331
+ // With `readMs`, each call to Orca is given that long on top of what it was
332
+ // asked to wait, and ends as TIMED_OUT when it has not answered (#422).
333
+ const answer = ask(args, readMs === undefined ? undefined : timeoutMs + readMs);
332
334
 
333
335
  // Out of time means nothing went idle, busy or absent alike. That is an
334
336
  // answer, not a breakdown.
@@ -336,20 +338,26 @@ export function harnessInTab(handle, timeoutMs) {
336
338
  throw refusal(args, answer);
337
339
  }
338
340
 
339
- const shown = orca(['terminal', 'show', '--terminal', handle]).terminal;
341
+ const shown = orca(['terminal', 'show', '--terminal', handle], { timeoutMs: readMs }).terminal;
340
342
  const agent = typeof shown?.agentIdentity === 'string' && shown.agentIdentity !== '' ? shown.agentIdentity : undefined;
341
343
  return {
342
344
  answered: answer.ok === true,
343
345
  blockedReason: answer.result?.wait?.blockedReason,
344
346
  agent,
345
- ...frontOf(handle, shown?.ptyId),
346
- ...screenOf(handle),
347
+ ...frontOf(handle, shown?.ptyId, readMs),
348
+ ...screenOf(handle, readMs),
347
349
  };
348
350
  }
349
351
 
350
352
  /** Orca's reason for a folder-trust screen, which it can go on giving once that is answered (#342). */
351
353
  const TRUST_REASON = 'agent-trust-workspace';
352
354
 
355
+ /**
356
+ * How long each call to Orca the typing gate makes is given, beyond any wait it
357
+ * asked for, before the gate gives up on it and cannot tell (#422).
358
+ */
359
+ const GATE_READ_MS = 5000;
360
+
353
361
  /** The kit's word for a tab whose screen shows a question of its harness's own (#329). */
354
362
  export const QUESTION_ON_SCREEN = 'question-on-screen';
355
363
 
@@ -360,10 +368,10 @@ export const QUESTION_ON_SCREEN = 'question-on-screen';
360
368
  * rendered screen: accumulated output comes back in fragments (tech notes,
361
369
  * section 1).
362
370
  */
363
- function screenOf(handle) {
371
+ function screenOf(handle, readMs) {
364
372
  let read;
365
373
  try {
366
- read = orca(['terminal', 'read', '--terminal', handle, '--screen']).terminal;
374
+ read = orca(['terminal', 'read', '--terminal', handle, '--screen'], { timeoutMs: readMs }).terminal;
367
375
  } catch (error) {
368
376
  return { screenUnreadable: `Orca would not read its screen: ${error.message}` };
369
377
  }
@@ -425,10 +433,18 @@ export function questionIn(rows) {
425
433
  */
426
434
  export function tabToTypeInto(home, tabId, timeoutMs) {
427
435
  if (tabId === undefined) return {};
428
- const live = tabs(home).find((tab) => tab.tabId === tabId);
429
- if (live === undefined) return {};
430
-
431
- const seen = harnessInTab(live.handle, timeoutMs);
436
+ // Every call to Orca here has a bound, so one slow reply cannot hold the
437
+ // command that asked: one that runs out is "cannot tell" (#422).
438
+ let live;
439
+ let seen;
440
+ try {
441
+ live = tabs(home, { timeoutMs: GATE_READ_MS }).find((tab) => tab.tabId === tabId);
442
+ if (live === undefined) return {};
443
+ seen = harnessInTab(live.handle, timeoutMs, GATE_READ_MS);
444
+ } catch (error) {
445
+ if (error.code !== TIMED_OUT) throw error;
446
+ return { unsure: `Orca did not answer in time (${error.message}), so the kit could not tell whether a harness is running in it, and nothing was typed` };
447
+ }
432
448
  // A line typed into a screen waiting for an answer is that answer: once, it
433
449
  // confirmed Claude Code's folder-trust default, `No, exit` (tech notes,
434
450
  // section 1).
@@ -516,8 +532,8 @@ const psCli = () => process.env.OBK_PS || '/bin/ps';
516
532
  * sandbox, where it does not start at all, Orca's runtime is asked instead
517
533
  * (#298, ADR 0031).
518
534
  */
519
- function frontOf(handle, ptyId) {
520
- const read = frontByPs(ptyId);
535
+ function frontOf(handle, ptyId, readMs) {
536
+ const read = frontByPs(ptyId, readMs);
521
537
  if (read.unreadable === undefined) return read;
522
538
  const asked = frontByOrca(handle);
523
539
  return asked.unreadable === undefined ? asked : { unreadable: `${read.unreadable}, and ${asked.unreadable}` };
@@ -556,13 +572,17 @@ function frontByOrca(handle) {
556
572
  * `diagnostics memory` is a diagnostics command and may change, so everything
557
573
  * here ends in "cannot tell" rather than in an error or a guess.
558
574
  */
559
- function frontByPs(ptyId) {
575
+ function frontByPs(ptyId, readMs) {
560
576
  let pane;
561
577
  try {
562
- pane = orca(['diagnostics', 'memory']).worktrees
578
+ pane = orca(['diagnostics', 'memory'], { timeoutMs: readMs }).worktrees
563
579
  .flatMap((worktree) => worktree.sessions ?? [])
564
580
  .find((session) => session.sessionId === ptyId)?.pid;
565
581
  } catch (error) {
582
+ // Orca not answering within the bound the typing gate set is not a tab
583
+ // `ps` cannot read: it ends the gate as "cannot tell", with no fallback to
584
+ // the runtime (review of PR #424).
585
+ if (error.code === TIMED_OUT) throw error;
566
586
  return { unreadable: `Orca would not give the pane's pid: ${error.message}` };
567
587
  }
568
588
  if (!Number.isInteger(pane) || pane <= 0) return { unreadable: 'Orca gave no pid for its pane' };
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, tabs, TERMINAL_ENV, TIMED_OUT, tellWindow, typeIntoTab, useMailbox } from './orca.js';
17
+ import { asFolderProject, coordinatorOf, findProject, harnessInTab, makeMailbox, makeProject, openTab, QUESTION_ON_SCREEN, retitleTab, 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';
@@ -369,6 +369,14 @@ async function bringUpSession(bots, home, live, session, bot, title) {
369
369
  ? undefined
370
370
  : await heldInRecord(home, session.name, harness, launch.prompt, { launched, running: tui.running === true });
371
371
 
372
+ // A Codex session resumed here is not among Orca's agents until its first
373
+ // turn: Codex fires its SessionStart then and not at the resume, seen live on
374
+ // 0.157.1 (#226). So it is given one line, through the same gate as a mail
375
+ // notice, which the owner allowed. A fresh start's first turn is its prompt.
376
+ const list = harness === 'codex' && launch.resume !== undefined && tui.running === true
377
+ ? await typeListLine(home, made.tabId)
378
+ : undefined;
379
+
372
380
  return entry(made, {
373
381
  bot: bot.name,
374
382
  name: session.name,
@@ -382,9 +390,60 @@ async function bringUpSession(bots, home, live, session, bot, title) {
382
390
  resumed: launch.resume !== undefined,
383
391
  noConversation: which.noConversation,
384
392
  unclaimed: which.unclaimed,
393
+ listLine: list?.typed,
394
+ listLineTrouble: list?.why,
385
395
  });
386
396
  }
387
397
 
398
+ /** The line a resumed Codex session is typed so Orca lists it, in the architect's words (#226). */
399
+ const LIST_LINE = 'obk: this session was resumed in a new tab, and this line is only so Orca lists it. Reply "ok"; nothing else is asked.';
400
+
401
+ /**
402
+ * How long a resumed Codex tab is given to be one the kit may type into: live,
403
+ * Orca named the agent 7 to 11 s after the restart (#226), and the gate waits
404
+ * for that name. How long each look waits on the tab, and the pause between.
405
+ */
406
+ const LIST_WAIT_MS = 20000;
407
+ const LIST_LOOK_MS = 2000;
408
+ const LIST_ASK_MS = 500;
409
+
410
+ /**
411
+ * Type LIST_LINE into the tab `tabId` once the gate every typed line goes
412
+ * through lets it: `{ typed: true }`, or `{ typed: false, why }` with the
413
+ * gate's last reason once LIST_WAIT_MS is up. Nothing goes into a question, a
414
+ * form or a menu (#329, #416), nor into a tab the kit cannot tell about.
415
+ */
416
+ async function typeListLine(home, tabId) {
417
+ const until = Date.now() + LIST_WAIT_MS;
418
+ for (;;) {
419
+ let found;
420
+ try {
421
+ // Each look waits no longer than what is left of the wait.
422
+ found = tabToTypeInto(home, tabId, Math.max(1, Math.min(LIST_LOOK_MS, until - Date.now())));
423
+ } catch (error) {
424
+ return { typed: false, why: `Orca would not say whether it may be typed into: ${error.message}` };
425
+ }
426
+ // A yes that comes after the wait is too late: the wait is a cutoff, not a
427
+ // count of looks (review of PR #421).
428
+ if (found.handle !== undefined && Date.now() > until) {
429
+ return { typed: false, why: `the ${LIST_WAIT_MS / 1000} s wait for it ran out before the kit could tell it may be typed into` };
430
+ }
431
+ if (found.handle !== undefined) {
432
+ try {
433
+ typeIntoTab(found.handle, LIST_LINE);
434
+ return { typed: true };
435
+ } catch (error) {
436
+ return { typed: false, why: `Orca refused the line: ${error.message}` };
437
+ }
438
+ }
439
+ if (Date.now() >= until) {
440
+ const last = found.blocked !== undefined ? `it is waiting on ${found.blocked}` : found.unsure ?? 'no harness is running in it';
441
+ return { typed: false, why: `the ${LIST_WAIT_MS / 1000} s wait for it ran out: ${last}` };
442
+ }
443
+ await pause(LIST_ASK_MS);
444
+ }
445
+ }
446
+
388
447
  /**
389
448
  * The line that starts this session: resuming `resume`, or, with none, a fresh
390
449
  * start carrying its duty. Returns `{ resume, prompt, promptFile, command, address }`.
@@ -714,11 +773,15 @@ const ids = (setup, change) => ({ project: setup.projectId, setup: setup.id, cha
714
773
  export const promptPath = (bots, bot, session) =>
715
774
  path.join(`${bots}.prompts`, `${encodeURIComponent(bot)}.${encodeURIComponent(session)}.txt`);
716
775
 
717
- function entry(tab, { bot, name, created, running = false, blockedReason, promptReceived, promptFile, resumed, noConversation, unclaimed, noMailbox = false }) {
776
+ function entry(tab, { bot, name, created, running = false, blockedReason, promptReceived, promptFile, resumed, noConversation, unclaimed, noMailbox = false, listLine, listLineTrouble }) {
718
777
  const made = { bot, name, title: tab.title, tabId: tab.tabId, terminal: tab.handle, created, harnessStarted: running };
719
778
  // Whether this run picked the session up where it was or started a new one.
720
779
  // Only for a tab this run opened: a tab that was already there was left alone.
721
780
  if (resumed !== undefined) made.resumed = resumed;
781
+ // Whether a resumed Codex session was typed its one line so Orca lists it,
782
+ // and why not when it was not (#226).
783
+ if (listLine !== undefined) made.listLine = listLine;
784
+ if (listLineTrouble !== undefined) made.listLineTrouble = listLineTrouble;
722
785
  // The id the book held that had no conversation behind it, now in the history.
723
786
  if (noConversation !== undefined) made.noConversation = noConversation;
724
787
  // Orca's own words for what is on screen waiting to be answered, when it