@ouro.bot/cli 0.1.0-alpha.261 → 0.1.0-alpha.263

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/changelog.json CHANGED
@@ -1,6 +1,18 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.263",
6
+ "changes": [
7
+ "fix(tui): arrow keys char-by-char (Ink 3.2 key.meta bug), Option+Arrow word-jump via CSI sequences."
8
+ ]
9
+ },
10
+ {
11
+ "version": "0.1.0-alpha.262",
12
+ "changes": [
13
+ "test(daemon): inject explicit `vi.mock(\"...heart/daemon/socket-client\", ...)` blocks into all 40 grandfathered test files in `TESTAGENT_NO_MOCK_ALLOWLIST`. The runtime guard in `socket-client.ts` already prevents real socket leaks under vitest, but the explicit mocks let those tests assert call counts cleanly and shrink the contract-test allowlist to zero. Both the testagent and the bundle-write allowlists in `test-isolation.contract.test.ts` are now empty Sets \u2014 any new offender fails the build."
14
+ ]
15
+ },
4
16
  {
5
17
  "version": "0.1.0-alpha.261",
6
18
  "changes": [
@@ -119,6 +119,12 @@ function classifyEscapeSequence(inputChar) {
119
119
  // End: \x1b[F or \x1b[4~
120
120
  if (seq === "F" || seq === "4~")
121
121
  return "end";
122
+ // Option+Left: \x1b[1;3D (CSI modified arrow with Alt modifier)
123
+ if (seq === "1;3D")
124
+ return "word-left";
125
+ // Option+Right: \x1b[1;3C
126
+ if (seq === "1;3C")
127
+ return "word-right";
122
128
  // PageUp: \x1b[5~, PageDown: \x1b[6~
123
129
  if (seq === "5~" || seq === "6~")
124
130
  return "ignore";
@@ -324,39 +324,20 @@ function InputArea({ onSubmit, onCtrlC, history, queuedInputs, onPopQueue, agent
324
324
  historyIdx.current = -1;
325
325
  return;
326
326
  }
327
- // Left/right arrow: move cursor
327
+ // Left/right arrow: move cursor (char by char, token-aware)
328
+ // NOTE: Ink 3.2 bug — key.meta is ALWAYS true for arrow keys because all
329
+ // arrows start with \x1b and Ink sets meta=true for any ESC-prefixed input.
330
+ // Word-jump is handled via Meta+B/F (emacs bindings) instead.
328
331
  if (key.leftArrow) {
329
- if (key.meta) {
330
- // Option+Left: jump to previous word boundary
331
- const before = inputRef.current.slice(0, cursorRef.current);
332
- const match = before.match(/(?:^|\s)\S+\s*$/);
333
- const newPos = match ? cursorRef.current - match[0].length + (match[0].startsWith(" ") ? 1 : 0) : 0;
334
- cursorRef.current = Math.max(0, newPos);
335
- setCursorPos(cursorRef.current);
336
- }
337
- else {
338
- // Token-aware: hop over image ref chips
339
- const chipStart = (0, image_ref_navigation_1.imageRefEndingAt)(inputRef.current, cursorRef.current);
340
- cursorRef.current = chipStart !== undefined ? chipStart : Math.max(0, cursorRef.current - 1);
341
- setCursorPos(cursorRef.current);
342
- }
332
+ const chipStart = (0, image_ref_navigation_1.imageRefEndingAt)(inputRef.current, cursorRef.current);
333
+ cursorRef.current = chipStart !== undefined ? chipStart : Math.max(0, cursorRef.current - 1);
334
+ setCursorPos(cursorRef.current);
343
335
  return;
344
336
  }
345
337
  if (key.rightArrow) {
346
- if (key.meta) {
347
- // Option+Right: jump to next word boundary
348
- const after = inputRef.current.slice(cursorRef.current);
349
- const match = after.match(/^\s*\S+/);
350
- const newPos = match ? cursorRef.current + match[0].length : inputRef.current.length;
351
- cursorRef.current = Math.min(inputRef.current.length, newPos);
352
- setCursorPos(cursorRef.current);
353
- }
354
- else {
355
- // Token-aware: hop over image ref chips
356
- const chipEnd = (0, image_ref_navigation_1.imageRefStartingAt)(inputRef.current, cursorRef.current);
357
- cursorRef.current = chipEnd !== undefined ? chipEnd : Math.min(inputRef.current.length, cursorRef.current + 1);
358
- setCursorPos(cursorRef.current);
359
- }
338
+ const chipEnd = (0, image_ref_navigation_1.imageRefStartingAt)(inputRef.current, cursorRef.current);
339
+ cursorRef.current = chipEnd !== undefined ? chipEnd : Math.min(inputRef.current.length, cursorRef.current + 1);
340
+ setCursorPos(cursorRef.current);
360
341
  return;
361
342
  }
362
343
  // Up/Down: queue pop takes priority over history
@@ -542,22 +523,38 @@ function InputArea({ onSubmit, onCtrlC, history, queuedInputs, onPopQueue, agent
542
523
  // ─── Non-kill/non-yank keystroke resets ────────────────────────
543
524
  killRing.resetAccumulation();
544
525
  killRing.resetYankState();
526
+ // Detect raw escape sequences — Ink 3.2 strips \x1b prefix and sets key.meta,
527
+ // so we re-prepend it for classification when meta is set and inputChar starts with [
528
+ const escInput = key.meta && inputChar.startsWith("[") ? "\x1b" + inputChar : inputChar;
529
+ const escClass = (0, input_keys_1.classifyEscapeSequence)(escInput);
530
+ if (escClass === "home") {
531
+ cursorRef.current = (0, input_keys_1.handleHome)(inputRef.current, cursorRef.current);
532
+ setCursorPos(cursorRef.current);
533
+ return;
534
+ }
535
+ if (escClass === "end") {
536
+ cursorRef.current = (0, input_keys_1.handleEnd)(inputRef.current, cursorRef.current);
537
+ setCursorPos(cursorRef.current);
538
+ return;
539
+ }
540
+ if (escClass === "word-left") {
541
+ const before = inputRef.current.slice(0, cursorRef.current);
542
+ const match = before.match(/(?:^|\s)\S+\s*$/);
543
+ cursorRef.current = match ? Math.max(0, cursorRef.current - match[0].length + (match[0].startsWith(" ") ? 1 : 0)) : 0;
544
+ setCursorPos(cursorRef.current);
545
+ return;
546
+ }
547
+ if (escClass === "word-right") {
548
+ const after = inputRef.current.slice(cursorRef.current);
549
+ const match = after.match(/^\s*\S+/);
550
+ cursorRef.current = match ? Math.min(inputRef.current.length, cursorRef.current + match[0].length) : inputRef.current.length;
551
+ setCursorPos(cursorRef.current);
552
+ return;
553
+ }
554
+ if (escClass === "ignore")
555
+ return;
545
556
  // Regular character: insert at cursor position
546
557
  if (!key.ctrl && !key.meta && inputChar) {
547
- // Detect raw escape sequences before inserting text
548
- const escClass = (0, input_keys_1.classifyEscapeSequence)(inputChar);
549
- if (escClass === "home") {
550
- cursorRef.current = (0, input_keys_1.handleHome)(inputRef.current, cursorRef.current);
551
- setCursorPos(cursorRef.current);
552
- return;
553
- }
554
- if (escClass === "end") {
555
- cursorRef.current = (0, input_keys_1.handleEnd)(inputRef.current, cursorRef.current);
556
- setCursorPos(cursorRef.current);
557
- return;
558
- }
559
- if (escClass === "ignore")
560
- return; // PageUp/PageDown/mouse wheel fallback
561
558
  const before = inputRef.current.slice(0, cursorRef.current);
562
559
  const after = inputRef.current.slice(cursorRef.current);
563
560
  updateInput(before + inputChar + after, cursorRef.current + 1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.261",
3
+ "version": "0.1.0-alpha.263",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",