@assuredloop/orca-bot-kit 0.25.1 → 0.25.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assuredloop/orca-bot-kit",
3
- "version": "0.25.1",
3
+ "version": "0.25.2",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
package/src/clear.js CHANGED
@@ -19,7 +19,7 @@ import { botDir, readBot } from './bot.js';
19
19
  import { claudeTranscript, codexRollout } from './conversations.js';
20
20
  import { harnessOf, ownCli, shellWord } from './launch.js';
21
21
  import { eachLine, firstLine } from './lines.js';
22
- import { findProject, orca, QUESTION_ON_SCREEN, questionIn, screenRows, tabs, tabToTypeInto, TIMED_OUT } from './orca.js';
22
+ import { FEEDBACK_PANEL, feedbackPanelIn, findProject, orca, QUESTION_ON_SCREEN, questionIn, screenRows, tabs, tabToTypeInto, TIMED_OUT } from './orca.js';
23
23
  import { botsNamed, sessionsOf, typeListLine } from './up.js';
24
24
 
25
25
  /** The harness's own command for each, as typed into its input line. Codex's clear is `/new` (tech notes, section 3). */
@@ -201,7 +201,7 @@ async function enter(it, verb, before = () => {}) {
201
201
  if (typing === undefined) throw new Error(`${it.name}: ${TYPING_HELD}. Run this again in a moment.`);
202
202
  before();
203
203
  },
204
- cannot: (wrong) => (verb === 'compact' && wrong.menu ? ` ${harnessName(it.harness)} here cannot compact: its menu does not offer ${COMMANDS[verb][it.harness]}.` : ''),
204
+ cannot: (wrong) => (verb === 'compact' && wrong.menu && it.harness === 'codex' ? ` ${harnessName(it.harness)} here cannot compact: its menu does not offer ${COMMANDS[verb][it.harness]}.` : ''),
205
205
  });
206
206
  return { ...typed, typing };
207
207
  } catch (error) {
@@ -350,8 +350,8 @@ function lookAt(it, { idle = true } = {}) {
350
350
  const found = tabToTypeInto(it.home, it.tabId, idle ? LOOK_MS : CHAR_LOOK_MS);
351
351
  if (found.blocked !== undefined) {
352
352
  return {
353
- why: found.blocked === QUESTION_ON_SCREEN
354
- ? 'a question of its harness\'s own is on its screen'
353
+ why: found.blocked === FEEDBACK_PANEL ? PANEL_WHY
354
+ : found.blocked === QUESTION_ON_SCREEN ? 'a question of its harness\'s own is on its screen'
355
355
  : `a question is waiting on its screen: Orca says ${found.blocked}`,
356
356
  };
357
357
  }
@@ -390,10 +390,18 @@ async function typedWrong(handle, harness, command, version, check) {
390
390
  }
391
391
  }
392
392
 
393
+ /**
394
+ * Why nothing goes in while Claude Code's panel of feedback drafts is open
395
+ * (#502): it reads single keys from the input line, and one sends a draft to
396
+ * Anthropic, so only the user answers it.
397
+ */
398
+ const PANEL_WHY = 'its panel of feedback drafts is open above its input box, and the kit does not answer it, since a draft sent from it goes to Anthropic: it clears once the user reviews, sends or dismisses the drafts in that tab';
399
+
393
400
  /** Why `rows` say not to type now, as the look says it, or undefined: a turn at work, or a question. */
394
401
  function signalIn(rows) {
395
402
  const working = rows.find((row) => AT_WORK.some((marker) => marker.test(row)));
396
403
  if (working !== undefined) return `it is busy with a turn: its screen shows "${working.trim()}"`;
404
+ if (feedbackPanelIn(rows)) return PANEL_WHY;
397
405
  if (questionIn(rows)) return 'a question of its harness\'s own is on its screen';
398
406
  return undefined;
399
407
  }
package/src/orca.js CHANGED
@@ -427,6 +427,7 @@ const ON_A_CHOICE = /^( *[›❯] +)\d+\. /;
427
427
  * line, lined up with it, and by its layout that is one.
428
428
  */
429
429
  export function questionIn(rows) {
430
+ if (feedbackPanelIn(rows)) return true;
430
431
  const at = rows.findLastIndex((row) => POINTER_ROW.test(row));
431
432
  // A form of the harness's own has no numbers but a foot that says how to
432
433
  // answer it: Claude Code 2.1.283's "Teach auto mode" ends `Enter to continue
@@ -455,6 +456,39 @@ export function questionIn(rows) {
455
456
  return false;
456
457
  }
457
458
 
459
+ /** The gate's answer for Claude Code's panel of feedback drafts (#502). */
460
+ export const FEEDBACK_PANEL = 'feedback-drafts-panel';
461
+
462
+ /** The first row in the panel's box, as Claude Code 2.1.291 draws it: `✻ Bug report drafted: <title>`. */
463
+ const PANEL_TITLE = /^ *│ +(?:\S+ +)?(?:Bug report|Product feedback|Feature request|Feedback) drafted: /;
464
+
465
+ /** What Claude Code 2.1.291 asks in the panel's place after a `0`, its keys' foot last: `… 0 to turn off · Esc to keep`. */
466
+ const PANEL_TURN_OFF = /^Turn off Claude-drafted feedback\? 0 to turn off · Esc to keep$/i;
467
+
468
+ /**
469
+ * Whether the rows of a rendered screen show Claude Code's panel of feedback
470
+ * drafts (#502). It has no pointer and no numbers, but it reads single keys
471
+ * from the input line, and `2` twice sends a draft to Anthropic. Claude Code
472
+ * 2.1.291 draws it as a box, its title row first, as the last thing above its
473
+ * input box's top rule; after a `0`, the question whether to turn the drafts
474
+ * off stands in its place, wrapped as the pane needs, and a typed line answers
475
+ * that too. The same words
476
+ * with anything after them are history.
477
+ */
478
+ export function feedbackPanelIn(rows) {
479
+ const at = rows.findLastIndex((row) => POINTER_ROW.test(row));
480
+ if (at < 1 || !RULE_ROW.test(rows[at - 1])) return false;
481
+ const end = rows.slice(0, at - 1).findLastIndex((row) => row.trim() !== '');
482
+ if (end < 0) return false;
483
+ // The question is plain text, which a narrow pane wraps onto rows of its
484
+ // own, with a blank row above it: it is the whole block that ends there.
485
+ const start = rows.slice(0, end).findLastIndex((row) => row.trim() === '') + 1;
486
+ if (PANEL_TURN_OFF.test(rows.slice(start, end + 1).map((row) => row.trim()).join(' '))) return true;
487
+ if (!/^ *╰─/.test(rows[end])) return false;
488
+ const top = rows.slice(0, end).findLastIndex((row) => /^ *╭─/.test(row));
489
+ return top >= 0 && PANEL_TITLE.test(rows[top + 1]);
490
+ }
491
+
458
492
  /** A rule Claude Code draws right across, as the top of its input box: `─` or `▔`. */
459
493
  const RULE_ROW = /^ *[─▔]{8,}/;
460
494
 
@@ -538,7 +572,7 @@ export function tabToTypeInto(home, tabId, timeoutMs) {
538
572
  // Orca's reason does not cover every question: it called Codex's update
539
573
  // offer idle, and a return typed into it took "Update now" (#329). So the
540
574
  // screen is read too, and one that cannot be read is not a reason to type.
541
- if (seen.question === true) return { blocked: QUESTION_ON_SCREEN };
575
+ if (seen.question === true) return { blocked: feedbackPanelIn(seen.rows) ? FEEDBACK_PANEL : QUESTION_ON_SCREEN };
542
576
  if (seen.question === undefined) {
543
577
  return { unsure: `the kit could not tell whether a question is waiting on its screen (${seen.screenUnreadable}), so nothing was typed` };
544
578
  }