@falkenslab/agent-kit 0.10.1 → 0.10.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.
@@ -34,7 +34,8 @@ export declare const SHORTCUTS: string[];
34
34
  * blocks folded into a token, multi-line input (`\` + Enter or Ctrl+J), word and line
35
35
  * shortcuts (Ctrl+W/K/U, Ctrl+←/→, Home/End, Ctrl+A/E), reverse history search (Ctrl+R)
36
36
  * and taking a queued line back (↑ on an empty prompt). Written here rather than taken from
37
- * @inkjs/ui because its TextInput is uncontrolled. Backspace also arrives as `delete` on
38
- * Windows terminals, so both erase backwards.
37
+ * @inkjs/ui because its TextInput is uncontrolled. Ink reports Backspace and Delete both as
38
+ * `key.delete`: the raw input chunk (read before Ink parses it) tells Delete apart, which
39
+ * erases forwards; anything else reported as delete erases backwards.
39
40
  */
40
41
  export declare function PromptInput({ label, inset, suggestion, history, commands, onSubmit, onExit, onRecallQueued, files }: PromptInputProps): import("react").JSX.Element;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useContext, useLayoutEffect, useReducer, useRef } from "react";
3
- import { Box, Text, useInput, useStdout } from "ink";
3
+ import { Box, Text, useInput, useStdin, useStdout } from "ink";
4
4
  import stringWidth from "string-width";
5
5
  import { fitWidth, stripAnsi } from "./lineBuffer.js";
6
6
  import { liveWidth } from "./sessionModel.js";
@@ -8,7 +8,7 @@ import { isMouseReport } from "./fullscreen.js";
8
8
  import { CursorContext } from "./terminalCursor.js";
9
9
  import { isFocusReport } from "./terminalStatus.js";
10
10
  import { matchingFiles, mentionAt } from "./fileMentions.js";
11
- import { continueLine, createPasteRegistry, cursorLine, deleteBack, deleteWordBack, insert, isBlockPaste, killToLineEnd, moveLine, normalizePaste, searchHistory, toLineEnd, toLineStart, wordLeft, wordRight, } from "./promptEditing.js";
11
+ import { continueLine, createPasteRegistry, cursorLine, deleteBack, deleteForward, deleteWordBack, insert, isBlockPaste, isForwardDelete, killToLineEnd, moveLine, normalizePaste, searchHistory, toLineEnd, toLineStart, wordLeft, wordRight, } from "./promptEditing.js";
12
12
  import * as ui from "../ui.js";
13
13
  const MAX_SUGGESTIONS = 6;
14
14
  /** Commands (names without "/") a partial "/name" line could still become. */
@@ -56,13 +56,15 @@ export const SHORTCUTS = [
56
56
  // A paste can reach us in several chunks; pieces this close together extend the same one.
57
57
  const PASTE_CHUNK_MS = 100;
58
58
  const PASTE_TOKEN_BEFORE = /\[Pasted text #\d+ \+\d+ lines?\]$/;
59
+ const PASTE_TOKEN_AFTER = /^\[Pasted text #\d+ \+\d+ lines?\]/;
59
60
  /**
60
61
  * The chat's prompt: history (↑/↓), "/command" completion and suggestions (Tab), pasted
61
62
  * blocks folded into a token, multi-line input (`\` + Enter or Ctrl+J), word and line
62
63
  * shortcuts (Ctrl+W/K/U, Ctrl+←/→, Home/End, Ctrl+A/E), reverse history search (Ctrl+R)
63
64
  * and taking a queued line back (↑ on an empty prompt). Written here rather than taken from
64
- * @inkjs/ui because its TextInput is uncontrolled. Backspace also arrives as `delete` on
65
- * Windows terminals, so both erase backwards.
65
+ * @inkjs/ui because its TextInput is uncontrolled. Ink reports Backspace and Delete both as
66
+ * `key.delete`: the raw input chunk (read before Ink parses it) tells Delete apart, which
67
+ * erases forwards; anything else reported as delete erases backwards.
66
68
  */
67
69
  export function PromptInput({ label, inset = 0, suggestion, history, commands, onSubmit, onExit, onRecallQueued, files = [] }) {
68
70
  // Kept in a ref, not in state: two keystrokes can arrive before React re-renders, and a
@@ -78,6 +80,19 @@ export function PromptInput({ label, inset = 0, suggestion, history, commands, o
78
80
  });
79
81
  const pastes = useRef(createPasteRegistry()).current;
80
82
  const [, rerender] = useReducer((n) => n + 1, 0);
83
+ // Whether the chunk being handled is the Delete key. Prepended, so it runs before the
84
+ // useInput listener below, which reads it (both listen to the same chunk on Ink's emitter).
85
+ const forwardDelete = useRef(false);
86
+ const { internal_eventEmitter: stdinEvents } = useStdin();
87
+ useLayoutEffect(() => {
88
+ const onInput = (data) => {
89
+ forwardDelete.current = isForwardDelete(String(data));
90
+ };
91
+ stdinEvents.prependListener("input", onInput);
92
+ return () => {
93
+ stdinEvents.removeListener("input", onInput);
94
+ };
95
+ }, [stdinEvents]);
81
96
  function apply(next) {
82
97
  state.current.value = next.value;
83
98
  state.current.cursor = next.cursor;
@@ -106,6 +121,9 @@ export function PromptInput({ label, inset = 0, suggestion, history, commands, o
106
121
  else if (key.return) {
107
122
  finish(true);
108
123
  }
124
+ else if (key.delete && forwardDelete.current) {
125
+ // The search query has no cursor inside it: Delete has nothing after it to erase.
126
+ }
109
127
  else if (key.backspace || key.delete) {
110
128
  search.query = search.query.slice(0, -1);
111
129
  search.index = searchHistory(history, search.query);
@@ -242,6 +260,11 @@ export function PromptInput({ label, inset = 0, suggestion, history, commands, o
242
260
  else if (key.end || (key.ctrl && input === "e")) {
243
261
  apply(toLineEnd(current));
244
262
  }
263
+ else if (key.delete && forwardDelete.current) {
264
+ // A pasted block goes away whole, as with Backspace.
265
+ const token = PASTE_TOKEN_AFTER.exec(value.slice(cursor))?.[0];
266
+ apply(token ? { value: value.slice(0, cursor) + value.slice(cursor + token.length), cursor } : deleteForward(current));
267
+ }
245
268
  else if (key.backspace || key.delete) {
246
269
  // A pasted block goes away whole.
247
270
  const token = PASTE_TOKEN_BEFORE.exec(value.slice(0, cursor))?.[0];
@@ -9,6 +9,15 @@ export interface EditState {
9
9
  }
10
10
  export declare function insert({ value, cursor }: EditState, text: string): EditState;
11
11
  export declare function deleteBack({ value, cursor }: EditState): EditState;
12
+ /** Delete (Supr): removes the character after the cursor, which stays where it is. */
13
+ export declare function deleteForward({ value, cursor }: EditState): EditState;
14
+ /**
15
+ * Whether a raw input chunk is the Delete key: `ESC [ 3 ~`, with modifiers `ESC [ 3 ; 5 ~`,
16
+ * rxvt's `ESC [ 3 $` / `ESC [ 3 ^`, or the kitty protocol's `ESC [ 3 ; …~`. Ink reports it and
17
+ * Backspace (`DEL`, 0x7f, which Windows terminals send too) both as `key.delete`, so only the
18
+ * raw sequence tells them apart.
19
+ */
20
+ export declare function isForwardDelete(data: string): boolean;
12
21
  /** Ctrl+W: deletes the word before the cursor. */
13
22
  export declare function deleteWordBack({ value, cursor }: EditState): EditState;
14
23
  /** Ctrl+K: deletes from the cursor to the end of its line. */
@@ -6,6 +6,23 @@ export function deleteBack({ value, cursor }) {
6
6
  return { value, cursor };
7
7
  return { value: value.slice(0, cursor - 1) + value.slice(cursor), cursor: cursor - 1 };
8
8
  }
9
+ /** Delete (Supr): removes the character after the cursor, which stays where it is. */
10
+ export function deleteForward({ value, cursor }) {
11
+ if (cursor >= value.length)
12
+ return { value, cursor };
13
+ return { value: value.slice(0, cursor) + value.slice(cursor + 1), cursor };
14
+ }
15
+ // eslint-disable-next-line no-control-regex -- \x1b is the ESC byte the Delete key's sequence starts with
16
+ const FORWARD_DELETE = /^\x1b\[3(;[\d:]*)?[~$^]$/;
17
+ /**
18
+ * Whether a raw input chunk is the Delete key: `ESC [ 3 ~`, with modifiers `ESC [ 3 ; 5 ~`,
19
+ * rxvt's `ESC [ 3 $` / `ESC [ 3 ^`, or the kitty protocol's `ESC [ 3 ; …~`. Ink reports it and
20
+ * Backspace (`DEL`, 0x7f, which Windows terminals send too) both as `key.delete`, so only the
21
+ * raw sequence tells them apart.
22
+ */
23
+ export function isForwardDelete(data) {
24
+ return FORWARD_DELETE.test(data);
25
+ }
9
26
  const isSpace = (char) => char !== undefined && /\s/.test(char);
10
27
  /** Where the word before the cursor starts (spaces right before it skipped). */
11
28
  function wordStartBefore(value, cursor) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@falkenslab/agent-kit",
3
- "version": "0.10.1",
3
+ "version": "0.10.2",
4
4
  "description": "Library for building an agent on top of the Claude Agent SDK",
5
5
  "author": "Francisco Vargas Ruiz",
6
6
  "license": "MIT",