kaizen-agent 1.44.0 → 1.46.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/web/term.ts CHANGED
@@ -4,6 +4,8 @@
4
4
  import { Terminal } from "@xterm/xterm";
5
5
  import { FitAddon } from "@xterm/addon-fit";
6
6
  import { WebglAddon } from "@xterm/addon-webgl";
7
+ import { WebLinksAddon } from "@xterm/addon-web-links";
8
+ import { SearchAddon } from "@xterm/addon-search";
7
9
 
8
10
  function mountTerm(el: HTMLElement, key: string, onEnd?: () => void): () => void {
9
11
  const css = getComputedStyle(el);
@@ -13,6 +15,7 @@ function mountTerm(el: HTMLElement, key: string, onEnd?: () => void): () => void
13
15
  fontFamily: tok("--mono"),
14
16
  fontSize: 12,
15
17
  scrollback: 5000,
18
+ allowProposedApi: true, // search match decorations
16
19
  theme: {
17
20
  background: tok("--bg"),
18
21
  foreground: tok("--ink-hi"),
@@ -22,6 +25,9 @@ function mountTerm(el: HTMLElement, key: string, onEnd?: () => void): () => void
22
25
  });
23
26
  const fit = new FitAddon();
24
27
  term.loadAddon(fit);
28
+ term.loadAddon(new WebLinksAddon((_e, url) => window.open(url, "_blank", "noopener")));
29
+ const search = new SearchAddon();
30
+ term.loadAddon(search);
25
31
  term.open(el);
26
32
  // The DOM renderer lags on a full-screen agent's constant redraws; WebGL keeps up.
27
33
  // No WebGL, or the context lost: xterm.js falls back to the DOM renderer by itself.
@@ -148,6 +154,58 @@ function mountTerm(el: HTMLElement, key: string, onEnd?: () => void): () => void
148
154
  };
149
155
  el.addEventListener("touchstart", touchStart, { passive: true });
150
156
  el.addEventListener("touchmove", touchMove, { passive: false });
157
+ // Top-right strip: search the output (all devices; Ctrl/Cmd+Shift+F from the terminal)
158
+ // and, with a mouse, Paste — phones already have Paste in the key bar.
159
+ const tools = document.createElement("div");
160
+ tools.className = "termtools";
161
+ const tool = (label: string, title: string, fn: () => void) => {
162
+ const b = document.createElement("button");
163
+ b.type = "button";
164
+ b.textContent = label;
165
+ b.title = title;
166
+ b.addEventListener("mousedown", (e) => e.preventDefault());
167
+ b.addEventListener("click", fn);
168
+ return b;
169
+ };
170
+ const q = document.createElement("input");
171
+ q.type = "search";
172
+ q.placeholder = "Search";
173
+ const deco = { matchBackground: "#7a5c00", activeMatchBackground: "#d08000", matchOverviewRuler: "#d08000", activeMatchColorOverviewRuler: "#ff9900" };
174
+ const find = (back = false) => {
175
+ if (!q.value) return search.clearDecorations();
176
+ const o = { decorations: deco };
177
+ back ? search.findPrevious(q.value, o) : search.findNext(q.value, o);
178
+ };
179
+ const box = document.createElement("span");
180
+ box.className = "tsearch";
181
+ box.hidden = true;
182
+ const closeSearch = () => { box.hidden = true; search.clearDecorations(); term.focus(); };
183
+ const openSearch = () => { box.hidden = false; q.select(); q.focus(); };
184
+ q.addEventListener("input", () => find());
185
+ q.addEventListener("keydown", (e) => {
186
+ if (e.key === "Enter") { e.preventDefault(); find(e.shiftKey); }
187
+ else if (e.key === "Escape") { e.preventDefault(); closeSearch(); }
188
+ });
189
+ box.append(q, tool("↑", "Previous (Shift+Enter)", () => find(true)), tool("↓", "Next (Enter)", () => find()), tool("✕", "Close (Esc)", closeSearch));
190
+ tools.append(box, tool("⌕", "Search (Ctrl+Shift+F)", () => (box.hidden ? openSearch() : closeSearch())));
191
+ // A refused read (permission, or a plain-http origin) says so on the button for a moment.
192
+ const pasteBtn = tool("Paste", "Paste from clipboard", () => {
193
+ const no = () => { pasteBtn.textContent = "Blocked"; setTimeout(() => (pasteBtn.textContent = "Paste"), 1500); };
194
+ if (!navigator.clipboard?.readText) no();
195
+ else navigator.clipboard.readText().then((t) => t && term.paste(t), no);
196
+ term.focus();
197
+ });
198
+ if (!bar) tools.append(pasteBtn);
199
+ el.append(tools);
200
+ // xterm keeps one custom key handler: add further shortcuts here, not in a second call.
201
+ term.attachCustomKeyEventHandler((e) => {
202
+ if (e.type === "keydown" && (e.ctrlKey || e.metaKey) && e.shiftKey && e.code === "KeyF") {
203
+ e.preventDefault();
204
+ openSearch();
205
+ return false;
206
+ }
207
+ return true;
208
+ });
151
209
  // Each resize makes the agent redraw its whole screen, so a panel animating or a
152
210
  // soft keyboard opening sends one, not dozens.
153
211
  let resizing: Timer | undefined;
@@ -162,6 +220,7 @@ function mountTerm(el: HTMLElement, key: string, onEnd?: () => void): () => void
162
220
  el.removeEventListener("touchmove", touchMove);
163
221
  typed.dispose();
164
222
  bar?.remove();
223
+ tools.remove();
165
224
  ws.onclose = null;
166
225
  ws.close();
167
226
  term.dispose();