agent-sh 0.8.0 → 0.10.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/README.md +27 -43
- package/dist/agent/agent-loop.d.ts +69 -6
- package/dist/agent/agent-loop.js +954 -153
- package/dist/agent/conversation-state.d.ts +74 -21
- package/dist/agent/conversation-state.js +361 -150
- package/dist/agent/history-file.d.ts +13 -4
- package/dist/agent/history-file.js +110 -36
- package/dist/agent/nuclear-form.d.ts +28 -3
- package/dist/agent/nuclear-form.js +88 -6
- package/dist/agent/skills.d.ts +2 -4
- package/dist/agent/skills.js +10 -4
- package/dist/agent/subagent.d.ts +23 -0
- package/dist/agent/subagent.js +53 -11
- package/dist/agent/system-prompt.d.ts +37 -5
- package/dist/agent/system-prompt.js +100 -67
- package/dist/{token-budget.d.ts → agent/token-budget.d.ts} +5 -4
- package/dist/{token-budget.js → agent/token-budget.js} +15 -20
- package/dist/agent/tool-protocol.d.ts +105 -0
- package/dist/agent/tool-protocol.js +551 -0
- package/dist/agent/tools/bash.js +3 -3
- package/dist/agent/tools/edit-file.js +9 -6
- package/dist/agent/tools/glob.js +4 -2
- package/dist/agent/tools/grep.js +27 -3
- package/dist/agent/tools/ls.js +5 -6
- package/dist/agent/types.d.ts +22 -2
- package/dist/context-manager.d.ts +17 -0
- package/dist/context-manager.js +37 -4
- package/dist/core.d.ts +7 -7
- package/dist/core.js +99 -196
- package/dist/event-bus.d.ts +85 -2
- package/dist/event-bus.js +20 -1
- package/dist/executor.d.ts +4 -3
- package/dist/executor.js +18 -15
- package/dist/extension-loader.d.ts +5 -0
- package/dist/extension-loader.js +143 -19
- package/dist/extensions/agent-backend.d.ts +14 -0
- package/dist/extensions/agent-backend.js +188 -0
- package/dist/extensions/command-suggest.d.ts +3 -3
- package/dist/extensions/command-suggest.js +4 -3
- package/dist/extensions/index.d.ts +19 -0
- package/dist/extensions/index.js +24 -0
- package/dist/extensions/slash-commands.d.ts +1 -1
- package/dist/extensions/slash-commands.js +30 -10
- package/dist/extensions/tui-renderer.js +117 -113
- package/dist/index.js +39 -26
- package/dist/settings.d.ts +40 -3
- package/dist/settings.js +57 -10
- package/dist/{input-handler.d.ts → shell/input-handler.d.ts} +3 -2
- package/dist/{input-handler.js → shell/input-handler.js} +111 -85
- package/dist/{output-parser.d.ts → shell/output-parser.d.ts} +1 -1
- package/dist/{output-parser.js → shell/output-parser.js} +1 -1
- package/dist/{shell.d.ts → shell/shell.d.ts} +8 -2
- package/dist/{shell.js → shell/shell.js} +39 -8
- package/dist/types.d.ts +61 -10
- package/dist/utils/ansi.d.ts +5 -0
- package/dist/utils/ansi.js +1 -1
- package/dist/utils/compositor.d.ts +67 -0
- package/dist/utils/compositor.js +116 -0
- package/dist/utils/diff-renderer.d.ts +9 -0
- package/dist/utils/diff-renderer.js +312 -146
- package/dist/utils/diff.d.ts +21 -2
- package/dist/utils/diff.js +165 -89
- package/dist/utils/floating-panel.d.ts +2 -0
- package/dist/utils/floating-panel.js +30 -14
- package/dist/utils/handler-registry.d.ts +31 -10
- package/dist/utils/handler-registry.js +58 -16
- package/dist/utils/line-editor.d.ts +33 -3
- package/dist/utils/line-editor.js +221 -44
- package/dist/utils/markdown.d.ts +1 -0
- package/dist/utils/markdown.js +1 -1
- package/dist/utils/message-utils.d.ts +35 -0
- package/dist/utils/message-utils.js +75 -0
- package/dist/utils/terminal-buffer.d.ts +5 -1
- package/dist/utils/terminal-buffer.js +18 -2
- package/dist/utils/tool-display.d.ts +1 -1
- package/dist/utils/tool-display.js +4 -4
- package/dist/utils/tool-interactive.d.ts +12 -0
- package/dist/utils/tool-interactive.js +53 -0
- package/examples/extensions/ash-acp-bridge/README.md +39 -0
- package/examples/extensions/ash-acp-bridge/package.json +23 -0
- package/examples/extensions/ash-acp-bridge/src/index.ts +574 -0
- package/examples/extensions/ash-acp-bridge/tsconfig.json +14 -0
- package/examples/extensions/ash-mcp-bridge/README.md +72 -0
- package/examples/extensions/ash-mcp-bridge/index.ts +164 -0
- package/examples/extensions/ash-mcp-bridge/package.json +9 -0
- package/examples/extensions/claude-code-bridge/index.ts +198 -51
- package/examples/extensions/claude-code-bridge/package.json +1 -0
- package/examples/extensions/interactive-prompts.ts +98 -112
- package/examples/extensions/overlay-agent.ts +84 -38
- package/examples/extensions/peer-mesh.ts +565 -0
- package/examples/extensions/pi-bridge/index.ts +2 -2
- package/examples/extensions/questionnaire.ts +260 -0
- package/examples/extensions/subagents.ts +19 -4
- package/examples/extensions/terminal-buffer.ts +32 -53
- package/examples/extensions/tmux-pane.ts +307 -0
- package/examples/extensions/user-shell.ts +136 -0
- package/examples/extensions/web-access.ts +335 -0
- package/package.json +44 -2
- package/dist/agent/tools/display.d.ts +0 -13
- package/dist/agent/tools/display.js +0 -70
- package/dist/agent/tools/user-shell.d.ts +0 -13
- package/dist/agent/tools/user-shell.js +0 -87
- package/dist/extensions/overlay-agent.d.ts +0 -14
- package/dist/extensions/overlay-agent.js +0 -147
- package/dist/extensions/terminal-buffer.d.ts +0 -14
- package/dist/extensions/terminal-buffer.js +0 -125
|
@@ -4,11 +4,12 @@ import * as path from "path";
|
|
|
4
4
|
import * as pty from "node-pty";
|
|
5
5
|
import { InputHandler } from "./input-handler.js";
|
|
6
6
|
import { OutputParser } from "./output-parser.js";
|
|
7
|
-
import { getSettings } from "
|
|
8
|
-
import { RefCounter } from "
|
|
7
|
+
import { getSettings } from "../settings.js";
|
|
8
|
+
import { RefCounter } from "../utils/output-writer.js";
|
|
9
9
|
export class Shell {
|
|
10
10
|
ptyProcess;
|
|
11
11
|
bus;
|
|
12
|
+
handlers;
|
|
12
13
|
inputHandler;
|
|
13
14
|
outputParser;
|
|
14
15
|
paused = false;
|
|
@@ -139,6 +140,7 @@ export class Shell {
|
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
142
|
this.bus = opts.bus;
|
|
143
|
+
this.handlers = opts.handlers;
|
|
142
144
|
this.outputParser = new OutputParser(opts.bus, opts.cwd);
|
|
143
145
|
// Ensure temp dir cleanup on abnormal exit (SIGKILL won't fire this,
|
|
144
146
|
// but it covers uncaught exceptions and normal process.exit paths)
|
|
@@ -195,6 +197,11 @@ export class Shell {
|
|
|
195
197
|
* For bash, falls back to sending \n for a fresh prompt cycle.
|
|
196
198
|
*/
|
|
197
199
|
redrawPrompt() {
|
|
200
|
+
// A stale echoSkip or paused flag (left over from handleProcessingDone
|
|
201
|
+
// re-entering a mode) would swallow the redrawn prompt and make the
|
|
202
|
+
// terminal appear frozen. Reset both before emitting.
|
|
203
|
+
this.echoSkip = false;
|
|
204
|
+
this.paused = false;
|
|
198
205
|
const result = this.bus.emitPipe("shell:redraw-prompt", {
|
|
199
206
|
cwd: this.outputParser.getCwd(),
|
|
200
207
|
handled: false,
|
|
@@ -225,7 +232,9 @@ export class Shell {
|
|
|
225
232
|
});
|
|
226
233
|
if (!result.handled) {
|
|
227
234
|
this.ptyProcess.write("\n");
|
|
235
|
+
return true;
|
|
228
236
|
}
|
|
237
|
+
return false;
|
|
229
238
|
}
|
|
230
239
|
onCommandEntered(command, cwd) {
|
|
231
240
|
this.outputParser.onCommandEntered(command, cwd);
|
|
@@ -265,18 +274,32 @@ export class Shell {
|
|
|
265
274
|
* zero frontend knowledge; any frontend can subscribe to the same events.
|
|
266
275
|
*/
|
|
267
276
|
setupAgentLifecycle() {
|
|
268
|
-
|
|
277
|
+
// Default agent lifecycle: pause the shell while the agent works,
|
|
278
|
+
// then redraw the prompt when done. Extensions advise these handlers
|
|
279
|
+
// to change behavior (e.g. tmux split keeps the shell interactive).
|
|
280
|
+
this.handlers.define("shell:on-processing-start", () => {
|
|
269
281
|
this.agentActive = true;
|
|
270
282
|
this.paused = true;
|
|
271
283
|
});
|
|
272
|
-
this.
|
|
273
|
-
this.paused = false;
|
|
284
|
+
this.handlers.define("shell:on-processing-done", () => {
|
|
274
285
|
this.agentActive = false;
|
|
275
|
-
|
|
286
|
+
// If handleProcessingDone re-entered a mode, leave stdout paused so
|
|
287
|
+
// stale PTY output doesn't overwrite the mode prompt (exitMode →
|
|
288
|
+
// redrawPrompt will unpause). Setting echoSkip here would swallow
|
|
289
|
+
// that PTY output since no \n was sent.
|
|
276
290
|
if (!this.inputHandler.handleProcessingDone()) {
|
|
277
|
-
this.
|
|
291
|
+
this.paused = false;
|
|
292
|
+
if (this.freshPrompt()) {
|
|
293
|
+
this.echoSkip = true;
|
|
294
|
+
}
|
|
278
295
|
}
|
|
279
296
|
});
|
|
297
|
+
this.bus.on("agent:processing-start", () => {
|
|
298
|
+
this.handlers.call("shell:on-processing-start");
|
|
299
|
+
});
|
|
300
|
+
this.bus.on("agent:processing-done", () => {
|
|
301
|
+
this.handlers.call("shell:on-processing-done");
|
|
302
|
+
});
|
|
280
303
|
// Permission prompts need stdout unpaused so the interactive UI renders,
|
|
281
304
|
// then re-paused after the decision.
|
|
282
305
|
this.bus.on("permission:request", () => {
|
|
@@ -303,11 +326,19 @@ export class Shell {
|
|
|
303
326
|
const handler = (e) => {
|
|
304
327
|
clearTimeout(timeout);
|
|
305
328
|
this.bus.off("shell:command-done", handler);
|
|
329
|
+
// Re-pause stdout so the prompt text following the marker doesn't
|
|
330
|
+
// leak to the terminal while the agent is still processing.
|
|
331
|
+
this.paused = true;
|
|
306
332
|
resolve({ output: e.output, cwd: e.cwd, exitCode: e.exitCode });
|
|
307
333
|
};
|
|
308
334
|
this.bus.on("shell:command-done", handler);
|
|
309
335
|
this.outputParser.onCommandEntered(payload.command, this.outputParser.getCwd());
|
|
310
|
-
|
|
336
|
+
// Collapse literal newlines to spaces so the PTY receives a single-line
|
|
337
|
+
// command. Multi-line commands (e.g. git commit -m "...\n...") would
|
|
338
|
+
// cause the shell to execute prematurely, producing garbled output from
|
|
339
|
+
// syntax highlighting plugins (zsh syntax highlighting, etc).
|
|
340
|
+
const oneLine = payload.command.replace(/\n/g, " ");
|
|
341
|
+
this.ptyProcess.write(oneLine + "\r");
|
|
311
342
|
});
|
|
312
343
|
this.paused = true;
|
|
313
344
|
this.echoSkip = false;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,13 +1,37 @@
|
|
|
1
1
|
import type { EventBus } from "./event-bus.js";
|
|
2
2
|
import type { ContextManager } from "./context-manager.js";
|
|
3
|
-
import type { LlmClient } from "./utils/llm-client.js";
|
|
4
3
|
import type { ColorPalette } from "./utils/palette.js";
|
|
5
4
|
import type { BlockTransformOptions, FencedBlockTransformOptions } from "./utils/stream-transform.js";
|
|
6
5
|
import type { ToolDefinition } from "./agent/types.js";
|
|
7
6
|
import type { TerminalBuffer } from "./utils/terminal-buffer.js";
|
|
8
|
-
import type {
|
|
7
|
+
import type { Compositor } from "./utils/compositor.js";
|
|
9
8
|
export type { ContentBlock } from "./event-bus.js";
|
|
10
9
|
export type { BlockTransformOptions, FencedBlockTransformOptions } from "./utils/stream-transform.js";
|
|
10
|
+
export type { RenderSurface } from "./utils/compositor.js";
|
|
11
|
+
export interface RemoteSessionOptions {
|
|
12
|
+
/** The surface to render agent output to. */
|
|
13
|
+
surface: import("./utils/compositor.js").RenderSurface;
|
|
14
|
+
/** Suppress response borders (default: true). */
|
|
15
|
+
suppressBorders?: boolean;
|
|
16
|
+
/** Suppress user query box (default: false).
|
|
17
|
+
* True for sessions with their own input (rsplit, overlay).
|
|
18
|
+
* False for sessions where input comes from the main shell (split). */
|
|
19
|
+
suppressQueryBox?: boolean;
|
|
20
|
+
/** Suppress usage stats line (default: true). */
|
|
21
|
+
suppressUsage?: boolean;
|
|
22
|
+
/** Set interactive-session dynamic context (default: false). */
|
|
23
|
+
interactive?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface RemoteSession {
|
|
26
|
+
/** Submit a query to the agent from this session. */
|
|
27
|
+
submit(query: string): void;
|
|
28
|
+
/** The surface this session renders to. */
|
|
29
|
+
readonly surface: import("./utils/compositor.js").RenderSurface;
|
|
30
|
+
/** Whether this session is currently active. */
|
|
31
|
+
readonly active: boolean;
|
|
32
|
+
/** Tear down — restores all routing and advisors. */
|
|
33
|
+
close(): void;
|
|
34
|
+
}
|
|
11
35
|
/** A model entry in the cycling list, optionally tied to a provider. */
|
|
12
36
|
export interface AgentMode {
|
|
13
37
|
model: string;
|
|
@@ -45,8 +69,8 @@ export interface AgentShellConfig {
|
|
|
45
69
|
export interface ExtensionContext {
|
|
46
70
|
bus: EventBus;
|
|
47
71
|
contextManager: ContextManager;
|
|
48
|
-
/**
|
|
49
|
-
|
|
72
|
+
/** Stable per-instance identifier (4-char hex). */
|
|
73
|
+
readonly instanceId: string;
|
|
50
74
|
quit: () => void;
|
|
51
75
|
/** Override color palette slots for theming. */
|
|
52
76
|
setPalette: (overrides: Partial<ColorPalette>) => void;
|
|
@@ -56,29 +80,56 @@ export interface ExtensionContext {
|
|
|
56
80
|
createFencedBlockTransform: (opts: FencedBlockTransformOptions) => void;
|
|
57
81
|
/** Read extension-namespaced settings from ~/.agent-sh/settings.json. */
|
|
58
82
|
getExtensionSettings: <T extends Record<string, unknown>>(namespace: string, defaults: T) => T;
|
|
83
|
+
/**
|
|
84
|
+
* Get (and lazily create) a per-extension storage directory under
|
|
85
|
+
* ~/.agent-sh/<namespace>/. Returns the absolute path. Lets extensions
|
|
86
|
+
* persist state without each one re-deriving the location.
|
|
87
|
+
*/
|
|
88
|
+
getStoragePath: (namespace: string) => string;
|
|
59
89
|
/** Register a slash command available in any input mode. */
|
|
60
90
|
registerCommand: (name: string, description: string, handler: (args: string) => Promise<void> | void) => void;
|
|
61
91
|
/** Register a tool for the built-in agent. No-op when using bridge backends. */
|
|
62
92
|
registerTool: (tool: ToolDefinition) => void;
|
|
93
|
+
/** Unregister a tool by name. */
|
|
94
|
+
unregisterTool: (name: string) => void;
|
|
63
95
|
/** Get all registered tools (for subagent tool subsets). Returns [] when using bridge backends. */
|
|
64
96
|
getTools: () => ToolDefinition[];
|
|
97
|
+
/** Register a named instruction block for the agent's system prompt. */
|
|
98
|
+
registerInstruction: (name: string, text: string) => void;
|
|
99
|
+
/** Remove a named instruction block from the system prompt. */
|
|
100
|
+
removeInstruction: (name: string) => void;
|
|
101
|
+
/** Register a skill (on-demand reference material) for the agent. */
|
|
102
|
+
registerSkill: (name: string, description: string, filePath: string) => void;
|
|
103
|
+
/** Remove a registered skill by name. */
|
|
104
|
+
removeSkill: (name: string) => void;
|
|
65
105
|
/** Register a named handler. */
|
|
66
106
|
define: (name: string, fn: (...args: any[]) => any) => void;
|
|
67
|
-
/** Wrap a named handler. Receives `next` (original) + args. */
|
|
68
|
-
advise: (name: string, wrapper: (next: (...args: any[]) => any, ...args: any[]) => any) => void;
|
|
107
|
+
/** Wrap a named handler. Receives `next` (original) + args. Returns an unadvise function. */
|
|
108
|
+
advise: (name: string, wrapper: (next: (...args: any[]) => any, ...args: any[]) => any) => () => void;
|
|
69
109
|
/** Call a named handler. */
|
|
70
110
|
call: (name: string, ...args: any[]) => any;
|
|
111
|
+
/** Names of all registered handlers — for diagnostic / introspection use. */
|
|
112
|
+
list: () => string[];
|
|
71
113
|
/**
|
|
72
114
|
* Shared headless terminal buffer mirroring PTY output.
|
|
73
115
|
* Lazily created on first access. Returns null if @xterm/headless is not installed.
|
|
74
116
|
*/
|
|
75
117
|
terminalBuffer: TerminalBuffer | null;
|
|
76
118
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
|
|
119
|
+
* Routes named render streams ("agent", "query", "status") to surfaces.
|
|
120
|
+
* Extensions use `compositor.redirect()` to capture output (e.g. overlay panels).
|
|
121
|
+
*/
|
|
122
|
+
compositor: Compositor;
|
|
123
|
+
/**
|
|
124
|
+
* Create a remote session that routes agent output to a surface and
|
|
125
|
+
* optionally accepts queries. Handles all compositor routing, shell
|
|
126
|
+
* lifecycle advisors, and chrome suppression.
|
|
127
|
+
*
|
|
128
|
+
* const session = ctx.createRemoteSession({ surface, interactive: true });
|
|
129
|
+
* session.submit("what's on screen?");
|
|
130
|
+
* session.close(); // restores everything
|
|
80
131
|
*/
|
|
81
|
-
|
|
132
|
+
createRemoteSession: (opts: RemoteSessionOptions) => RemoteSession;
|
|
82
133
|
}
|
|
83
134
|
/**
|
|
84
135
|
* Configuration for a registered input mode.
|
package/dist/utils/ansi.d.ts
CHANGED
|
@@ -6,6 +6,11 @@ export declare const RED = "\u001B[31m";
|
|
|
6
6
|
export declare const GRAY = "\u001B[90m";
|
|
7
7
|
export declare const BOLD = "\u001B[1m";
|
|
8
8
|
export declare const RESET = "\u001B[0m";
|
|
9
|
+
/**
|
|
10
|
+
* Check if a Unicode code point is a wide character (CJK, fullwidth, emoji, etc.)
|
|
11
|
+
* Returns 2 for wide chars, 1 for normal chars.
|
|
12
|
+
*/
|
|
13
|
+
export declare function charWidth(codePoint: number): number;
|
|
9
14
|
/**
|
|
10
15
|
* Measure visible string length in terminal columns.
|
|
11
16
|
* Excludes SGR (color/style) sequences and accounts for CJK double-width chars.
|
package/dist/utils/ansi.js
CHANGED
|
@@ -12,7 +12,7 @@ export const RESET = "\x1b[0m";
|
|
|
12
12
|
* Check if a Unicode code point is a wide character (CJK, fullwidth, emoji, etc.)
|
|
13
13
|
* Returns 2 for wide chars, 1 for normal chars.
|
|
14
14
|
*/
|
|
15
|
-
function charWidth(codePoint) {
|
|
15
|
+
export function charWidth(codePoint) {
|
|
16
16
|
// CJK Unified Ideographs
|
|
17
17
|
if (codePoint >= 0x4e00 && codePoint <= 0x9fff)
|
|
18
18
|
return 2;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compositor — routes named render streams to surfaces.
|
|
3
|
+
*
|
|
4
|
+
* Components write to named streams ("agent", "query", "status").
|
|
5
|
+
* The compositor decides where each stream actually goes based on
|
|
6
|
+
* the current routing table. Extensions override routing with
|
|
7
|
+
* `redirect()` to capture output (e.g. overlay panels).
|
|
8
|
+
*
|
|
9
|
+
* Streams are hierarchical: "agent:diff" falls back to "agent" if
|
|
10
|
+
* no override or default is registered for "agent:diff" specifically.
|
|
11
|
+
* This enables fine-grained interception — redirect just diffs into
|
|
12
|
+
* a panel, or just a subagent's output ("agent:sub:abc123"), while
|
|
13
|
+
* everything else flows to the parent stream's surface.
|
|
14
|
+
*
|
|
15
|
+
* // tui-renderer registers default surfaces
|
|
16
|
+
* compositor.setDefault("agent", stdoutSurface);
|
|
17
|
+
*
|
|
18
|
+
* // overlay-agent redirects when active
|
|
19
|
+
* const restore = compositor.redirect("agent", panelSurface);
|
|
20
|
+
* // ... later ...
|
|
21
|
+
* restore(); // back to stdout
|
|
22
|
+
*
|
|
23
|
+
* // fine-grained: redirect only diffs to a viewer panel
|
|
24
|
+
* compositor.redirect("agent:diff", diffPanelSurface);
|
|
25
|
+
* // "agent:text", "agent:tool" etc. still go to stdout
|
|
26
|
+
*/
|
|
27
|
+
import type { EventBus } from "../event-bus.js";
|
|
28
|
+
/**
|
|
29
|
+
* A surface accepts rendered output. Stdout is a surface.
|
|
30
|
+
* A floating panel's content area is a surface. A test buffer is a surface.
|
|
31
|
+
*/
|
|
32
|
+
export interface RenderSurface {
|
|
33
|
+
/** Raw write — supports \r, partial lines, escape codes. */
|
|
34
|
+
write(text: string): void;
|
|
35
|
+
/** Convenience: write + newline. */
|
|
36
|
+
writeLine(line: string): void;
|
|
37
|
+
/** Available width in columns. */
|
|
38
|
+
readonly columns: number;
|
|
39
|
+
}
|
|
40
|
+
export interface Compositor {
|
|
41
|
+
/** Get the currently active surface for a stream. */
|
|
42
|
+
surface(stream: string): RenderSurface;
|
|
43
|
+
/** Override routing: redirect a stream to a different surface.
|
|
44
|
+
* Returns a restore function that undoes the redirect. */
|
|
45
|
+
redirect(stream: string, target: RenderSurface): () => void;
|
|
46
|
+
/** Register the default surface for a stream. */
|
|
47
|
+
setDefault(stream: string, target: RenderSurface): void;
|
|
48
|
+
}
|
|
49
|
+
/** Silent sink — drops all output. Used when no surface is registered. */
|
|
50
|
+
export declare const nullSurface: RenderSurface;
|
|
51
|
+
/** Surface backed by process.stdout. */
|
|
52
|
+
export declare class StdoutSurface implements RenderSurface {
|
|
53
|
+
write(text: string): void;
|
|
54
|
+
writeLine(line: string): void;
|
|
55
|
+
get columns(): number;
|
|
56
|
+
}
|
|
57
|
+
export declare class DefaultCompositor implements Compositor {
|
|
58
|
+
private defaults;
|
|
59
|
+
private overrides;
|
|
60
|
+
private readonly bus?;
|
|
61
|
+
constructor(bus?: EventBus);
|
|
62
|
+
surface(stream: string): RenderSurface;
|
|
63
|
+
redirect(stream: string, target: RenderSurface): () => void;
|
|
64
|
+
setDefault(stream: string, target: RenderSurface): void;
|
|
65
|
+
/** Wrap a surface so writes emit `compositor:write` before delegating. */
|
|
66
|
+
private wrap;
|
|
67
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compositor — routes named render streams to surfaces.
|
|
3
|
+
*
|
|
4
|
+
* Components write to named streams ("agent", "query", "status").
|
|
5
|
+
* The compositor decides where each stream actually goes based on
|
|
6
|
+
* the current routing table. Extensions override routing with
|
|
7
|
+
* `redirect()` to capture output (e.g. overlay panels).
|
|
8
|
+
*
|
|
9
|
+
* Streams are hierarchical: "agent:diff" falls back to "agent" if
|
|
10
|
+
* no override or default is registered for "agent:diff" specifically.
|
|
11
|
+
* This enables fine-grained interception — redirect just diffs into
|
|
12
|
+
* a panel, or just a subagent's output ("agent:sub:abc123"), while
|
|
13
|
+
* everything else flows to the parent stream's surface.
|
|
14
|
+
*
|
|
15
|
+
* // tui-renderer registers default surfaces
|
|
16
|
+
* compositor.setDefault("agent", stdoutSurface);
|
|
17
|
+
*
|
|
18
|
+
* // overlay-agent redirects when active
|
|
19
|
+
* const restore = compositor.redirect("agent", panelSurface);
|
|
20
|
+
* // ... later ...
|
|
21
|
+
* restore(); // back to stdout
|
|
22
|
+
*
|
|
23
|
+
* // fine-grained: redirect only diffs to a viewer panel
|
|
24
|
+
* compositor.redirect("agent:diff", diffPanelSurface);
|
|
25
|
+
* // "agent:text", "agent:tool" etc. still go to stdout
|
|
26
|
+
*/
|
|
27
|
+
/** Silent sink — drops all output. Used when no surface is registered. */
|
|
28
|
+
export const nullSurface = {
|
|
29
|
+
write() { },
|
|
30
|
+
writeLine() { },
|
|
31
|
+
get columns() { return 80; },
|
|
32
|
+
};
|
|
33
|
+
/** Surface backed by process.stdout. */
|
|
34
|
+
export class StdoutSurface {
|
|
35
|
+
write(text) {
|
|
36
|
+
if (process.stdout.writable) {
|
|
37
|
+
try {
|
|
38
|
+
process.stdout.write(text);
|
|
39
|
+
}
|
|
40
|
+
catch { /* ignore */ }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
writeLine(line) {
|
|
44
|
+
this.write(line + "\n");
|
|
45
|
+
}
|
|
46
|
+
get columns() {
|
|
47
|
+
return process.stdout.columns || 80;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export class DefaultCompositor {
|
|
51
|
+
defaults = new Map();
|
|
52
|
+
overrides = new Map();
|
|
53
|
+
bus;
|
|
54
|
+
constructor(bus) {
|
|
55
|
+
this.bus = bus;
|
|
56
|
+
}
|
|
57
|
+
surface(stream) {
|
|
58
|
+
const stack = this.overrides.get(stream);
|
|
59
|
+
if (stack && stack.length > 0)
|
|
60
|
+
return stack[stack.length - 1];
|
|
61
|
+
if (this.defaults.has(stream))
|
|
62
|
+
return this.defaults.get(stream);
|
|
63
|
+
// Hierarchical fallback: "agent:diff" → "agent"
|
|
64
|
+
const colon = stream.lastIndexOf(":");
|
|
65
|
+
if (colon !== -1)
|
|
66
|
+
return this.surface(stream.slice(0, colon));
|
|
67
|
+
return nullSurface;
|
|
68
|
+
}
|
|
69
|
+
redirect(stream, target) {
|
|
70
|
+
const wrapped = this.wrap(stream, target);
|
|
71
|
+
let stack = this.overrides.get(stream);
|
|
72
|
+
if (!stack) {
|
|
73
|
+
stack = [];
|
|
74
|
+
this.overrides.set(stream, stack);
|
|
75
|
+
}
|
|
76
|
+
stack.push(wrapped);
|
|
77
|
+
let restored = false;
|
|
78
|
+
return () => {
|
|
79
|
+
if (restored)
|
|
80
|
+
return;
|
|
81
|
+
restored = true;
|
|
82
|
+
const s = this.overrides.get(stream);
|
|
83
|
+
if (!s)
|
|
84
|
+
return;
|
|
85
|
+
const idx = s.indexOf(wrapped);
|
|
86
|
+
if (idx !== -1)
|
|
87
|
+
s.splice(idx, 1);
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
setDefault(stream, target) {
|
|
91
|
+
this.defaults.set(stream, this.wrap(stream, target));
|
|
92
|
+
}
|
|
93
|
+
/** Wrap a surface so writes emit `compositor:write` before delegating. */
|
|
94
|
+
wrap(stream, target) {
|
|
95
|
+
const bus = this.bus;
|
|
96
|
+
if (!bus)
|
|
97
|
+
return target;
|
|
98
|
+
return {
|
|
99
|
+
write: (text) => {
|
|
100
|
+
try {
|
|
101
|
+
bus.emit("compositor:write", { stream, text });
|
|
102
|
+
}
|
|
103
|
+
catch { }
|
|
104
|
+
target.write(text);
|
|
105
|
+
},
|
|
106
|
+
writeLine: (line) => {
|
|
107
|
+
try {
|
|
108
|
+
bus.emit("compositor:write", { stream, text: line + "\n" });
|
|
109
|
+
}
|
|
110
|
+
catch { }
|
|
111
|
+
target.writeLine(line);
|
|
112
|
+
},
|
|
113
|
+
get columns() { return target.columns; },
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -18,3 +18,12 @@ export interface DiffRenderOptions {
|
|
|
18
18
|
export declare function selectMode(width: number): DiffDisplayMode;
|
|
19
19
|
/** Render a diff result as an array of ANSI-formatted terminal lines. */
|
|
20
20
|
export declare function renderDiff(diff: DiffResult, opts: DiffRenderOptions): string[];
|
|
21
|
+
/**
|
|
22
|
+
* Async variant of renderDiff that yields to the event loop between hunks.
|
|
23
|
+
* Use when rendering in a context where a spinner or other UI needs to stay
|
|
24
|
+
* responsive (e.g. showing a large diff during a permission prompt).
|
|
25
|
+
*
|
|
26
|
+
* @param onLines - Callback invoked with each batch of rendered lines as they
|
|
27
|
+
* are produced. Allows progressive/streaming display.
|
|
28
|
+
*/
|
|
29
|
+
export declare function renderDiffAsync(diff: DiffResult, opts: DiffRenderOptions, onLines: (lines: string[]) => void): Promise<void>;
|