agent-sh 0.4.0 → 0.6.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.
Files changed (83) hide show
  1. package/README.md +37 -115
  2. package/dist/agent/agent-loop.d.ts +86 -0
  3. package/dist/agent/agent-loop.js +704 -0
  4. package/dist/agent/conversation-state.d.ts +27 -0
  5. package/dist/agent/conversation-state.js +59 -0
  6. package/dist/agent/index.d.ts +11 -0
  7. package/dist/agent/index.js +9 -0
  8. package/dist/agent/skills.d.ts +25 -0
  9. package/dist/agent/skills.js +186 -0
  10. package/dist/agent/subagent.d.ts +37 -0
  11. package/dist/agent/subagent.js +119 -0
  12. package/dist/agent/system-prompt.d.ts +14 -0
  13. package/dist/agent/system-prompt.js +103 -0
  14. package/dist/agent/tool-registry.d.ts +15 -0
  15. package/dist/agent/tool-registry.js +30 -0
  16. package/dist/agent/tools/bash.d.ts +7 -0
  17. package/dist/agent/tools/bash.js +71 -0
  18. package/dist/agent/tools/display.d.ts +13 -0
  19. package/dist/agent/tools/display.js +70 -0
  20. package/dist/agent/tools/edit-file.d.ts +2 -0
  21. package/dist/agent/tools/edit-file.js +148 -0
  22. package/dist/agent/tools/glob.d.ts +2 -0
  23. package/dist/agent/tools/glob.js +87 -0
  24. package/dist/agent/tools/grep.d.ts +2 -0
  25. package/dist/agent/tools/grep.js +168 -0
  26. package/dist/agent/tools/list-skills.d.ts +2 -0
  27. package/dist/agent/tools/list-skills.js +28 -0
  28. package/dist/agent/tools/ls.d.ts +2 -0
  29. package/dist/agent/tools/ls.js +72 -0
  30. package/dist/agent/tools/read-file.d.ts +10 -0
  31. package/dist/agent/tools/read-file.js +101 -0
  32. package/dist/agent/tools/user-shell.d.ts +13 -0
  33. package/dist/agent/tools/user-shell.js +84 -0
  34. package/dist/agent/tools/write-file.d.ts +2 -0
  35. package/dist/agent/tools/write-file.js +82 -0
  36. package/dist/agent/types.d.ts +78 -0
  37. package/dist/agent/types.js +1 -0
  38. package/dist/core.d.ts +22 -14
  39. package/dist/core.js +256 -36
  40. package/dist/event-bus.d.ts +98 -17
  41. package/dist/event-bus.js +10 -1
  42. package/dist/extension-loader.d.ts +1 -1
  43. package/dist/extension-loader.js +10 -1
  44. package/dist/extensions/command-suggest.d.ts +10 -0
  45. package/dist/extensions/command-suggest.js +41 -0
  46. package/dist/extensions/slash-commands.d.ts +1 -1
  47. package/dist/extensions/slash-commands.js +161 -64
  48. package/dist/extensions/tui-renderer.js +426 -126
  49. package/dist/index.js +110 -129
  50. package/dist/input-handler.js +78 -9
  51. package/dist/output-parser.d.ts +7 -0
  52. package/dist/output-parser.js +27 -0
  53. package/dist/settings.d.ts +53 -2
  54. package/dist/settings.js +46 -3
  55. package/dist/shell.js +35 -28
  56. package/dist/types.d.ts +33 -6
  57. package/dist/utils/box-frame.d.ts +3 -1
  58. package/dist/utils/box-frame.js +12 -5
  59. package/dist/utils/diff.js +10 -0
  60. package/dist/utils/llm-client.d.ts +45 -0
  61. package/dist/utils/llm-client.js +60 -0
  62. package/dist/utils/markdown.d.ts +1 -0
  63. package/dist/utils/markdown.js +25 -3
  64. package/dist/utils/stream-transform.js +20 -47
  65. package/dist/utils/tool-display.d.ts +4 -0
  66. package/dist/utils/tool-display.js +35 -8
  67. package/examples/extensions/claude-code-bridge/README.md +35 -0
  68. package/examples/extensions/claude-code-bridge/index.ts +194 -0
  69. package/examples/extensions/claude-code-bridge/package.json +11 -0
  70. package/examples/extensions/openrouter.ts +87 -0
  71. package/examples/extensions/pi-bridge/README.md +35 -0
  72. package/examples/extensions/pi-bridge/index.ts +263 -0
  73. package/examples/extensions/pi-bridge/package.json +13 -0
  74. package/examples/extensions/secret-guard.ts +100 -0
  75. package/examples/extensions/subagents.ts +87 -0
  76. package/package.json +3 -5
  77. package/dist/acp-client.d.ts +0 -105
  78. package/dist/acp-client.js +0 -684
  79. package/dist/extensions/shell-exec.d.ts +0 -24
  80. package/dist/extensions/shell-exec.js +0 -188
  81. package/dist/mcp-server.d.ts +0 -13
  82. package/dist/mcp-server.js +0 -234
  83. package/examples/pi-agent-sh.ts +0 -166
@@ -0,0 +1,72 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ function formatSize(bytes) {
4
+ if (bytes < 1024)
5
+ return `${bytes}B`;
6
+ if (bytes < 1024 * 1024)
7
+ return `${(bytes / 1024).toFixed(1)}K`;
8
+ if (bytes < 1024 * 1024 * 1024)
9
+ return `${(bytes / (1024 * 1024)).toFixed(1)}M`;
10
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}G`;
11
+ }
12
+ export function createLsTool(getCwd) {
13
+ return {
14
+ name: "ls",
15
+ description: "List files and directories with timestamps and sizes. " +
16
+ "Use for exploring a single directory. Use glob for recursive file search by pattern.",
17
+ input_schema: {
18
+ type: "object",
19
+ properties: {
20
+ path: {
21
+ type: "string",
22
+ description: "Directory to list (default: cwd)",
23
+ },
24
+ },
25
+ },
26
+ showOutput: false,
27
+ getDisplayInfo: (args) => ({
28
+ kind: "read",
29
+ icon: "◆",
30
+ locations: args.path
31
+ ? [{ path: args.path }]
32
+ : [],
33
+ }),
34
+ formatResult: (_args, result) => {
35
+ if (result.isError || result.content === "(empty directory)")
36
+ return { summary: "0 entries" };
37
+ const lines = result.content.split("\n").filter(Boolean);
38
+ return { summary: `${lines.length} entries` };
39
+ },
40
+ async execute(args) {
41
+ const dirPath = args.path ?? ".";
42
+ const absPath = path.resolve(getCwd(), dirPath);
43
+ try {
44
+ const entries = await fs.readdir(absPath, {
45
+ withFileTypes: true,
46
+ });
47
+ const lines = [];
48
+ for (const e of entries) {
49
+ const fullPath = path.join(absPath, e.name);
50
+ try {
51
+ const stat = await fs.stat(fullPath);
52
+ const size = e.isDirectory() ? "-" : formatSize(stat.size);
53
+ const mtime = stat.mtime.toISOString().slice(0, 16).replace("T", " ");
54
+ lines.push(`${mtime} ${size.padStart(8)} ${e.isDirectory() ? e.name + "/" : e.name}`);
55
+ }
56
+ catch {
57
+ lines.push(`${"?".padStart(16)} ${"?".padStart(8)} ${e.isDirectory() ? e.name + "/" : e.name}`);
58
+ }
59
+ }
60
+ return {
61
+ content: lines.join("\n") || "(empty directory)",
62
+ exitCode: 0,
63
+ isError: false,
64
+ };
65
+ }
66
+ catch (err) {
67
+ const msg = err instanceof Error ? err.message : String(err);
68
+ return { content: `Error: ${msg}`, exitCode: 1, isError: true };
69
+ }
70
+ },
71
+ };
72
+ }
@@ -0,0 +1,10 @@
1
+ import type { ToolDefinition } from "../types.js";
2
+ /** Tracks the last-read state of a file for deduplication. */
3
+ export interface FileReadState {
4
+ mtimeMs: number;
5
+ offset: number;
6
+ limit: number | undefined;
7
+ }
8
+ /** Shared cache — keyed by absolute path. */
9
+ export type FileReadCache = Map<string, FileReadState>;
10
+ export declare function createReadFileTool(getCwd: () => string, cache?: FileReadCache): ToolDefinition;
@@ -0,0 +1,101 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ export function createReadFileTool(getCwd, cache) {
4
+ return {
5
+ name: "read_file",
6
+ description: "Read a file's contents with line numbers. Use offset and limit for large files. " +
7
+ "Always read a file before editing it. " +
8
+ "If the file hasn't changed since last read, returns a stub to save context.",
9
+ input_schema: {
10
+ type: "object",
11
+ properties: {
12
+ path: {
13
+ type: "string",
14
+ description: "Absolute or relative file path",
15
+ },
16
+ offset: {
17
+ type: "number",
18
+ description: "Starting line number (1-indexed)",
19
+ },
20
+ limit: {
21
+ type: "number",
22
+ description: "Max lines to read",
23
+ },
24
+ },
25
+ required: ["path"],
26
+ },
27
+ showOutput: false,
28
+ getDisplayInfo: (args) => ({
29
+ kind: "read",
30
+ icon: "◆",
31
+ locations: [{ path: args.path }],
32
+ }),
33
+ formatResult: (_args, result) => {
34
+ if (result.isError)
35
+ return {};
36
+ if (result.content.startsWith("File unchanged"))
37
+ return { summary: "cached" };
38
+ const lines = result.content.split("\n").filter(l => !l.startsWith("["));
39
+ return { summary: `${lines.length} lines` };
40
+ },
41
+ async execute(args) {
42
+ const filePath = args.path;
43
+ const absPath = path.resolve(getCwd(), filePath);
44
+ const reqOffset = args.offset ?? 1;
45
+ const reqLimit = args.limit;
46
+ try {
47
+ const stat = await fs.stat(absPath);
48
+ // Deduplication: if the file hasn't changed and same range, return stub
49
+ if (cache) {
50
+ const prev = cache.get(absPath);
51
+ if (prev &&
52
+ prev.mtimeMs === stat.mtimeMs &&
53
+ prev.offset === reqOffset &&
54
+ prev.limit === reqLimit) {
55
+ return {
56
+ content: "File unchanged since last read. The content from the earlier read_file result in this conversation is still current — refer to that instead of re-reading.",
57
+ exitCode: 0,
58
+ isError: false,
59
+ };
60
+ }
61
+ }
62
+ // Check file size before reading to avoid OOM on huge files
63
+ const MAX_FILE_SIZE = 2 * 1024 * 1024; // 2MB
64
+ if (stat.size > MAX_FILE_SIZE && !args.offset && !args.limit) {
65
+ const sizeMB = (stat.size / (1024 * 1024)).toFixed(1);
66
+ return {
67
+ content: `File is ${sizeMB}MB (${stat.size} bytes) — too large to read in full. Use offset and limit to read specific sections, e.g. offset=1 limit=200.`,
68
+ exitCode: 1,
69
+ isError: true,
70
+ };
71
+ }
72
+ const content = await fs.readFile(absPath, "utf-8");
73
+ const lines = content.split("\n");
74
+ const start = reqOffset - 1; // 1-indexed → 0-indexed
75
+ const end = reqLimit ? start + reqLimit : lines.length;
76
+ const slice = lines.slice(start, end);
77
+ // Add line numbers (1-indexed)
78
+ const numbered = slice
79
+ .map((line, i) => `${start + i + 1}\t${line}`)
80
+ .join("\n");
81
+ const truncated = end < lines.length;
82
+ const suffix = truncated
83
+ ? `\n[${lines.length - end} more lines, use offset=${end + 1} to continue]`
84
+ : "";
85
+ // Update cache on successful read
86
+ if (cache) {
87
+ cache.set(absPath, {
88
+ mtimeMs: stat.mtimeMs,
89
+ offset: reqOffset,
90
+ limit: reqLimit,
91
+ });
92
+ }
93
+ return { content: numbered + suffix, exitCode: 0, isError: false };
94
+ }
95
+ catch (err) {
96
+ const msg = err instanceof Error ? err.message : String(err);
97
+ return { content: `Error: ${msg}`, exitCode: 1, isError: true };
98
+ }
99
+ },
100
+ };
101
+ }
@@ -0,0 +1,13 @@
1
+ import type { EventBus } from "../../event-bus.js";
2
+ import type { ToolDefinition } from "../types.js";
3
+ /**
4
+ * user_shell — runs commands in the user's live PTY shell.
5
+ *
6
+ * Unlike bash, this affects the user's shell state (cd, export, source).
7
+ * Output is shown directly in the terminal. By default, the agent doesn't
8
+ * see the output (return_output=false) to save tokens.
9
+ */
10
+ export declare function createUserShellTool(opts: {
11
+ getCwd: () => string;
12
+ bus: EventBus;
13
+ }): ToolDefinition;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * user_shell — runs commands in the user's live PTY shell.
3
+ *
4
+ * Unlike bash, this affects the user's shell state (cd, export, source).
5
+ * Output is shown directly in the terminal. By default, the agent doesn't
6
+ * see the output (return_output=false) to save tokens.
7
+ */
8
+ export function createUserShellTool(opts) {
9
+ return {
10
+ name: "user_shell",
11
+ description: "Run a command with lasting effects in the user's live shell (cd, export, install packages, start servers, apply changes). Output is shown directly to the user but NOT returned to you by default — set return_output=true if you need to inspect the result.",
12
+ input_schema: {
13
+ type: "object",
14
+ properties: {
15
+ command: {
16
+ type: "string",
17
+ description: "Command to execute in user's shell",
18
+ },
19
+ timeout: {
20
+ type: "number",
21
+ description: "Timeout in seconds (default: 30)",
22
+ },
23
+ return_output: {
24
+ type: "boolean",
25
+ default: false,
26
+ description: "Whether to return the command output to you. Default false — output is shown directly to the user. Set true only if you need to inspect the result to answer a question.",
27
+ },
28
+ },
29
+ required: ["command"],
30
+ },
31
+ showOutput: false,
32
+ modifiesFiles: true,
33
+ getDisplayInfo: () => ({
34
+ kind: "execute",
35
+ icon: "▷",
36
+ locations: [],
37
+ }),
38
+ async execute(args) {
39
+ const command = args.command;
40
+ const timeoutSec = args.timeout ?? 30;
41
+ const returnOutput = args.return_output ?? false;
42
+ // Execute via the shell-exec extension's async pipe with timeout
43
+ let result;
44
+ try {
45
+ const execPromise = opts.bus.emitPipeAsync("shell:exec-request", {
46
+ command,
47
+ output: "",
48
+ cwd: opts.getCwd(),
49
+ exitCode: null,
50
+ done: false,
51
+ });
52
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), timeoutSec * 1000));
53
+ result = await Promise.race([execPromise, timeoutPromise]);
54
+ }
55
+ catch (err) {
56
+ const msg = err instanceof Error ? err.message : String(err);
57
+ if (msg === "timeout") {
58
+ return {
59
+ content: `Command timed out after ${timeoutSec}s.`,
60
+ exitCode: -1,
61
+ isError: true,
62
+ };
63
+ }
64
+ return { content: `Error: ${msg}`, exitCode: -1, isError: true };
65
+ }
66
+ const exitCode = result.exitCode ?? 0;
67
+ const isError = exitCode !== 0 && exitCode !== null;
68
+ if (returnOutput) {
69
+ return {
70
+ content: result.output || "(no output)",
71
+ exitCode,
72
+ isError,
73
+ };
74
+ }
75
+ return {
76
+ content: isError
77
+ ? `Command failed with exit code ${exitCode}.`
78
+ : "Command executed.",
79
+ exitCode,
80
+ isError,
81
+ };
82
+ },
83
+ };
84
+ }
@@ -0,0 +1,2 @@
1
+ import type { ToolDefinition } from "../types.js";
2
+ export declare function createWriteFileTool(getCwd: () => string): ToolDefinition;
@@ -0,0 +1,82 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import { computeDiff } from "../../utils/diff.js";
4
+ export function createWriteFileTool(getCwd) {
5
+ return {
6
+ name: "write_file",
7
+ description: "Create a new file or completely overwrite an existing one. Creates parent directories if needed. " +
8
+ "ALWAYS prefer edit_file for modifying existing files — only use write_file for new files or complete rewrites.",
9
+ input_schema: {
10
+ type: "object",
11
+ properties: {
12
+ path: {
13
+ type: "string",
14
+ description: "Absolute or relative file path",
15
+ },
16
+ content: {
17
+ type: "string",
18
+ description: "File content to write",
19
+ },
20
+ },
21
+ required: ["path", "content"],
22
+ },
23
+ showOutput: true,
24
+ modifiesFiles: true,
25
+ requiresPermission: true,
26
+ getDisplayInfo: (args) => ({
27
+ kind: "write",
28
+ icon: "✎",
29
+ locations: [{ path: args.path }],
30
+ }),
31
+ formatResult: (_args, result) => {
32
+ if (result.isError)
33
+ return {};
34
+ const m = result.content.match(/\((\+\d+(?:\s-\d+)?)\)/);
35
+ return m ? { summary: m[1] } : {};
36
+ },
37
+ async execute(args, onChunk) {
38
+ const filePath = args.path;
39
+ const content = args.content;
40
+ const absPath = path.resolve(getCwd(), filePath);
41
+ try {
42
+ let oldContent = null;
43
+ try {
44
+ oldContent = await fs.readFile(absPath, "utf-8");
45
+ }
46
+ catch {
47
+ // New file
48
+ }
49
+ await fs.mkdir(path.dirname(absPath), { recursive: true });
50
+ await fs.writeFile(absPath, content);
51
+ // Compute and stream diff for display
52
+ const diff = computeDiff(oldContent, content);
53
+ if (onChunk && diff.hunks.length > 0) {
54
+ for (const hunk of diff.hunks) {
55
+ for (const line of hunk.lines) {
56
+ if (line.type === "added")
57
+ onChunk(`+${line.text}\n`);
58
+ else if (line.type === "removed")
59
+ onChunk(`-${line.text}\n`);
60
+ else
61
+ onChunk(` ${line.text}\n`);
62
+ }
63
+ }
64
+ }
65
+ const stats = diff.isNewFile
66
+ ? `+${diff.added}`
67
+ : `+${diff.added} -${diff.removed}`;
68
+ return {
69
+ content: oldContent === null
70
+ ? `Created ${absPath} (${stats})`
71
+ : `Wrote ${absPath} (${stats})`,
72
+ exitCode: 0,
73
+ isError: false,
74
+ };
75
+ }
76
+ catch (err) {
77
+ const msg = err instanceof Error ? err.message : String(err);
78
+ return { content: `Error: ${msg}`, exitCode: 1, isError: true };
79
+ }
80
+ },
81
+ };
82
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Minimal agent backend interface — bus-driven.
3
+ *
4
+ * Backends self-wire to bus events in their constructor:
5
+ * - agent:submit → handle queries
6
+ * - agent:cancel-request → handle cancellation
7
+ * - config:cycle → handle mode switching
8
+ *
9
+ * They emit bus events for results:
10
+ * - agent:response-chunk, agent:tool-started, agent:tool-completed, etc.
11
+ *
12
+ * The only imperative method is kill() for lifecycle cleanup.
13
+ */
14
+ export interface AgentBackend {
15
+ /** Async startup (e.g. spawn subprocess). No-op if not needed. */
16
+ start?(): Promise<void>;
17
+ kill(): void;
18
+ }
19
+ export interface ToolResult {
20
+ content: string;
21
+ exitCode: number | null;
22
+ isError: boolean;
23
+ }
24
+ /** Structured result display — returned by formatResult or computed by defaults. */
25
+ export interface ToolResultDisplay {
26
+ /** One-line summary shown next to ✓/✗ (e.g. "42 papers found", "+3/-1"). */
27
+ summary?: string;
28
+ /** Structured content to render below the status line. */
29
+ body?: ToolResultBody;
30
+ }
31
+ export type ToolResultBody = {
32
+ kind: "diff";
33
+ diff: unknown;
34
+ filePath: string;
35
+ } | {
36
+ kind: "lines";
37
+ lines: string[];
38
+ maxLines?: number;
39
+ };
40
+ export interface ToolDisplayInfo {
41
+ kind: "read" | "write" | "execute" | "search" | "display";
42
+ locations?: {
43
+ path: string;
44
+ line?: number | null;
45
+ }[];
46
+ /** Custom icon character for TUI display (e.g., "◆", "⌕"). When set, the TUI shows
47
+ * icon + detail only. When absent, the tool name is shown alongside the detail. */
48
+ icon?: string;
49
+ }
50
+ export interface ToolDefinition {
51
+ name: string;
52
+ /** Short label for TUI display (e.g. "search" instead of "ads_search"). Defaults to name. */
53
+ displayName?: string;
54
+ description: string;
55
+ input_schema: Record<string, unknown>;
56
+ execute(args: Record<string, unknown>, onChunk?: (chunk: string) => void): Promise<ToolResult>;
57
+ /** Whether to stream tool output to the TUI (default: true). */
58
+ showOutput?: boolean;
59
+ /** Whether this tool may modify files — triggers file watcher (default: false). */
60
+ modifiesFiles?: boolean;
61
+ /** Whether to gate execution via permission:request (default: false). */
62
+ requiresPermission?: boolean;
63
+ /** Derive display metadata (icon kind, file paths) for the TUI. */
64
+ getDisplayInfo?: (args: Record<string, unknown>) => ToolDisplayInfo;
65
+ /**
66
+ * Format a short display string for the TUI when this tool is called.
67
+ * Return a concise summary of the args (e.g. the query, the file path).
68
+ * When absent, the TUI derives the detail from common arg fields (command, path, pattern).
69
+ */
70
+ formatCall?: (args: Record<string, unknown>) => string;
71
+ /**
72
+ * Format result display for the TUI after execution completes.
73
+ * Return a summary string and/or structured body to render.
74
+ * When absent, defaults are computed based on tool kind.
75
+ * Extensions can further override via bus.onPipe("agent:tool-completed", ...).
76
+ */
77
+ formatResult?: (args: Record<string, unknown>, result: ToolResult) => ToolResultDisplay;
78
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/core.d.ts CHANGED
@@ -1,41 +1,49 @@
1
1
  /**
2
2
  * Core kernel — the minimum viable agent-sh.
3
3
  *
4
- * Wires up EventBus + ContextManager + AcpClient without any frontend.
4
+ * Wires up EventBus + ContextManager + AgentBackend without any frontend.
5
5
  * Consumers attach their own I/O (Shell, WebSocket, REST, tests) by
6
- * subscribing to bus events and calling client methods.
6
+ * subscribing to bus events.
7
7
  *
8
- * The core listens for `agent:submit` and `agent:cancel-request` events
9
- * from any frontend, routing them to the AcpClient. This means frontends
10
- * never need a direct reference to AcpClient — they just emit events.
8
+ * The default backend (AgentLoop) is created eagerly but wired lazily —
9
+ * extensions can register alternative backends via agent:register-backend
10
+ * before activateBackend() is called.
11
11
  *
12
12
  * Usage:
13
13
  * import { createCore } from "agent-sh";
14
- * const core = createCore({ agentCommand: "pi-acp" });
15
- * core.bus.on("agent:response-chunk", ({ text }) => ws.send(text));
16
- * await core.start();
17
- * core.bus.emit("agent:submit", { query: "hello" });
14
+ * const core = createCore({ apiKey: "...", model: "gpt-4o" });
15
+ * core.bus.on("agent:response-chunk", ({ blocks }) => { ... });
16
+ * core.activateBackend();
17
+ * const response = await core.query("hello");
18
18
  */
19
19
  import { EventBus } from "./event-bus.js";
20
20
  import { ContextManager } from "./context-manager.js";
21
- import { AcpClient } from "./acp-client.js";
21
+ import { LlmClient } from "./utils/llm-client.js";
22
22
  import type { AgentShellConfig, ExtensionContext } from "./types.js";
23
23
  export { EventBus } from "./event-bus.js";
24
24
  export type { ShellEvents } from "./event-bus.js";
25
25
  export type { AgentShellConfig, ExtensionContext } from "./types.js";
26
26
  export { palette, setPalette, resetPalette } from "./utils/palette.js";
27
27
  export type { ColorPalette } from "./utils/palette.js";
28
+ export type { AgentBackend, ToolDefinition } from "./agent/types.js";
29
+ export { runSubagent, type SubagentOptions } from "./agent/subagent.js";
30
+ export { LlmClient } from "./utils/llm-client.js";
28
31
  export interface AgentShellCore {
29
32
  bus: EventBus;
30
33
  contextManager: ContextManager;
31
- client: AcpClient;
32
- /** Connect to the agent subprocess. Call after wiring up bus listeners. */
33
- start(): Promise<void>;
34
+ /** LLM client for fast-path features (null when no provider configured). */
35
+ llmClient: LlmClient | null;
36
+ /** Activate the agent backend (call after extensions load). */
37
+ activateBackend(): void;
38
+ /** Convenience: emit agent:submit and await the response. */
39
+ query(text: string): Promise<string>;
40
+ /** Convenience: emit agent:cancel-request. */
41
+ cancel(): void;
34
42
  /** Build an ExtensionContext for loading extensions against this core. */
35
43
  extensionContext(opts: {
36
44
  quit: () => void;
37
45
  }): ExtensionContext;
38
- /** Tear down the agent process and clean up. */
46
+ /** Tear down the agent and clean up. */
39
47
  kill(): void;
40
48
  }
41
49
  export declare function createCore(config: AgentShellConfig): AgentShellCore;