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.
- package/README.md +13 -2
- package/dist/agent/agent-loop.d.ts +3 -5
- package/dist/agent/agent-loop.js +42 -98
- package/dist/agent/conversation-state.d.ts +9 -0
- package/dist/agent/conversation-state.js +16 -0
- package/dist/agent/history-file.d.ts +6 -0
- package/dist/agent/history-file.js +1 -1
- package/dist/agent/host-types.d.ts +125 -0
- package/dist/agent/index.d.ts +12 -4
- package/dist/agent/index.js +358 -6
- package/dist/agent/nuclear-form.d.ts +7 -0
- package/dist/{extensions → agent}/providers/deepseek.d.ts +2 -2
- package/dist/{extensions → agent}/providers/deepseek.js +5 -4
- package/dist/{extensions → agent}/providers/openai-compatible.d.ts +2 -2
- package/dist/{extensions → agent}/providers/openai.d.ts +2 -2
- package/dist/{extensions → agent}/providers/openai.js +3 -2
- package/dist/{extensions → agent}/providers/openrouter.d.ts +2 -2
- package/dist/{extensions → agent}/providers/openrouter.js +4 -3
- package/dist/agent/skills.js +51 -7
- package/dist/agent/subagent.d.ts +1 -1
- package/dist/agent/system-prompt.js +14 -17
- package/dist/agent/tool-protocol.d.ts +1 -1
- package/dist/agent/tool-protocol.js +5 -3
- package/dist/agent/tool-registry.d.ts +9 -4
- package/dist/agent/tool-registry.js +27 -4
- package/dist/agent/tools/bash.d.ts +1 -1
- package/dist/agent/tools/bash.js +3 -2
- package/dist/agent/tools/edit-file.js +0 -1
- package/dist/agent/tools/glob.js +1 -1
- package/dist/agent/tools/grep.js +1 -1
- package/dist/agent/tools/pwsh.d.ts +1 -1
- package/dist/agent/tools/pwsh.js +1 -2
- package/dist/agent/tools/read-file.js +7 -4
- package/dist/agent/tools/write-file.js +0 -1
- package/dist/agent/types.d.ts +17 -2
- package/dist/cli/auth/cli.d.ts +1 -0
- package/dist/cli/auth/cli.js +216 -0
- package/dist/cli/auth/keys.d.ts +31 -0
- package/dist/cli/auth/keys.js +102 -0
- package/dist/{index.js → cli/index.js} +29 -32
- package/dist/{init.js → cli/init.js} +1 -1
- package/dist/{install.js → cli/install.js} +31 -2
- package/dist/cli/subcommands.d.ts +1 -0
- package/dist/cli/subcommands.js +17 -0
- package/dist/{event-bus.d.ts → core/event-bus.d.ts} +7 -13
- package/dist/{extension-loader.d.ts → core/extension-loader.d.ts} +1 -1
- package/dist/{extension-loader.js → core/extension-loader.js} +62 -70
- package/dist/{core.d.ts → core/index.d.ts} +18 -15
- package/dist/{core.js → core/index.js} +18 -92
- package/dist/{settings.d.ts → core/settings.d.ts} +7 -0
- package/dist/{settings.js → core/settings.js} +1 -0
- package/dist/core/types.d.ts +49 -0
- package/dist/core/types.js +1 -0
- package/dist/extensions/file-autocomplete.d.ts +1 -1
- package/dist/extensions/index.d.ts +7 -14
- package/dist/extensions/index.js +2 -19
- package/dist/extensions/slash-commands.d.ts +1 -1
- package/dist/extensions/slash-commands.js +7 -2
- package/dist/shell/host-types.d.ts +114 -0
- package/dist/shell/host-types.js +1 -0
- package/dist/shell/index.d.ts +8 -7
- package/dist/shell/index.js +58 -9
- package/dist/shell/input-handler.d.ts +7 -1
- package/dist/shell/input-handler.js +5 -2
- package/dist/shell/output-parser.d.ts +1 -1
- package/dist/{extensions → shell}/shell-context.d.ts +1 -1
- package/dist/{extensions → shell}/shell-context.js +18 -12
- package/dist/shell/shell.d.ts +6 -4
- package/dist/shell/shell.js +33 -109
- package/dist/shell/strategies/bash.d.ts +2 -0
- package/dist/shell/strategies/bash.js +68 -0
- package/dist/shell/strategies/fish.d.ts +2 -0
- package/dist/shell/strategies/fish.js +65 -0
- package/dist/shell/strategies/index.d.ts +13 -0
- package/dist/shell/strategies/index.js +17 -0
- package/dist/shell/strategies/types.d.ts +50 -0
- package/dist/shell/strategies/types.js +9 -0
- package/dist/shell/strategies/zsh.d.ts +2 -0
- package/dist/shell/strategies/zsh.js +72 -0
- package/dist/shell/tui-input-view.js +14 -3
- package/dist/{extensions → shell}/tui-renderer.d.ts +1 -1
- package/dist/{extensions → shell}/tui-renderer.js +27 -55
- package/dist/utils/box-frame.d.ts +4 -0
- package/dist/utils/box-frame.js +17 -6
- package/dist/utils/compositor.d.ts +1 -1
- package/dist/utils/compositor.js +2 -1
- package/dist/{executor.js → utils/executor.js} +1 -1
- package/dist/utils/floating-panel.d.ts +1 -1
- package/dist/utils/floating-panel.js +9 -4
- package/dist/utils/llm-client.d.ts +16 -26
- package/dist/utils/llm-client.js +15 -26
- package/dist/utils/llm-facade.d.ts +7 -3
- package/dist/utils/stream-transform.d.ts +1 -1
- package/dist/utils/terminal-buffer.d.ts +1 -1
- package/dist/utils/tool-display.js +4 -0
- package/dist/utils/tool-interactive.d.ts +1 -1
- package/dist/utils/tty.d.ts +7 -0
- package/dist/utils/tty.js +15 -0
- package/examples/extensions/ash-acp-bridge/README.md +4 -1
- package/examples/extensions/ash-acp-bridge/src/index.ts +654 -0
- package/examples/extensions/ash-mcp-bridge/index.ts +1 -1
- package/examples/extensions/ashi/README.md +250 -0
- package/examples/extensions/ashi/package.json +60 -0
- package/examples/extensions/ashi/src/autocomplete.ts +91 -0
- package/examples/extensions/ashi/src/capture.ts +34 -0
- package/examples/extensions/ashi/src/cli.ts +176 -0
- package/examples/extensions/ashi/src/commands.ts +82 -0
- package/examples/extensions/ashi/src/compaction.ts +157 -0
- package/examples/extensions/ashi/src/components.ts +327 -0
- package/examples/extensions/ashi/src/default-renderers.ts +153 -0
- package/examples/extensions/ashi/src/display-config.ts +62 -0
- package/examples/extensions/ashi/src/frontend.ts +735 -0
- package/examples/extensions/ashi/src/hooks.ts +136 -0
- package/examples/extensions/ashi/src/multi-session-store.ts +146 -0
- package/examples/extensions/ashi/src/session-commands.ts +76 -0
- package/examples/extensions/ashi/src/session-store.ts +264 -0
- package/examples/extensions/ashi/src/status-footer.ts +66 -0
- package/examples/extensions/ashi/src/theme.ts +151 -0
- package/examples/extensions/ashi/tsconfig.json +14 -0
- package/examples/extensions/emacs-buffer.ts +1 -1
- package/examples/extensions/interactive-prompts.ts +114 -69
- package/examples/extensions/latex-images.ts +3 -3
- package/examples/extensions/opencode-bridge/index.ts +1 -1
- package/examples/extensions/overlay-agent.ts +7 -5
- package/examples/extensions/peer-mesh.ts +1 -1
- package/examples/extensions/pi-bridge/index.ts +0 -1
- package/examples/extensions/questionnaire.ts +2 -1
- package/examples/extensions/rtk-proxy.ts +3 -3
- package/examples/extensions/solarized-theme.ts +3 -3
- package/examples/extensions/subagents.ts +6 -6
- package/examples/extensions/terminal-buffer.ts +1 -1
- package/examples/extensions/tmux-pane.ts +6 -4
- package/examples/extensions/tunnel-vision.ts +5 -5
- package/examples/extensions/user-shell.ts +1 -1
- package/examples/extensions/web-access.ts +5 -5
- package/package.json +38 -22
- package/dist/extensions/agent-backend.d.ts +0 -14
- package/dist/extensions/agent-backend.js +0 -307
- package/dist/types.d.ts +0 -227
- /package/dist/{types.js → agent/host-types.js} +0 -0
- /package/dist/{extensions → agent}/providers/openai-compatible.js +0 -0
- /package/dist/{index.d.ts → cli/index.d.ts} +0 -0
- /package/dist/{init.d.ts → cli/init.d.ts} +0 -0
- /package/dist/{install.d.ts → cli/install.d.ts} +0 -0
- /package/dist/{event-bus.js → core/event-bus.js} +0 -0
- /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 ${
|
|
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)} ${
|
|
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 "
|
|
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
|
|
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
|
-
|
|
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.
|
package/dist/utils/box-frame.js
CHANGED
|
@@ -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.
|
package/dist/utils/compositor.js
CHANGED
|
@@ -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 "./
|
|
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
|
-
//
|
|
920
|
-
// stdout-hold.
|
|
921
|
-
//
|
|
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
|
-
|
|
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>;
|
package/dist/utils/llm-client.js
CHANGED
|
@@ -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
|
|
39
|
+
const { signal, messages, tools, model, max_tokens, ...rest } = opts;
|
|
44
40
|
const body = {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
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
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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 {
|
|
6
|
-
|
|
7
|
-
|
|
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;
|
|
@@ -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
|
-
-
|
|
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.
|