agent-sh 0.12.27 → 0.13.1

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 (146) hide show
  1. package/README.md +13 -2
  2. package/dist/agent/agent-loop.d.ts +3 -5
  3. package/dist/agent/agent-loop.js +42 -98
  4. package/dist/agent/conversation-state.d.ts +9 -0
  5. package/dist/agent/conversation-state.js +16 -0
  6. package/dist/agent/history-file.d.ts +6 -0
  7. package/dist/agent/history-file.js +1 -1
  8. package/dist/agent/host-types.d.ts +125 -0
  9. package/dist/agent/index.d.ts +12 -4
  10. package/dist/agent/index.js +358 -6
  11. package/dist/agent/nuclear-form.d.ts +7 -0
  12. package/dist/{extensions → agent}/providers/deepseek.d.ts +2 -2
  13. package/dist/{extensions → agent}/providers/deepseek.js +5 -4
  14. package/dist/{extensions → agent}/providers/openai-compatible.d.ts +2 -2
  15. package/dist/{extensions → agent}/providers/openai.d.ts +2 -2
  16. package/dist/{extensions → agent}/providers/openai.js +3 -2
  17. package/dist/{extensions → agent}/providers/openrouter.d.ts +2 -2
  18. package/dist/{extensions → agent}/providers/openrouter.js +4 -3
  19. package/dist/agent/skills.js +51 -7
  20. package/dist/agent/subagent.d.ts +1 -1
  21. package/dist/agent/system-prompt.js +14 -17
  22. package/dist/agent/tool-protocol.d.ts +1 -1
  23. package/dist/agent/tool-protocol.js +5 -3
  24. package/dist/agent/tool-registry.d.ts +9 -4
  25. package/dist/agent/tool-registry.js +27 -4
  26. package/dist/agent/tools/bash.d.ts +1 -1
  27. package/dist/agent/tools/bash.js +3 -2
  28. package/dist/agent/tools/edit-file.js +0 -1
  29. package/dist/agent/tools/glob.js +1 -1
  30. package/dist/agent/tools/grep.js +1 -1
  31. package/dist/agent/tools/pwsh.d.ts +1 -1
  32. package/dist/agent/tools/pwsh.js +1 -2
  33. package/dist/agent/tools/read-file.js +7 -4
  34. package/dist/agent/tools/write-file.js +0 -1
  35. package/dist/agent/types.d.ts +17 -2
  36. package/dist/cli/auth/cli.d.ts +1 -0
  37. package/dist/cli/auth/cli.js +216 -0
  38. package/dist/cli/auth/keys.d.ts +31 -0
  39. package/dist/cli/auth/keys.js +102 -0
  40. package/dist/{index.js → cli/index.js} +29 -32
  41. package/dist/{init.js → cli/init.js} +1 -1
  42. package/dist/{install.js → cli/install.js} +31 -2
  43. package/dist/cli/subcommands.d.ts +1 -0
  44. package/dist/cli/subcommands.js +17 -0
  45. package/dist/{event-bus.d.ts → core/event-bus.d.ts} +7 -13
  46. package/dist/{extension-loader.d.ts → core/extension-loader.d.ts} +1 -1
  47. package/dist/{extension-loader.js → core/extension-loader.js} +62 -70
  48. package/dist/{core.d.ts → core/index.d.ts} +18 -15
  49. package/dist/{core.js → core/index.js} +18 -92
  50. package/dist/{settings.d.ts → core/settings.d.ts} +7 -0
  51. package/dist/{settings.js → core/settings.js} +1 -0
  52. package/dist/core/types.d.ts +49 -0
  53. package/dist/core/types.js +1 -0
  54. package/dist/extensions/file-autocomplete.d.ts +1 -1
  55. package/dist/extensions/index.d.ts +7 -14
  56. package/dist/extensions/index.js +2 -19
  57. package/dist/extensions/slash-commands.d.ts +1 -1
  58. package/dist/extensions/slash-commands.js +7 -2
  59. package/dist/shell/host-types.d.ts +114 -0
  60. package/dist/shell/host-types.js +1 -0
  61. package/dist/shell/index.d.ts +8 -7
  62. package/dist/shell/index.js +58 -9
  63. package/dist/shell/input-handler.d.ts +7 -1
  64. package/dist/shell/input-handler.js +5 -2
  65. package/dist/shell/output-parser.d.ts +1 -1
  66. package/dist/{extensions → shell}/shell-context.d.ts +1 -1
  67. package/dist/{extensions → shell}/shell-context.js +18 -12
  68. package/dist/shell/shell.d.ts +6 -4
  69. package/dist/shell/shell.js +33 -109
  70. package/dist/shell/strategies/bash.d.ts +2 -0
  71. package/dist/shell/strategies/bash.js +68 -0
  72. package/dist/shell/strategies/fish.d.ts +2 -0
  73. package/dist/shell/strategies/fish.js +65 -0
  74. package/dist/shell/strategies/index.d.ts +13 -0
  75. package/dist/shell/strategies/index.js +17 -0
  76. package/dist/shell/strategies/types.d.ts +50 -0
  77. package/dist/shell/strategies/types.js +9 -0
  78. package/dist/shell/strategies/zsh.d.ts +2 -0
  79. package/dist/shell/strategies/zsh.js +72 -0
  80. package/dist/shell/tui-input-view.js +14 -3
  81. package/dist/{extensions → shell}/tui-renderer.d.ts +1 -1
  82. package/dist/{extensions → shell}/tui-renderer.js +27 -55
  83. package/dist/utils/box-frame.d.ts +4 -0
  84. package/dist/utils/box-frame.js +17 -6
  85. package/dist/utils/compositor.d.ts +1 -1
  86. package/dist/utils/compositor.js +2 -1
  87. package/dist/{executor.js → utils/executor.js} +1 -1
  88. package/dist/utils/floating-panel.d.ts +1 -1
  89. package/dist/utils/floating-panel.js +9 -4
  90. package/dist/utils/llm-client.d.ts +16 -26
  91. package/dist/utils/llm-client.js +15 -26
  92. package/dist/utils/llm-facade.d.ts +7 -3
  93. package/dist/utils/stream-transform.d.ts +1 -1
  94. package/dist/utils/terminal-buffer.d.ts +1 -1
  95. package/dist/utils/tool-display.js +4 -0
  96. package/dist/utils/tool-interactive.d.ts +1 -1
  97. package/dist/utils/tty.d.ts +7 -0
  98. package/dist/utils/tty.js +15 -0
  99. package/examples/extensions/ash-acp-bridge/README.md +4 -1
  100. package/examples/extensions/ash-acp-bridge/src/index.ts +654 -0
  101. package/examples/extensions/ash-mcp-bridge/index.ts +1 -1
  102. package/examples/extensions/ashi/README.md +250 -0
  103. package/examples/extensions/ashi/package.json +60 -0
  104. package/examples/extensions/ashi/src/autocomplete.ts +91 -0
  105. package/examples/extensions/ashi/src/capture.ts +34 -0
  106. package/examples/extensions/ashi/src/cli.ts +176 -0
  107. package/examples/extensions/ashi/src/commands.ts +82 -0
  108. package/examples/extensions/ashi/src/compaction.ts +157 -0
  109. package/examples/extensions/ashi/src/components.ts +327 -0
  110. package/examples/extensions/ashi/src/default-renderers.ts +153 -0
  111. package/examples/extensions/ashi/src/display-config.ts +62 -0
  112. package/examples/extensions/ashi/src/frontend.ts +735 -0
  113. package/examples/extensions/ashi/src/hooks.ts +136 -0
  114. package/examples/extensions/ashi/src/multi-session-store.ts +146 -0
  115. package/examples/extensions/ashi/src/session-commands.ts +76 -0
  116. package/examples/extensions/ashi/src/session-store.ts +264 -0
  117. package/examples/extensions/ashi/src/status-footer.ts +66 -0
  118. package/examples/extensions/ashi/src/theme.ts +151 -0
  119. package/examples/extensions/ashi/tsconfig.json +14 -0
  120. package/examples/extensions/emacs-buffer.ts +1 -1
  121. package/examples/extensions/interactive-prompts.ts +114 -69
  122. package/examples/extensions/latex-images.ts +3 -3
  123. package/examples/extensions/opencode-bridge/index.ts +1 -1
  124. package/examples/extensions/overlay-agent.ts +7 -5
  125. package/examples/extensions/peer-mesh.ts +1 -1
  126. package/examples/extensions/pi-bridge/index.ts +0 -1
  127. package/examples/extensions/questionnaire.ts +2 -1
  128. package/examples/extensions/rtk-proxy.ts +3 -3
  129. package/examples/extensions/solarized-theme.ts +3 -3
  130. package/examples/extensions/subagents.ts +6 -6
  131. package/examples/extensions/terminal-buffer.ts +1 -1
  132. package/examples/extensions/tmux-pane.ts +6 -4
  133. package/examples/extensions/tunnel-vision.ts +5 -5
  134. package/examples/extensions/user-shell.ts +1 -1
  135. package/examples/extensions/web-access.ts +5 -5
  136. package/package.json +38 -22
  137. package/dist/extensions/agent-backend.d.ts +0 -14
  138. package/dist/extensions/agent-backend.js +0 -307
  139. package/dist/types.d.ts +0 -227
  140. /package/dist/{types.js → agent/host-types.js} +0 -0
  141. /package/dist/{extensions → agent}/providers/openai-compatible.js +0 -0
  142. /package/dist/{index.d.ts → cli/index.d.ts} +0 -0
  143. /package/dist/{init.d.ts → cli/init.d.ts} +0 -0
  144. /package/dist/{install.d.ts → cli/install.d.ts} +0 -0
  145. /package/dist/{event-bus.js → core/event-bus.js} +0 -0
  146. /package/dist/{executor.d.ts → utils/executor.d.ts} +0 -0
@@ -3,7 +3,7 @@
3
3
  * Owns screen state (cursor row/col, autocomplete line count) and the
4
4
  * ANSI redraw. The controller drives it via a small VM shape.
5
5
  */
6
- import { visibleLen } from "../utils/ansi.js";
6
+ import { visibleLen, truncateToWidth } from "../utils/ansi.js";
7
7
  import { palette as p } from "../utils/palette.js";
8
8
  import { StdoutSurface } from "../utils/compositor.js";
9
9
  export class TuiInputView {
@@ -146,15 +146,26 @@ export class TuiInputView {
146
146
  if (vm.items.length === 0)
147
147
  return;
148
148
  this.autoFrame(() => {
149
+ // Truncate descriptions so each row fits one physical line — a wrapped
150
+ // row makes autocompleteLines undercount, clearAutocomplete leaves a
151
+ // residual row, and the next drawPrompt redraws below the original
152
+ // prompt (staircase).
153
+ const termW = this.surface.columns;
149
154
  const lines = [];
150
155
  for (let i = 0; i < vm.items.length; i++) {
151
156
  const item = vm.items[i];
157
+ const nameW = Math.max(12, visibleLen(item.name));
158
+ const overhead = 5 + nameW;
159
+ const descBudget = Math.max(1, termW - overhead);
160
+ const desc = visibleLen(item.description) > descBudget
161
+ ? truncateToWidth(item.description, descBudget)
162
+ : item.description;
152
163
  const selected = i === vm.selected;
153
164
  if (selected) {
154
- lines.push(` \x1b[7m ${p.accent}${item.name.padEnd(12)}${p.reset}\x1b[7m ${item.description} ${p.reset}`);
165
+ lines.push(` \x1b[7m ${p.accent}${item.name.padEnd(12)}${p.reset}\x1b[7m ${desc} ${p.reset}`);
155
166
  }
156
167
  else {
157
- lines.push(` ${p.muted}${item.name.padEnd(12)} ${item.description}${p.reset}`);
168
+ lines.push(` ${p.muted}${item.name.padEnd(12)} ${desc}${p.reset}`);
158
169
  }
159
170
  }
160
171
  this.emit("\n" + lines.join("\n"));
@@ -1,2 +1,2 @@
1
- import type { ExtensionContext } from "../types.js";
1
+ import type { ExtensionContext } from "./host-types.js";
2
2
  export default function activate(ctx: ExtensionContext): void;
@@ -18,7 +18,7 @@ import { palette as p } from "../utils/palette.js";
18
18
  import { renderToolCall, createSpinner, formatElapsed, SPINNER_FRAMES, } from "../utils/tool-display.js";
19
19
  import { renderDiff } from "../utils/diff-renderer.js";
20
20
  import { renderBoxFrame } from "../utils/box-frame.js";
21
- import { getSettings } from "../settings.js";
21
+ import { getSettings } from "../core/settings.js";
22
22
  /** Encode a PNG buffer as a terminal inline image escape sequence. */
23
23
  function encodeImageForTerminal(data) {
24
24
  const b64 = data.toString("base64");
@@ -67,12 +67,11 @@ function createRenderState() {
67
67
  isThinking: false,
68
68
  showThinkingText: false,
69
69
  thinkingPending: false,
70
- previewedDiffPending: false,
71
- previewedDiffToolIds: new Set(),
72
70
  };
73
71
  }
74
72
  export default function activate(ctx) {
75
- const { bus, define, compositor } = ctx;
73
+ const { bus, define } = ctx;
74
+ const compositor = ctx.shell.compositor;
76
75
  const s = createRenderState();
77
76
  /** Track the shell's cwd so path shortening is relative to where the user actually is. */
78
77
  let shellCwd = process.cwd();
@@ -273,10 +272,6 @@ export default function activate(ctx) {
273
272
  s.currentToolKind = e.kind;
274
273
  s.toolStartTime = Date.now();
275
274
  s.orphanContHeaderKind = undefined;
276
- if (s.previewedDiffPending && e.toolCallId) {
277
- s.previewedDiffToolIds.add(e.toolCallId);
278
- }
279
- s.previewedDiffPending = false;
280
275
  if (e.title === "user_shell") {
281
276
  finalizeToolGroup();
282
277
  closeToolLine();
@@ -340,13 +335,7 @@ export default function activate(ctx) {
340
335
  s.toolExitCode = e.exitCode;
341
336
  if (e.exitCode !== 0)
342
337
  s.toolGroupAllOk = false;
343
- let resultDisplay = e.resultDisplay;
344
- if (e.toolCallId && s.previewedDiffToolIds.has(e.toolCallId)) {
345
- s.previewedDiffToolIds.delete(e.toolCallId);
346
- if (resultDisplay?.body?.kind === "diff") {
347
- resultDisplay = { ...resultDisplay, body: undefined };
348
- }
349
- }
338
+ const resultDisplay = e.resultDisplay;
350
339
  if (s.toolGroupKind) {
351
340
  // Grouped tool — track success/failure and summaries, show aggregate on ⎿ line.
352
341
  // Don't restart spinner between grouped tools — it's already running from group start.
@@ -411,46 +400,6 @@ export default function activate(ctx) {
411
400
  s.renderer.writeLine("");
412
401
  drain();
413
402
  });
414
- bus.on("permission:request", (e) => {
415
- if (!shouldRender())
416
- return;
417
- stopCurrentSpinner();
418
- flushCommandOutput();
419
- if (s.renderer) {
420
- s.renderer.flush();
421
- drain();
422
- }
423
- // Diff rendering is handled in the async pipe below so it can yield
424
- // to the event loop between hunks (keeping the spinner responsive).
425
- });
426
- // Async pipe: render diffs via the tui:render-diff handler (extensions can
427
- // advise to customize). Runs after the sync `on` handler above (which
428
- // flushes state) and before shell.ts's pipe (which pauses stdout).
429
- bus.onPipeAsync("permission:request", async (e) => {
430
- if (!shouldRender())
431
- return e;
432
- if (e.kind === "file-write" && e.metadata?.diff) {
433
- showCollapsedThinking();
434
- const lines = ctx.call("tui:render-diff", e.title, e.metadata.diff, cappedW());
435
- if (lines.length > 0) {
436
- if (!s.renderer)
437
- startAgentResponse();
438
- contentGap("diff");
439
- for (const line of lines)
440
- s.renderer.writeLine(line);
441
- drain();
442
- }
443
- // The diff box IS the visual representation of the upcoming tool call.
444
- // Mark lastContentKind as "tool" so the tool call line that follows
445
- // doesn't inject an extra gap between the diff box and the checkmark.
446
- s.lastContentKind = "tool";
447
- s.previewedDiffPending = true;
448
- }
449
- // Don't endAgentResponse() here — permission requests that aren't
450
- // file-write diffs are handled inline (auto-approved or by extensions).
451
- // Closing the response prematurely causes double separator borders.
452
- return e;
453
- });
454
403
  bus.on("input:keypress", (e) => {
455
404
  if (e.key === "\x14")
456
405
  toggleThinkingDisplay(); // Ctrl+T
@@ -661,6 +610,25 @@ export default function activate(ctx) {
661
610
  define("tui:render-diff", (filePath, diff, width) => {
662
611
  return renderDiffBody(diff, filePath, width);
663
612
  });
613
+ /** Display a diff inline above the agent response (e.g. for pre-execute
614
+ * preview from a gating advisor). Formats via `tui:render-diff` so
615
+ * advisors can theme it. */
616
+ define("tui:show-diff", (filePath, diff) => {
617
+ if (!shouldRender())
618
+ return;
619
+ stopCurrentSpinner();
620
+ showCollapsedThinking();
621
+ const lines = ctx.call("tui:render-diff", filePath, diff, cappedW());
622
+ if (lines.length === 0)
623
+ return;
624
+ if (!s.renderer)
625
+ startAgentResponse();
626
+ contentGap("diff");
627
+ for (const line of lines)
628
+ s.renderer.writeLine(line);
629
+ drain();
630
+ s.lastContentKind = "tool";
631
+ });
664
632
  /** Render a diff as framed box lines (pure — no TUI state side effects). */
665
633
  function renderDiffBody(diff, filePath, width) {
666
634
  if (diff.isIdentical)
@@ -716,6 +684,10 @@ export default function activate(ctx) {
716
684
  return "";
717
685
  if (typeof raw.command === "string")
718
686
  return `$ ${raw.command}`;
687
+ if (typeof raw.source === "string") {
688
+ const firstLine = raw.source.split("\n").map((l) => l.trim()).find((l) => l.length > 0) ?? "";
689
+ return firstLine.length > 80 ? firstLine.slice(0, 77) + "…" : firstLine;
690
+ }
719
691
  if (typeof raw.pattern === "string")
720
692
  return raw.pattern;
721
693
  if (typeof raw.path === "string") {
@@ -12,6 +12,10 @@ export interface BoxFrameOptions {
12
12
  titleRight?: string;
13
13
  /** Footer lines shown below a divider, inside the box. */
14
14
  footer?: string[];
15
+ /** Raw bg ANSI open code (e.g. "\x1b[48;2;40;50;40m"). When set, fills
16
+ * the frame interior with this bg and rewrites internal `\x1b[49m` /
17
+ * `\x1b[0m` resets so per-cell colors in content don't punch holes. */
18
+ bgColor?: string;
15
19
  }
16
20
  /**
17
21
  * Render content lines inside a bordered frame.
@@ -27,6 +27,7 @@ export function renderBoxFrame(content, opts) {
27
27
  const style = opts.style ?? "rounded";
28
28
  const b = BORDERS[style];
29
29
  const bc = borderColor;
30
+ const bg = opts.bgColor ?? "";
30
31
  // Content area width = total - 2 borders - 2 padding spaces
31
32
  const innerW = Math.max(1, width - 4);
32
33
  const output = [];
@@ -47,26 +48,36 @@ export function renderBoxFrame(content, opts) {
47
48
  const leftPart = title ? `${p.reset} ${title} ${bc}` : "";
48
49
  const rightPart = opts.titleRight ? `${p.reset} ${opts.titleRight} ${bc}` : "";
49
50
  const dashCount = Math.max(1, width - 2 - leftVis - rightVis);
50
- output.push(`${bc}${b.tl}${leftPart}${b.h.repeat(dashCount)}${rightPart}${b.tr}${p.reset}`);
51
+ output.push(paintBg(`${bc}${b.tl}${leftPart}${b.h.repeat(dashCount)}${rightPart}${b.tr}${p.reset}`, bg));
51
52
  }
52
53
  else {
53
- output.push(`${bc}${b.tl}${b.h.repeat(width - 2)}${b.tr}${p.reset}`);
54
+ output.push(paintBg(`${bc}${b.tl}${b.h.repeat(width - 2)}${b.tr}${p.reset}`, bg));
54
55
  }
55
56
  // Content lines
56
57
  for (const line of content) {
57
- output.push(boxLine(line, innerW, b.v, bc));
58
+ output.push(paintBg(boxLine(line, innerW, b.v, bc), bg));
58
59
  }
59
60
  // Footer with divider
60
61
  if (opts.footer && opts.footer.length > 0) {
61
- output.push(`${bc}${b.ml}${b.h.repeat(width - 2)}${b.mr}${p.reset}`);
62
+ output.push(paintBg(`${bc}${b.ml}${b.h.repeat(width - 2)}${b.mr}${p.reset}`, bg));
62
63
  for (const line of opts.footer) {
63
- output.push(boxLine(line, innerW, b.v, bc));
64
+ output.push(paintBg(boxLine(line, innerW, b.v, bc), bg));
64
65
  }
65
66
  }
66
67
  // Bottom border
67
- output.push(`${bc}${b.bl}${b.h.repeat(width - 2)}${b.br}${p.reset}`);
68
+ output.push(paintBg(`${bc}${b.bl}${b.h.repeat(width - 2)}${b.br}${p.reset}`, bg));
68
69
  return output;
69
70
  }
71
+ /** Wrap a line with a uniform bg, rewriting internal `\x1b[49m` (bg-default)
72
+ * and `\x1b[0m` (full-reset) so embedded colors don't punch through. */
73
+ function paintBg(line, bg) {
74
+ if (!bg)
75
+ return line;
76
+ const fixed = line
77
+ .replaceAll("\x1b[49m", bg)
78
+ .replaceAll("\x1b[0m", "\x1b[0m" + bg);
79
+ return `${bg}${fixed}\x1b[49m`;
80
+ }
70
81
  // ── Helpers ──────────────────────────────────────────────────────
71
82
  function boxLine(text, innerW, v, bc) {
72
83
  const textWidth = visibleLen(text);
@@ -24,7 +24,7 @@
24
24
  * compositor.redirect("agent:diff", diffPanelSurface);
25
25
  * // "agent:text", "agent:tool" etc. still go to stdout
26
26
  */
27
- import type { EventBus } from "../event-bus.js";
27
+ import type { EventBus } from "../core/event-bus.js";
28
28
  /**
29
29
  * A surface accepts rendered output. Stdout is a surface.
30
30
  * A floating panel's content area is a surface. A test buffer is a surface.
@@ -36,8 +36,9 @@ export const nullSurface = {
36
36
  export class StdoutSurface {
37
37
  write(text) {
38
38
  if (process.stdout.writable) {
39
+ // OPOST is cleared on the TTY; add CR to lone \n so we don't staircase.
39
40
  try {
40
- process.stdout.write(text);
41
+ process.stdout.write(text.replace(/(?<!\r)\n/g, "\r\n"));
41
42
  }
42
43
  catch { /* ignore */ }
43
44
  }
@@ -1,6 +1,6 @@
1
1
  import { spawn, spawnSync } from "node:child_process";
2
2
  import { existsSync } from "node:fs";
3
- import { stripAnsi } from "./utils/ansi.js";
3
+ import { stripAnsi } from "./ansi.js";
4
4
  // Node reports a missing cwd as `spawn <binary> ENOENT` — disambiguate.
5
5
  function explainSpawnError(err, cwd) {
6
6
  if (err.code === "ENOENT" && !existsSync(cwd)) {
@@ -1,6 +1,6 @@
1
1
  import { TerminalBuffer } from "./terminal-buffer.js";
2
2
  import { HandlerRegistry } from "./handler-registry.js";
3
- import type { EventBus } from "../event-bus.js";
3
+ import type { EventBus } from "../core/event-bus.js";
4
4
  import type { BorderStyle } from "./box-frame.js";
5
5
  import { type RenderSurface } from "./compositor.js";
6
6
  export interface FloatingPanelConfig {
@@ -916,11 +916,16 @@ export class FloatingPanel {
916
916
  }
917
917
  this.surface.write("\x1b[?1049l");
918
918
  if (!this.usedAltScreen) {
919
- // Program exited mid-overlay; its reset bytes were eaten by
920
- // stdout-hold. Reset modes serialize() doesn't track or the
921
- // host stays in vim's modifyOtherKeys mode.
919
+ // Alt-screen TUI exited mid-overlay; its reset bytes were eaten
920
+ // by stdout-hold. Re-emit the modes commonly set by full-screen
921
+ // programs (vim, neovim, emacs -nw, less, htop, tmux, ssh→TUI):
922
+ // modifyOtherKeys, kitty kbd, bracketed paste, focus reporting,
923
+ // mouse, DECCKM cursor-key mode, application keypad, cursor
924
+ // blink. Without this, arrow keys / keypad digits / cursor state
925
+ // misbehave at the post-overlay shell prompt.
922
926
  this.surface.write("\x1b[>4;0m\x1b[<u\x1b[?2004l\x1b[?1004l" +
923
- "\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l");
927
+ "\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l" +
928
+ "\x1b[?1l\x1b>\x1b[?12l");
924
929
  }
925
930
  this.bus.emit("shell:stdout-release", {});
926
931
  const serialized = this.buffer?.serialize();
@@ -24,30 +24,20 @@ export declare class LlmClient {
24
24
  constructor(config: LlmClientConfig);
25
25
  /** Swap the underlying client config at runtime (e.g. provider switch). */
26
26
  reconfigure(newConfig: LlmClientConfig): void;
27
- /**
28
- * Create a streaming chat completion.
29
- * Returns an async iterable of chunks.
30
- */
31
- stream(opts: {
32
- messages: ChatCompletionMessageParam[];
33
- tools?: ChatCompletionTool[];
34
- model?: string;
35
- max_tokens?: number;
36
- /** Reasoning effort: "off" | "low" | "medium" | "high". Provider-dependent;
37
- * "off" matches agent-loop's thinkingLevel and omits the field. */
38
- reasoning_effort?: string;
39
- signal?: AbortSignal;
40
- }): import("openai").APIPromise<import("openai/core/streaming.mjs").Stream<OpenAI.Chat.Completions.ChatCompletionChunk>>;
41
- /**
42
- * Single-shot completion (no streaming) — for fast-path features.
43
- * Returns the text content of the first choice.
44
- */
45
- complete(opts: {
46
- messages: ChatCompletionMessageParam[];
47
- model?: string;
48
- max_tokens?: number;
49
- /** Reasoning effort: "off" | "low" | "medium" | "high". Provider-dependent;
50
- * "off" matches agent-loop's thinkingLevel and omits the field. */
51
- reasoning_effort?: string;
52
- }): Promise<string>;
27
+ stream(opts: StreamOpts): import("openai").APIPromise<import("openai/core/streaming.mjs").Stream<OpenAI.Chat.Completions.ChatCompletionChunk>>;
28
+ complete(opts: CompleteOpts): Promise<string>;
53
29
  }
30
+ /** Known fields are typed; extras are forwarded verbatim to the SDK so
31
+ * provider hooks can ship non-standard params (thinking, reasoning, …). */
32
+ export type StreamOpts = {
33
+ messages: ChatCompletionMessageParam[];
34
+ tools?: ChatCompletionTool[];
35
+ model?: string;
36
+ max_tokens?: number;
37
+ signal?: AbortSignal;
38
+ } & Record<string, unknown>;
39
+ export type CompleteOpts = {
40
+ messages: ChatCompletionMessageParam[];
41
+ model?: string;
42
+ max_tokens?: number;
43
+ } & Record<string, unknown>;
@@ -35,39 +35,28 @@ export class LlmClient {
35
35
  });
36
36
  this.model = newConfig.model;
37
37
  }
38
- /**
39
- * Create a streaming chat completion.
40
- * Returns an async iterable of chunks.
41
- */
42
38
  stream(opts) {
43
- const sendEffort = opts.reasoning_effort && opts.reasoning_effort !== "off";
39
+ const { signal, messages, tools, model, max_tokens, ...rest } = opts;
44
40
  const body = {
45
- model: opts.model ?? this.model,
46
- messages: opts.messages,
47
- tools: opts.tools?.length ? opts.tools : undefined,
48
- max_tokens: opts.max_tokens ?? 65536,
41
+ ...rest,
42
+ model: model ?? this.model,
43
+ messages,
44
+ tools: tools?.length ? tools : undefined,
45
+ max_tokens: max_tokens ?? 65536,
49
46
  stream: true,
50
47
  stream_options: { include_usage: true },
51
- ...(sendEffort
52
- ? { reasoning_effort: opts.reasoning_effort }
53
- : {}),
54
48
  };
55
- return this.client.chat.completions.create(body, { signal: opts.signal });
49
+ return this.client.chat.completions.create(body, { signal });
56
50
  }
57
- /**
58
- * Single-shot completion (no streaming) — for fast-path features.
59
- * Returns the text content of the first choice.
60
- */
61
51
  async complete(opts) {
62
- const sendEffort = opts.reasoning_effort && opts.reasoning_effort !== "off";
63
- const response = await this.client.chat.completions.create({
64
- model: opts.model ?? this.model,
65
- messages: opts.messages,
66
- max_tokens: opts.max_tokens ?? 1024,
67
- ...(sendEffort
68
- ? { reasoning_effort: opts.reasoning_effort }
69
- : {}),
70
- });
52
+ const { messages, model, max_tokens, ...rest } = opts;
53
+ const body = {
54
+ ...rest,
55
+ model: model ?? this.model,
56
+ messages,
57
+ max_tokens: max_tokens ?? 1024,
58
+ };
59
+ const response = await this.client.chat.completions.create(body);
71
60
  return response.choices[0]?.message?.content ?? "";
72
61
  }
73
62
  }
@@ -2,6 +2,10 @@
2
2
  * ctx.llm facade — delegates to an `llm:invoke` handler registered by the
3
3
  * active backend. No handler → `available` is false and calls reject.
4
4
  */
5
- import type { HandlerRegistry } from "./handler-registry.js";
6
- import type { LlmInterface } from "../types.js";
7
- export declare function createLlmFacade(handlers: HandlerRegistry): LlmInterface;
5
+ import type { LlmInterface } from "../agent/host-types.js";
6
+ interface HandlerGate {
7
+ list: () => string[];
8
+ call: (name: string, ...args: unknown[]) => unknown;
9
+ }
10
+ export declare function createLlmFacade(handlers: HandlerGate): LlmInterface;
11
+ export {};
@@ -4,7 +4,7 @@
4
4
  * Handles the boilerplate of buffering across chunk boundaries,
5
5
  * pattern matching, and flush-on-done coordination.
6
6
  */
7
- import type { EventBus, ContentBlock } from "../event-bus.js";
7
+ import type { EventBus, ContentBlock } from "../core/event-bus.js";
8
8
  export interface BlockTransformOptions {
9
9
  /** Opening delimiter (e.g. "$$") */
10
10
  open: string;
@@ -1,4 +1,4 @@
1
- import type { EventBus } from "../event-bus.js";
1
+ import type { EventBus } from "../core/event-bus.js";
2
2
  export interface TerminalBufferConfig {
3
3
  /** Terminal width in columns. Default: process.stdout.columns || 80. */
4
4
  cols?: number;
@@ -79,6 +79,10 @@ export function renderToolCall(tool, width, cwd = process.cwd()) {
79
79
  if (typeof raw.command === "string") {
80
80
  detail = `$ ${raw.command}`;
81
81
  }
82
+ else if (typeof raw.source === "string") {
83
+ const firstLine = raw.source.split("\n").map((l) => l.trim()).find((l) => l.length > 0) ?? "";
84
+ detail = firstLine.length > 80 ? firstLine.slice(0, 77) + "…" : firstLine;
85
+ }
82
86
  else if (typeof raw.pattern === "string") {
83
87
  // grep/glob — show the search pattern
84
88
  const target = typeof raw.path === "string" ? ` ${shortenPath(raw.path, cwd)}` : "";
@@ -6,7 +6,7 @@
6
6
  * handles surface writing, input interception, shell pause/unpause,
7
7
  * and cleanup.
8
8
  */
9
- import type { EventBus } from "../event-bus.js";
9
+ import type { EventBus } from "../core/event-bus.js";
10
10
  import type { RenderSurface } from "./compositor.js";
11
11
  import type { ToolUI } from "../agent/types.js";
12
12
  export declare function createToolUI(bus: EventBus, surface: RenderSurface): ToolUI;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * libuv's setRawMode(true) keeps OPOST on, so the kernel rewrites \n to
3
+ * \r\n. TUIs over ssh (e.g. emacs -nw) emit relative moves like "\n\n\n\b\b"
4
+ * assuming a raw path — with OPOST on, \n snaps the cursor to col 0 and
5
+ * subsequent text lands in the wrong column. Call after every setRawMode(true).
6
+ */
7
+ export declare function clearOpost(): void;
@@ -0,0 +1,15 @@
1
+ import { execSync } from "child_process";
2
+ /**
3
+ * libuv's setRawMode(true) keeps OPOST on, so the kernel rewrites \n to
4
+ * \r\n. TUIs over ssh (e.g. emacs -nw) emit relative moves like "\n\n\n\b\b"
5
+ * assuming a raw path — with OPOST on, \n snaps the cursor to col 0 and
6
+ * subsequent text lands in the wrong column. Call after every setRawMode(true).
7
+ */
8
+ export function clearOpost() {
9
+ if (!process.stdin.isTTY)
10
+ return;
11
+ try {
12
+ execSync("stty -opost", { stdio: "inherit" });
13
+ }
14
+ catch { /* best effort */ }
15
+ }
@@ -36,4 +36,7 @@ The adapter translates between ACP methods and agent-sh's event bus:
36
36
  - `session/new` → create core, set cwd
37
37
  - `session/prompt` → `agent:submit` event
38
38
  - `session/update` notifications ← `agent:response-chunk`, `agent:tool-started`, etc.
39
- - `session/request_permission` `permission:request` async pipe
39
+ - Permission gating is not bridged — agent-sh runs tools without confirmation
40
+ unless a gating extension (e.g. interactive-prompts) is also loaded. To
41
+ surface ACP's `session/request_permission`, register tool advisors here
42
+ that call back into the ACP client.