agent-sh 0.14.1 → 0.14.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.
Files changed (55) hide show
  1. package/dist/agent/agent-loop.d.ts +1 -1
  2. package/dist/agent/agent-loop.js +42 -31
  3. package/dist/agent/conversation-state.d.ts +3 -2
  4. package/dist/agent/conversation-state.js +20 -3
  5. package/dist/agent/events.d.ts +2 -0
  6. package/dist/agent/host-types.d.ts +3 -0
  7. package/dist/agent/index.js +2 -1
  8. package/dist/agent/subagent.d.ts +1 -1
  9. package/dist/agent/subagent.js +5 -1
  10. package/dist/agent/tool-protocol.d.ts +2 -2
  11. package/dist/agent/tool-protocol.js +5 -4
  12. package/dist/agent/tools/glob.d.ts +1 -1
  13. package/dist/agent/tools/glob.js +4 -2
  14. package/dist/agent/tools/grep.d.ts +1 -1
  15. package/dist/agent/tools/grep.js +4 -2
  16. package/dist/agent/tools/ls.d.ts +1 -1
  17. package/dist/agent/tools/ls.js +4 -2
  18. package/dist/agent/tools/read-file.d.ts +1 -1
  19. package/dist/agent/tools/read-file.js +30 -2
  20. package/dist/agent/types.d.ts +11 -1
  21. package/dist/agent/types.js +6 -1
  22. package/dist/cli/index.js +0 -0
  23. package/dist/core/index.d.ts +1 -1
  24. package/dist/core/settings.d.ts +3 -0
  25. package/dist/core/settings.js +2 -2
  26. package/dist/shell/index.d.ts +6 -0
  27. package/dist/shell/index.js +10 -10
  28. package/dist/shell/shell.d.ts +4 -0
  29. package/dist/shell/shell.js +15 -29
  30. package/dist/shell/terminal.d.ts +33 -0
  31. package/dist/shell/terminal.js +62 -0
  32. package/examples/extensions/ash-scheme/index.ts +2170 -0
  33. package/examples/extensions/ash-scheme/package.json +11 -0
  34. package/examples/extensions/ash-scheme-render.ts +58 -0
  35. package/examples/extensions/ashi/README.md +36 -26
  36. package/examples/extensions/ashi/package.json +9 -1
  37. package/examples/extensions/ashi/src/capture.ts +1 -0
  38. package/examples/extensions/ashi/src/cli.ts +21 -7
  39. package/examples/extensions/ashi/src/compaction.ts +25 -96
  40. package/examples/extensions/ashi/src/components.ts +64 -166
  41. package/examples/extensions/ashi/src/default-schema-renderers.ts +229 -0
  42. package/examples/extensions/ashi/src/display-config.ts +21 -22
  43. package/examples/extensions/ashi/src/frontend.ts +64 -65
  44. package/examples/extensions/ashi/src/hooks.ts +47 -63
  45. package/examples/extensions/ashi/src/multi-session-store.ts +44 -3
  46. package/examples/extensions/ashi/src/schema.ts +407 -0
  47. package/examples/extensions/ashi/src/session-store.ts +55 -4
  48. package/examples/extensions/ashi/src/status-footer.ts +27 -6
  49. package/examples/extensions/ashi-compact-llm.ts +93 -0
  50. package/examples/extensions/claude-code-bridge/index.ts +2 -0
  51. package/examples/extensions/opencode-bridge/index.ts +3 -0
  52. package/examples/extensions/opencode-provider.ts +252 -0
  53. package/examples/extensions/pi-bridge/index.ts +1 -0
  54. package/package.json +12 -1
  55. package/examples/extensions/ashi/src/default-renderers.ts +0 -171
@@ -5,12 +5,13 @@
5
5
  */
6
6
  import "./events.js"; // augments BusEvents with shell-owned events
7
7
  import { Shell } from "./shell.js";
8
- import { DefaultCompositor, StdoutSurface } from "../utils/compositor.js";
8
+ import { DefaultCompositor } from "../utils/compositor.js";
9
9
  import { TerminalBuffer } from "../utils/terminal-buffer.js";
10
10
  import { setPalette } from "../utils/palette.js";
11
11
  import * as streamTransform from "../utils/stream-transform.js";
12
12
  import activateShellContext from "./shell-context.js";
13
13
  import activateTuiRenderer from "./tui-renderer.js";
14
+ import { processTerminal, surfaceFromTerminal } from "./terminal.js";
14
15
  /**
15
16
  * Register shell-owned handlers extensions can `ctx.call`, and attach
16
17
  * the shell surface to ctx. Must run before `loadExtensions` so user
@@ -77,10 +78,11 @@ export function registerShellHandlers(ctx) {
77
78
  * `src/cli/index.ts`) uses to drive lifecycle from process-level events.
78
79
  */
79
80
  export function activateShell(ctx, opts) {
80
- const stdoutSurface = new StdoutSurface();
81
- ctx.shell.compositor.setDefault("agent", stdoutSurface);
82
- ctx.shell.compositor.setDefault("query", stdoutSurface);
83
- ctx.shell.compositor.setDefault("status", stdoutSurface);
81
+ const terminal = opts.terminal ?? processTerminal();
82
+ const surface = surfaceFromTerminal(terminal);
83
+ ctx.shell.compositor.setDefault("agent", surface);
84
+ ctx.shell.compositor.setDefault("query", surface);
85
+ ctx.shell.compositor.setDefault("status", surface);
84
86
  const shell = new Shell({
85
87
  bus: ctx.bus,
86
88
  handlers: { define: ctx.define, call: ctx.call },
@@ -90,13 +92,11 @@ export function activateShell(ctx, opts) {
90
92
  cwd: opts.cwd,
91
93
  instanceId: ctx.instanceId,
92
94
  onShowAgentInfo: opts.onShowAgentInfo,
95
+ terminal,
93
96
  });
94
- const onResize = () => {
95
- shell.resize(process.stdout.columns || 80, process.stdout.rows || 24);
96
- };
97
- process.stdout.on("resize", onResize);
97
+ const offResize = terminal.onResize((cols, rows) => shell.resize(cols, rows));
98
98
  ctx.onDispose(() => {
99
- process.stdout.off("resize", onResize);
99
+ offResize();
100
100
  shell.kill();
101
101
  });
102
102
  return {
@@ -1,5 +1,6 @@
1
1
  import type { EventBus } from "../core/event-bus.js";
2
2
  import { type InputContext } from "./input-handler.js";
3
+ import { type Terminal } from "./terminal.js";
3
4
  export interface ShellHandlers {
4
5
  define: (name: string, fn: (...args: any[]) => any) => void;
5
6
  call: (name: string, ...args: any[]) => any;
@@ -19,6 +20,8 @@ export declare class Shell implements InputContext {
19
20
  private handlers;
20
21
  private inputHandler;
21
22
  private outputParser;
23
+ private terminal;
24
+ private inputDispose;
22
25
  private hardMuteScopes;
23
26
  private softMuteScopes;
24
27
  private unmuteScopes;
@@ -38,6 +41,7 @@ export declare class Shell implements InputContext {
38
41
  shell: string;
39
42
  cwd: string;
40
43
  instanceId: string;
44
+ terminal?: Terminal;
41
45
  });
42
46
  /** Compositing-layer claim — overrides any unmute. */
43
47
  acquireHardMute(reason: string): ShellScope;
@@ -5,6 +5,7 @@ import { InputHandler } from "./input-handler.js";
5
5
  import { OutputParser } from "./output-parser.js";
6
6
  import { getSettings } from "../core/settings.js";
7
7
  import { clearOpost } from "../utils/tty.js";
8
+ import { processTerminal } from "./terminal.js";
8
9
  import { pickStrategy, FALLBACK_STRATEGY, SUPPORTED_SHELL_NAMES, } from "./strategies/index.js";
9
10
  export class Shell {
10
11
  ptyProcess;
@@ -12,6 +13,8 @@ export class Shell {
12
13
  handlers;
13
14
  inputHandler;
14
15
  outputParser;
16
+ terminal;
17
+ inputDispose = null;
15
18
  // hardMute is unconditional (overlay compositing); softMute is overridable
16
19
  // by unmute (terminal_keys, permission UI). Gate: hard wins; otherwise
17
20
  // muted iff softMute held without an unmute.
@@ -23,6 +26,7 @@ export class Shell {
23
26
  strategy;
24
27
  tmpDir;
25
28
  constructor(opts) {
29
+ this.terminal = opts.terminal ?? processTerminal();
26
30
  // Build environment — filter out undefined values (node-pty's native
27
31
  // posix_spawnp fails if any env value is undefined)
28
32
  const env = {};
@@ -58,18 +62,10 @@ export class Shell {
58
62
  this.tmpDir = spawnConfig.tmpDir;
59
63
  Object.assign(env, spawnConfig.envOverrides);
60
64
  const shellArgs = spawnConfig.args;
61
- // Pause stdin before spawning PTY to avoid TTY contention on macOS.
62
- // The PTY will become the controlling terminal for the child shell.
63
- const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
64
- if (process.stdin.isTTY) {
65
- try {
66
- process.stdin.setRawMode(false);
67
- process.stdin.pause();
68
- }
69
- catch {
70
- // Ignore
71
- }
72
- }
65
+ // The PTY will become the controlling terminal for the child shell;
66
+ // suspend the host terminal's input around spawn to avoid TTY contention
67
+ // on macOS. Headless terminals make this a no-op.
68
+ const suspended = this.terminal.suspendInput?.();
73
69
  this.ptyProcess = pty.spawn(shellBin, shellArgs, {
74
70
  name: "xterm-256color",
75
71
  cols: opts.cols,
@@ -77,18 +73,7 @@ export class Shell {
77
73
  cwd: opts.cwd,
78
74
  env,
79
75
  });
80
- // Restore stdin after PTY is created
81
- if (process.stdin.isTTY) {
82
- try {
83
- process.stdin.resume();
84
- if (wasRaw) {
85
- process.stdin.setRawMode(true);
86
- }
87
- }
88
- catch {
89
- // Ignore - will be set up later in index.ts
90
- }
91
- }
76
+ suspended?.resume();
92
77
  clearOpost();
93
78
  this.bus = opts.bus;
94
79
  this.handlers = opts.handlers;
@@ -259,15 +244,14 @@ export class Shell {
259
244
  this.pendingEchoSkips--;
260
245
  const rest = data.slice(nlIdx + 1);
261
246
  if (rest)
262
- process.stdout.write(rest);
247
+ this.terminal.write(rest);
263
248
  return;
264
249
  }
265
- process.stdout.write(data);
250
+ this.terminal.write(data);
266
251
  });
267
252
  }
268
253
  setupInput() {
269
- process.stdin.on("data", (data) => {
270
- const str = data.toString("utf-8");
254
+ this.inputDispose = this.terminal.onInput((str) => {
271
255
  this.inputHandler.handleInput(str);
272
256
  });
273
257
  }
@@ -304,7 +288,7 @@ export class Shell {
304
288
  this.bus.onPipeAsync("shell:exec-request", async (payload) => {
305
289
  const visible = this.acquireUnmute("exec-request");
306
290
  this.skipNextLine();
307
- process.stdout.write("\r\n");
291
+ this.terminal.write("\r\n");
308
292
  this.bus.emit("shell:agent-exec-start", {});
309
293
  try {
310
294
  const output = await new Promise((resolve, reject) => {
@@ -347,6 +331,8 @@ export class Shell {
347
331
  this.ptyProcess.onExit(callback);
348
332
  }
349
333
  kill() {
334
+ this.inputDispose?.();
335
+ this.inputDispose = null;
350
336
  this.ptyProcess.kill();
351
337
  if (this.tmpDir) {
352
338
  fs.rmSync(this.tmpDir, { recursive: true, force: true });
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Terminal — the user-facing I/O endpoint that a Shell talks to.
3
+ *
4
+ * Shell wraps a *pseudo*-terminal (the PTY the child shell sees). This
5
+ * interface is the *real* terminal (or its substitute) on the other end:
6
+ * bytes in, bytes out, dimensions, resize notifications. The default
7
+ * factory wires it to process.stdin/stdout for the CLI; headless hosts
8
+ * (multi-session web hubs, tests) supply their own.
9
+ */
10
+ import type { RenderSurface } from "../utils/compositor.js";
11
+ export interface Terminal {
12
+ write(data: string): void;
13
+ onInput(cb: (data: string) => void): () => void;
14
+ onResize(cb: (cols: number, rows: number) => void): () => void;
15
+ cols(): number;
16
+ rows(): number;
17
+ /**
18
+ * Called around PTY spawn to avoid TTY contention: the child PTY becomes
19
+ * the controlling tty for the spawned shell. No-op when the terminal
20
+ * isn't a real tty.
21
+ */
22
+ suspendInput?(): {
23
+ resume(): void;
24
+ };
25
+ }
26
+ /** Default Terminal: wraps process.stdin/stdout. */
27
+ export declare function processTerminal(): Terminal;
28
+ /**
29
+ * Adapt a Terminal to a RenderSurface (the compositor's sink type). Adds
30
+ * the OPOST-cleared `\n` → `\r\n` translation that StdoutSurface applies,
31
+ * since the PTY has OPOST disabled.
32
+ */
33
+ export declare function surfaceFromTerminal(terminal: Terminal): RenderSurface;
@@ -0,0 +1,62 @@
1
+ /** Default Terminal: wraps process.stdin/stdout. */
2
+ export function processTerminal() {
3
+ return {
4
+ write(data) {
5
+ if (process.stdout.writable) {
6
+ try {
7
+ process.stdout.write(data);
8
+ }
9
+ catch { /* ignore */ }
10
+ }
11
+ },
12
+ onInput(cb) {
13
+ const handler = (b) => cb(b.toString("utf-8"));
14
+ process.stdin.on("data", handler);
15
+ return () => { process.stdin.off("data", handler); };
16
+ },
17
+ onResize(cb) {
18
+ const handler = () => cb(process.stdout.columns || 80, process.stdout.rows || 24);
19
+ process.stdout.on("resize", handler);
20
+ return () => { process.stdout.off("resize", handler); };
21
+ },
22
+ cols() { return process.stdout.columns || 80; },
23
+ rows() { return process.stdout.rows || 24; },
24
+ suspendInput() {
25
+ const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
26
+ if (process.stdin.isTTY) {
27
+ try {
28
+ process.stdin.setRawMode(false);
29
+ process.stdin.pause();
30
+ }
31
+ catch { /* ignore */ }
32
+ }
33
+ return {
34
+ resume() {
35
+ if (process.stdin.isTTY) {
36
+ try {
37
+ process.stdin.resume();
38
+ if (wasRaw)
39
+ process.stdin.setRawMode(true);
40
+ }
41
+ catch { /* ignore */ }
42
+ }
43
+ },
44
+ };
45
+ },
46
+ };
47
+ }
48
+ /**
49
+ * Adapt a Terminal to a RenderSurface (the compositor's sink type). Adds
50
+ * the OPOST-cleared `\n` → `\r\n` translation that StdoutSurface applies,
51
+ * since the PTY has OPOST disabled.
52
+ */
53
+ export function surfaceFromTerminal(terminal) {
54
+ const write = (text) => terminal.write(text.replace(/(?<!\r)\n/g, "\r\n"));
55
+ return {
56
+ write,
57
+ writeLine: (line) => write(line + "\n"),
58
+ get columns() { return terminal.cols(); },
59
+ get rows() { return terminal.rows(); },
60
+ onResize: (cb) => terminal.onResize(cb),
61
+ };
62
+ }