kaizen-agent 1.34.2 → 1.34.4

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/web/term.ts CHANGED
@@ -41,7 +41,47 @@ function mountTerm(el: HTMLElement, key: string, onEnd?: () => void): () => void
41
41
  term.write(`\r\n\x1b[2m[${e.reason === "ended" ? "agent ended" : "connection to the board lost"}]\x1b[0m\r\n`);
42
42
  onEnd?.();
43
43
  };
44
- const typed = term.onData((d) => send({ t: "i", d }));
44
+ // Phone/tablet: a bar of the keys a soft keyboard lacks. Ctrl and Alt are sticky —
45
+ // they modify the next key typed, from the soft keyboard or the bar. The ⌨ button
46
+ // hides the native keyboard (inputmode none) so the bar alone drives the terminal.
47
+ const mods = { ctrl: false, alt: false };
48
+ const typed = term.onData((d) => {
49
+ if (d.length === 1 && mods.ctrl && /[a-z@[\\\]^_]/i.test(d)) d = String.fromCharCode(d.toUpperCase().charCodeAt(0) & 31);
50
+ if (mods.alt) d = "\x1b" + d;
51
+ mods.ctrl = mods.alt = false;
52
+ bar?.querySelectorAll(".on").forEach((b) => b.classList.remove("on"));
53
+ send({ t: "i", d });
54
+ });
55
+ let bar: HTMLElement | null = null;
56
+ if (matchMedia("(pointer: coarse)").matches) {
57
+ const arrow = (c: string) => () => `\x1b${term.modes.applicationCursorKeysMode ? "O" : "["}${c}`;
58
+ const keys: [string, string | (() => string) | "ctrl" | "alt" | "kbd"][] = [
59
+ ["Esc", "\x1b"], ["Tab", "\t"], ["↵", "\r"], ["⇧⇥", "\x1b[Z"], ["Ctrl", "ctrl"], ["Alt", "alt"],
60
+ ["←", arrow("D")], ["↑", arrow("A")], ["↓", arrow("B")], ["→", arrow("C")], ["^C", "\x03"], ["⌨", "kbd"],
61
+ ];
62
+ bar = document.createElement("div");
63
+ bar.className = "termkeys";
64
+ for (const [label, v] of keys) {
65
+ const b = document.createElement("button");
66
+ b.type = "button";
67
+ b.textContent = label;
68
+ // pointerdown + preventDefault: the tap must not move focus off the terminal, or the keyboard closes.
69
+ b.addEventListener("pointerdown", (e) => {
70
+ e.preventDefault();
71
+ if (v === "ctrl" || v === "alt") b.classList.toggle("on", (mods[v] = !mods[v]));
72
+ else if (v === "kbd") {
73
+ const ta = el.querySelector<HTMLTextAreaElement>(".xterm-helper-textarea");
74
+ if (ta) ta.inputMode = ta.inputMode === "none" ? "text" : "none";
75
+ b.classList.toggle("on", ta?.inputMode === "none");
76
+ ta?.blur();
77
+ ta?.focus();
78
+ } else send({ t: "i", d: (typeof v === "function" ? v() : v) as string });
79
+ term.focus();
80
+ });
81
+ bar.append(b);
82
+ }
83
+ el.after(bar);
84
+ }
45
85
  // xterm.js has no touch scrolling, so on a phone a swipe did nothing. Each line's
46
86
  // worth of swipe scrolls the buffer, or — when the agent tracks the mouse, as a
47
87
  // full-screen Claude Code does — goes to it as a wheel report.
@@ -74,6 +114,7 @@ function mountTerm(el: HTMLElement, key: string, onEnd?: () => void): () => void
74
114
  el.removeEventListener("touchstart", touchStart);
75
115
  el.removeEventListener("touchmove", touchMove);
76
116
  typed.dispose();
117
+ bar?.remove();
77
118
  ws.onclose = null;
78
119
  ws.close();
79
120
  term.dispose();