agent-sh 0.9.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 +14 -21
- package/dist/agent/agent-loop.d.ts +43 -3
- package/dist/agent/agent-loop.js +811 -128
- package/dist/agent/conversation-state.d.ts +72 -21
- package/dist/agent/conversation-state.js +357 -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 +84 -3
- 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 +34 -1
- package/dist/agent/system-prompt.js +96 -47
- package/dist/agent/token-budget.d.ts +5 -4
- package/dist/agent/token-budget.js +14 -19
- package/dist/agent/tool-protocol.d.ts +23 -1
- package/dist/agent/tool-protocol.js +169 -4
- 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 +1 -1
- package/dist/context-manager.d.ts +17 -0
- package/dist/context-manager.js +37 -4
- package/dist/core.js +27 -6
- package/dist/event-bus.d.ts +59 -2
- package/dist/executor.d.ts +4 -3
- package/dist/executor.js +18 -15
- package/dist/extension-loader.js +50 -13
- package/dist/extensions/agent-backend.d.ts +8 -7
- package/dist/extensions/agent-backend.js +69 -48
- package/dist/extensions/index.js +0 -1
- package/dist/extensions/slash-commands.js +14 -9
- package/dist/extensions/tui-renderer.js +62 -78
- package/dist/index.js +25 -6
- package/dist/settings.d.ts +36 -5
- package/dist/settings.js +53 -9
- package/dist/shell/input-handler.d.ts +2 -1
- package/dist/shell/input-handler.js +82 -73
- package/dist/shell/shell.js +19 -2
- package/dist/types.d.ts +12 -0
- package/dist/utils/ansi.d.ts +5 -0
- package/dist/utils/ansi.js +1 -1
- package/dist/utils/compositor.d.ts +5 -0
- package/dist/utils/compositor.js +31 -3
- package/dist/utils/diff-renderer.d.ts +9 -0
- package/dist/utils/diff-renderer.js +221 -143
- package/dist/utils/diff.d.ts +21 -2
- package/dist/utils/diff.js +165 -89
- package/dist/utils/handler-registry.d.ts +5 -0
- package/dist/utils/handler-registry.js +6 -0
- package/dist/utils/line-editor.d.ts +11 -1
- package/dist/utils/line-editor.js +44 -5
- package/dist/utils/tool-display.d.ts +1 -1
- package/dist/utils/tool-display.js +4 -4
- package/examples/extensions/ash-acp-bridge/src/index.ts +4 -1
- package/examples/extensions/ash-mcp-bridge/index.ts +13 -3
- 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 +39 -25
- package/examples/extensions/overlay-agent.ts +3 -3
- package/examples/extensions/peer-mesh.ts +115 -0
- package/examples/extensions/pi-bridge/index.ts +2 -2
- package/examples/extensions/questionnaire.ts +16 -5
- package/examples/extensions/subagents.ts +19 -4
- package/examples/extensions/terminal-buffer.ts +163 -0
- package/examples/extensions/user-shell.ts +136 -0
- package/examples/extensions/web-access.ts +8 -0
- package/package.json +36 -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/terminal-buffer.d.ts +0 -14
- package/dist/extensions/terminal-buffer.js +0 -134
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* Usage:
|
|
9
9
|
* agent-sh -e ./examples/extensions/subagents.ts
|
|
10
10
|
*/
|
|
11
|
-
import type { ExtensionContext } from "
|
|
12
|
-
import { runSubagent } from "
|
|
11
|
+
import type { ExtensionContext } from "agent-sh/types";
|
|
12
|
+
import { runSubagent } from "agent-sh/agent/subagent";
|
|
13
13
|
|
|
14
14
|
export default function activate(ctx: ExtensionContext): void {
|
|
15
15
|
const { bus, llmClient, contextManager } = ctx;
|
|
@@ -17,6 +17,12 @@ export default function activate(ctx: ExtensionContext): void {
|
|
|
17
17
|
|
|
18
18
|
const allToolNames = () => ctx.getTools().map(t => t.name);
|
|
19
19
|
|
|
20
|
+
ctx.registerInstruction("subagent-guide", [
|
|
21
|
+
"You have a spawn_agent tool for delegating work to a subagent with its own context.",
|
|
22
|
+
"The subagent inherits your session history, so write a short directive (what to do), not a briefing (what happened).",
|
|
23
|
+
"Use it for tasks that need multiple tool calls you don't need to see — research, exploration, independent implementation.",
|
|
24
|
+
].join("\n"));
|
|
25
|
+
|
|
20
26
|
ctx.registerTool({
|
|
21
27
|
name: "spawn_agent",
|
|
22
28
|
description:
|
|
@@ -44,8 +50,15 @@ export default function activate(ctx: ExtensionContext): void {
|
|
|
44
50
|
|
|
45
51
|
getDisplayInfo: () => ({
|
|
46
52
|
kind: "execute",
|
|
53
|
+
icon: "⤵",
|
|
47
54
|
}),
|
|
48
55
|
|
|
56
|
+
formatCall: (args) => {
|
|
57
|
+
const task = String(args.task ?? "");
|
|
58
|
+
const max = 80;
|
|
59
|
+
return task.length > max ? task.slice(0, max - 1) + "…" : task;
|
|
60
|
+
},
|
|
61
|
+
|
|
49
62
|
async execute(args) {
|
|
50
63
|
const task = args.task as string;
|
|
51
64
|
const toolNames = args.tools as string[] | undefined;
|
|
@@ -56,9 +69,12 @@ export default function activate(ctx: ExtensionContext): void {
|
|
|
56
69
|
? allTools.filter(t => toolNames.includes(t.name))
|
|
57
70
|
: allTools.filter(t => t.name !== "spawn_agent");
|
|
58
71
|
|
|
72
|
+
const nuclearSummary = bus.emitPipe("agent:get-nuclear-summary", { summary: null }).summary;
|
|
73
|
+
|
|
59
74
|
const systemPrompt =
|
|
60
75
|
`You are a focused subagent. Complete the task and return a clear, concise result.\n` +
|
|
61
|
-
`Working directory: ${contextManager.getCwd()}
|
|
76
|
+
`Working directory: ${contextManager.getCwd()}` +
|
|
77
|
+
(nuclearSummary ? `\n\n[Parent session history]\n${nuclearSummary}` : "");
|
|
62
78
|
|
|
63
79
|
try {
|
|
64
80
|
const result = await runSubagent({
|
|
@@ -66,7 +82,6 @@ export default function activate(ctx: ExtensionContext): void {
|
|
|
66
82
|
tools,
|
|
67
83
|
systemPrompt,
|
|
68
84
|
task,
|
|
69
|
-
bus,
|
|
70
85
|
maxIterations: 25,
|
|
71
86
|
});
|
|
72
87
|
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal buffer extension.
|
|
3
|
+
*
|
|
4
|
+
* Registers two agent tools:
|
|
5
|
+
* - terminal_read: get the current screen contents + cursor position
|
|
6
|
+
* - terminal_keys: send raw keystrokes into the user's live PTY
|
|
7
|
+
*
|
|
8
|
+
* Together these let the agent operate inside interactive programs
|
|
9
|
+
* (vim, htop, less, etc.) by reading the screen and typing keys.
|
|
10
|
+
*
|
|
11
|
+
* Requires xterm in the extension directory:
|
|
12
|
+
* npm install @xterm/headless@5.5.0 @xterm/addon-serialize@0.13.0
|
|
13
|
+
*
|
|
14
|
+
* Core already loads xterm lazily (for floating-panel compositing), so
|
|
15
|
+
* installing these deps anywhere on the NODE_PATH is enough.
|
|
16
|
+
*/
|
|
17
|
+
import type { ExtensionContext } from "agent-sh/types";
|
|
18
|
+
|
|
19
|
+
/** Interpret C-style escape sequences (e.g. \r → CR, \x1b → ESC). */
|
|
20
|
+
function interpretEscapes(str: string): string {
|
|
21
|
+
return str.replace(/\\(x[0-9a-fA-F]{2}|r|n|t|\\|0)/g, (_, seq: string) => {
|
|
22
|
+
if (seq === "r") return "\r";
|
|
23
|
+
if (seq === "n") return "\n";
|
|
24
|
+
if (seq === "t") return "\t";
|
|
25
|
+
if (seq === "\\") return "\\";
|
|
26
|
+
if (seq === "0") return "\0";
|
|
27
|
+
if (seq.startsWith("x")) return String.fromCharCode(parseInt(seq.slice(1), 16));
|
|
28
|
+
return seq;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function settle(ms = 100): Promise<void> {
|
|
33
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default function activate(ctx: ExtensionContext): void {
|
|
37
|
+
const { bus, terminalBuffer: tb, registerTool, registerInstruction } = ctx;
|
|
38
|
+
if (!tb) return; // @xterm/headless not installed
|
|
39
|
+
|
|
40
|
+
registerTool({
|
|
41
|
+
name: "terminal_read",
|
|
42
|
+
description:
|
|
43
|
+
"Read what is currently visible on the user's terminal screen. Returns clean text (ANSI stripped) " +
|
|
44
|
+
"with cursor position and whether an alternate-screen program (vim, htop, less) is active. " +
|
|
45
|
+
"Use this to observe what the user sees — helpful for answering questions about terminal output, " +
|
|
46
|
+
"diagnosing errors on screen, or checking state before/after sending keystrokes with terminal_keys.",
|
|
47
|
+
input_schema: {
|
|
48
|
+
type: "object",
|
|
49
|
+
properties: {
|
|
50
|
+
include_scrollback: {
|
|
51
|
+
type: "boolean",
|
|
52
|
+
description:
|
|
53
|
+
"If true, include scrollback buffer (content that scrolled off screen) " +
|
|
54
|
+
"in addition to the visible viewport. Useful for capturing output from " +
|
|
55
|
+
"long-running or streaming commands. Default: false.",
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
showOutput: true,
|
|
60
|
+
|
|
61
|
+
getDisplayInfo: () => ({
|
|
62
|
+
kind: "read" as const,
|
|
63
|
+
icon: "⊞",
|
|
64
|
+
locations: [],
|
|
65
|
+
}),
|
|
66
|
+
|
|
67
|
+
async execute(args) {
|
|
68
|
+
const includeScrollback = (args.include_scrollback as boolean) ?? false;
|
|
69
|
+
const { text, altScreen, cursorX, cursorY } = tb.readScreen({ includeScrollback });
|
|
70
|
+
const info = [
|
|
71
|
+
altScreen ? "mode: alternate screen" : "mode: normal",
|
|
72
|
+
`cursor: row=${cursorY} col=${cursorX}`,
|
|
73
|
+
].join(", ");
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
content: `[${info}]\n\n${text}`,
|
|
77
|
+
exitCode: 0,
|
|
78
|
+
isError: false,
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
registerTool({
|
|
84
|
+
name: "terminal_keys",
|
|
85
|
+
description:
|
|
86
|
+
"Send keystrokes directly into the user's live terminal PTY, as if the user typed them. " +
|
|
87
|
+
"Use this to interact with programs already running in the terminal (vim, htop, less, ssh, REPLs, etc.) " +
|
|
88
|
+
"or to type commands at the shell prompt. This types directly into whatever is currently on screen.\n\n" +
|
|
89
|
+
"Escape sequences for special keys:\n" +
|
|
90
|
+
" - Escape: \\x1b\n" +
|
|
91
|
+
" - Enter/Return: \\r\n" +
|
|
92
|
+
" - Tab: \\t\n" +
|
|
93
|
+
" - Ctrl+C: \\x03\n" +
|
|
94
|
+
" - Ctrl+D: \\x04\n" +
|
|
95
|
+
" - Ctrl+Z: \\x1a\n" +
|
|
96
|
+
" - Arrow keys: \\x1b[A (up), \\x1b[B (down), \\x1b[C (right), \\x1b[D (left)\n" +
|
|
97
|
+
" - Backspace: \\x7f\n\n" +
|
|
98
|
+
"Example: to quit vim without saving, send keys=\"\\x1b:q!\\r\" (Escape, :q!, Enter).\n" +
|
|
99
|
+
"Always call terminal_read after sending keys to verify the result.",
|
|
100
|
+
input_schema: {
|
|
101
|
+
type: "object",
|
|
102
|
+
properties: {
|
|
103
|
+
keys: {
|
|
104
|
+
type: "string",
|
|
105
|
+
description:
|
|
106
|
+
"The keystrokes to send. Use \\x1b for Escape, \\r for Enter, \\t for Tab, " +
|
|
107
|
+
"\\x03 for Ctrl+C, etc. Regular characters are sent as-is.",
|
|
108
|
+
},
|
|
109
|
+
settle_ms: {
|
|
110
|
+
type: "number",
|
|
111
|
+
description:
|
|
112
|
+
"Milliseconds to wait after sending keys for the terminal to settle before " +
|
|
113
|
+
"returning (default: 150). Increase for slow programs.",
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
required: ["keys"],
|
|
117
|
+
},
|
|
118
|
+
showOutput: false,
|
|
119
|
+
|
|
120
|
+
getDisplayInfo: () => ({
|
|
121
|
+
kind: "execute" as const,
|
|
122
|
+
icon: "⌨",
|
|
123
|
+
locations: [],
|
|
124
|
+
}),
|
|
125
|
+
|
|
126
|
+
formatCall: (args) => {
|
|
127
|
+
const keys = args.keys as string;
|
|
128
|
+
return keys
|
|
129
|
+
.replace(/\\x1b|\x1b/g, "ESC")
|
|
130
|
+
.replace(/\\r|\r/g, "⏎")
|
|
131
|
+
.replace(/\\n|\n/g, "↵")
|
|
132
|
+
.replace(/\\t|\t/g, "TAB")
|
|
133
|
+
.replace(/\\x03|\x03/g, "^C")
|
|
134
|
+
.replace(/\\x04|\x04/g, "^D")
|
|
135
|
+
.replace(/\\x7f|\x7f/g, "BS");
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
async execute(args) {
|
|
139
|
+
const raw = args.keys as string;
|
|
140
|
+
const keys = interpretEscapes(raw);
|
|
141
|
+
const settleMs = (args.settle_ms as number) ?? 150;
|
|
142
|
+
|
|
143
|
+
bus.emit("shell:stdout-show", {});
|
|
144
|
+
process.stdout.write("\n");
|
|
145
|
+
bus.emit("shell:pty-write", { data: keys });
|
|
146
|
+
|
|
147
|
+
await settle(settleMs);
|
|
148
|
+
bus.emit("shell:stdout-hide", {});
|
|
149
|
+
|
|
150
|
+
const { text, altScreen, cursorX, cursorY } = tb.readScreen();
|
|
151
|
+
const info = [
|
|
152
|
+
altScreen ? "mode: alternate screen" : "mode: normal",
|
|
153
|
+
`cursor: row=${cursorY} col=${cursorX}`,
|
|
154
|
+
].join(", ");
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
content: `Keys sent. Screen after:\n[${info}]\n\n${text}`,
|
|
158
|
+
exitCode: 0,
|
|
159
|
+
isError: false,
|
|
160
|
+
};
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User shell extension.
|
|
3
|
+
*
|
|
4
|
+
* Registers the user_shell tool, which runs commands in the user's live PTY
|
|
5
|
+
* shell — affecting real shell state (cd, export, source). Also registers
|
|
6
|
+
* system prompt guidance so the agent knows when to use it.
|
|
7
|
+
*
|
|
8
|
+
* Without this extension, the agent only has the isolated bash tool.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* agent-sh -e ./examples/extensions/user-shell.ts
|
|
12
|
+
*
|
|
13
|
+
* # Or copy to ~/.agent-sh/extensions/ for permanent use:
|
|
14
|
+
* cp examples/extensions/user-shell.ts ~/.agent-sh/extensions/
|
|
15
|
+
*/
|
|
16
|
+
import type { ExtensionContext } from "agent-sh/types";
|
|
17
|
+
import type { ToolDefinition } from "agent-sh/agent/types";
|
|
18
|
+
|
|
19
|
+
export default function activate(ctx: ExtensionContext): void {
|
|
20
|
+
const { bus, registerTool, registerInstruction } = ctx;
|
|
21
|
+
const getCwd = () => ctx.contextManager.getCwd();
|
|
22
|
+
|
|
23
|
+
// ── Tool ───────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
registerTool(createUserShellTool({ getCwd, bus }));
|
|
26
|
+
|
|
27
|
+
// ── System prompt guidance ─────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
registerInstruction("user-shell-guide", `# user_shell Tool Guide
|
|
30
|
+
|
|
31
|
+
You have access to user_shell, which runs commands in the user's live shell (PTY).
|
|
32
|
+
- user_shell affects real shell state (cd, export, source).
|
|
33
|
+
- The user sees output directly — do not repeat or summarize it.
|
|
34
|
+
- Use it for: cd, export, source, installing packages, starting servers, git commands.
|
|
35
|
+
- Set return_output=true only if you need to inspect the result.
|
|
36
|
+
- When the user asks to see, list, view, or display anything, use user_shell.
|
|
37
|
+
Internal tools (bash, read, ls, etc.) run in an isolated subprocess — the user cannot see their output.
|
|
38
|
+
- Only use internal tools when you need to reason about content silently.`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function createUserShellTool(opts: {
|
|
42
|
+
getCwd: () => string;
|
|
43
|
+
bus: ExtensionContext["bus"];
|
|
44
|
+
}): ToolDefinition {
|
|
45
|
+
return {
|
|
46
|
+
name: "user_shell",
|
|
47
|
+
description:
|
|
48
|
+
"Run a complete, non-interactive command in the user's live shell (cd, export, install packages, start servers, git commands). " +
|
|
49
|
+
"Use this for commands that have side effects or that the user wants to see. Output is shown directly to the user but NOT returned " +
|
|
50
|
+
"to you by default — set return_output=true if you need to inspect the result. " +
|
|
51
|
+
"Do NOT use this to interact with programs that are already running in the terminal — use terminal_keys/terminal_read instead.",
|
|
52
|
+
input_schema: {
|
|
53
|
+
type: "object",
|
|
54
|
+
properties: {
|
|
55
|
+
command: {
|
|
56
|
+
type: "string",
|
|
57
|
+
description: "Command to execute in user's shell",
|
|
58
|
+
},
|
|
59
|
+
timeout: {
|
|
60
|
+
type: "number",
|
|
61
|
+
description: "Timeout in seconds (default: 30)",
|
|
62
|
+
},
|
|
63
|
+
return_output: {
|
|
64
|
+
type: "boolean",
|
|
65
|
+
default: false,
|
|
66
|
+
description:
|
|
67
|
+
"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.",
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
required: ["command"],
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
showOutput: false,
|
|
74
|
+
modifiesFiles: true,
|
|
75
|
+
|
|
76
|
+
getDisplayInfo: () => ({
|
|
77
|
+
kind: "execute",
|
|
78
|
+
icon: "▷",
|
|
79
|
+
locations: [],
|
|
80
|
+
}),
|
|
81
|
+
|
|
82
|
+
async execute(args) {
|
|
83
|
+
const command = args.command as string;
|
|
84
|
+
const timeoutSec = (args.timeout as number) ?? 30;
|
|
85
|
+
const returnOutput = (args.return_output as boolean) ?? false;
|
|
86
|
+
|
|
87
|
+
// Execute via the shell-exec extension's async pipe with timeout
|
|
88
|
+
let result: { output: string; exitCode: number | null; [k: string]: unknown };
|
|
89
|
+
try {
|
|
90
|
+
const execPromise = opts.bus.emitPipeAsync(
|
|
91
|
+
"shell:exec-request",
|
|
92
|
+
{
|
|
93
|
+
command,
|
|
94
|
+
output: "",
|
|
95
|
+
cwd: opts.getCwd(),
|
|
96
|
+
exitCode: null as number | null,
|
|
97
|
+
done: false,
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
const timeoutPromise = new Promise<never>((_, reject) =>
|
|
101
|
+
setTimeout(() => reject(new Error("timeout")), timeoutSec * 1000),
|
|
102
|
+
);
|
|
103
|
+
result = await Promise.race([execPromise, timeoutPromise]);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
106
|
+
if (msg === "timeout") {
|
|
107
|
+
return {
|
|
108
|
+
content: `Command timed out after ${timeoutSec}s.`,
|
|
109
|
+
exitCode: -1,
|
|
110
|
+
isError: true,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return { content: `Error: ${msg}`, exitCode: -1, isError: true };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const exitCode = result.exitCode ?? 0;
|
|
117
|
+
const isError = exitCode !== 0 && exitCode !== null;
|
|
118
|
+
|
|
119
|
+
if (returnOutput) {
|
|
120
|
+
return {
|
|
121
|
+
content: result.output || "(no output)",
|
|
122
|
+
exitCode,
|
|
123
|
+
isError,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
content: isError
|
|
129
|
+
? `Command failed with exit code ${exitCode}.`
|
|
130
|
+
: "Command executed.",
|
|
131
|
+
exitCode,
|
|
132
|
+
isError,
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
@@ -230,6 +230,14 @@ export default function activate(ctx: ExtensionContext) {
|
|
|
230
230
|
const timeout = config.timeout ?? 30000;
|
|
231
231
|
const numResults = config.searchNumResults ?? 5;
|
|
232
232
|
|
|
233
|
+
// ── System instruction ────────────────────────────────────────────
|
|
234
|
+
|
|
235
|
+
ctx.registerInstruction(
|
|
236
|
+
"You have access to web search and fetching tools. " +
|
|
237
|
+
"Use `web_search` to find information on the web, then `web_fetch` to read specific pages. " +
|
|
238
|
+
"Use `web_fetch` with `raw: true` for JSON APIs or plain text files.",
|
|
239
|
+
);
|
|
240
|
+
|
|
233
241
|
// ── Tool: web_search (Exa MCP, free) ────────────────────────────
|
|
234
242
|
|
|
235
243
|
ctx.registerTool({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-sh",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "A shell-first terminal where AI is one keystroke away",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/core.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"types": "./dist/core.d.ts",
|
|
18
18
|
"default": "./dist/core.js"
|
|
19
19
|
},
|
|
20
|
-
"./utils/*": "./dist/utils
|
|
20
|
+
"./utils/*": "./dist/utils/*",
|
|
21
21
|
"./types": {
|
|
22
22
|
"types": "./dist/types.d.ts",
|
|
23
23
|
"default": "./dist/types.js"
|
|
@@ -41,6 +41,38 @@
|
|
|
41
41
|
"./utils/terminal-buffer": {
|
|
42
42
|
"types": "./dist/utils/terminal-buffer.d.ts",
|
|
43
43
|
"default": "./dist/utils/terminal-buffer.js"
|
|
44
|
+
},
|
|
45
|
+
"./agent/types": {
|
|
46
|
+
"types": "./dist/agent/types.d.ts",
|
|
47
|
+
"default": "./dist/agent/types.js"
|
|
48
|
+
},
|
|
49
|
+
"./agent/subagent": {
|
|
50
|
+
"types": "./dist/agent/subagent.d.ts",
|
|
51
|
+
"default": "./dist/agent/subagent.js"
|
|
52
|
+
},
|
|
53
|
+
"./agent/agent-loop": {
|
|
54
|
+
"types": "./dist/agent/agent-loop.d.ts",
|
|
55
|
+
"default": "./dist/agent/agent-loop.js"
|
|
56
|
+
},
|
|
57
|
+
"./event-bus": {
|
|
58
|
+
"types": "./dist/event-bus.d.ts",
|
|
59
|
+
"default": "./dist/event-bus.js"
|
|
60
|
+
},
|
|
61
|
+
"./utils/compositor": {
|
|
62
|
+
"types": "./dist/utils/compositor.d.ts",
|
|
63
|
+
"default": "./dist/utils/compositor.js"
|
|
64
|
+
},
|
|
65
|
+
"./utils/floating-panel": {
|
|
66
|
+
"types": "./dist/utils/floating-panel.d.ts",
|
|
67
|
+
"default": "./dist/utils/floating-panel.js"
|
|
68
|
+
},
|
|
69
|
+
"./agent/token-budget": {
|
|
70
|
+
"types": "./dist/agent/token-budget.d.ts",
|
|
71
|
+
"default": "./dist/agent/token-budget.js"
|
|
72
|
+
},
|
|
73
|
+
"./executor": {
|
|
74
|
+
"types": "./dist/executor.d.ts",
|
|
75
|
+
"default": "./dist/executor.js"
|
|
44
76
|
}
|
|
45
77
|
},
|
|
46
78
|
"files": [
|
|
@@ -78,12 +110,14 @@
|
|
|
78
110
|
},
|
|
79
111
|
"dependencies": {
|
|
80
112
|
"cli-highlight": "^2.1.11",
|
|
113
|
+
"diff": "^9.0.0",
|
|
81
114
|
"marked": "^17.0.6",
|
|
82
115
|
"node-pty": "^1.2.0-beta.12",
|
|
83
116
|
"openai": "^6.34.0",
|
|
84
117
|
"tsx": "^4.19.0"
|
|
85
118
|
},
|
|
86
119
|
"devDependencies": {
|
|
120
|
+
"@types/diff": "^8.0.0",
|
|
87
121
|
"@types/node": "^22.0.0",
|
|
88
122
|
"typescript": "^5.7.0"
|
|
89
123
|
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { EventBus } from "../../event-bus.js";
|
|
2
|
-
import type { ToolDefinition } from "../types.js";
|
|
3
|
-
/**
|
|
4
|
-
* display — shows command output to the user in their live terminal.
|
|
5
|
-
*
|
|
6
|
-
* Unlike bash (scratchpad), the user sees the output directly in their shell.
|
|
7
|
-
* Unlike user_shell, this is for read-only display — no lasting side effects.
|
|
8
|
-
* The agent does NOT receive the output back.
|
|
9
|
-
*/
|
|
10
|
-
export declare function createDisplayTool(opts: {
|
|
11
|
-
getCwd: () => string;
|
|
12
|
-
bus: EventBus;
|
|
13
|
-
}): ToolDefinition;
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* display — shows command output to the user in their live terminal.
|
|
3
|
-
*
|
|
4
|
-
* Unlike bash (scratchpad), the user sees the output directly in their shell.
|
|
5
|
-
* Unlike user_shell, this is for read-only display — no lasting side effects.
|
|
6
|
-
* The agent does NOT receive the output back.
|
|
7
|
-
*/
|
|
8
|
-
export function createDisplayTool(opts) {
|
|
9
|
-
return {
|
|
10
|
-
name: "display",
|
|
11
|
-
description: "Show command output to the user in their terminal. Use when the user asks to see something (cat, git log, diff, man, etc.) and you don't need to process the output yourself. Output is NOT returned to you.",
|
|
12
|
-
input_schema: {
|
|
13
|
-
type: "object",
|
|
14
|
-
properties: {
|
|
15
|
-
command: {
|
|
16
|
-
type: "string",
|
|
17
|
-
description: "Command to run and display output to the user",
|
|
18
|
-
},
|
|
19
|
-
timeout: {
|
|
20
|
-
type: "number",
|
|
21
|
-
description: "Timeout in seconds (default: 30)",
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
required: ["command"],
|
|
25
|
-
},
|
|
26
|
-
showOutput: false,
|
|
27
|
-
modifiesFiles: false,
|
|
28
|
-
getDisplayInfo: () => ({
|
|
29
|
-
kind: "display",
|
|
30
|
-
icon: "◇",
|
|
31
|
-
locations: [],
|
|
32
|
-
}),
|
|
33
|
-
async execute(args) {
|
|
34
|
-
const command = args.command;
|
|
35
|
-
const timeoutSec = args.timeout ?? 30;
|
|
36
|
-
let result;
|
|
37
|
-
try {
|
|
38
|
-
const execPromise = opts.bus.emitPipeAsync("shell:exec-request", {
|
|
39
|
-
command,
|
|
40
|
-
output: "",
|
|
41
|
-
cwd: opts.getCwd(),
|
|
42
|
-
exitCode: null,
|
|
43
|
-
done: false,
|
|
44
|
-
});
|
|
45
|
-
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), timeoutSec * 1000));
|
|
46
|
-
result = await Promise.race([execPromise, timeoutPromise]);
|
|
47
|
-
}
|
|
48
|
-
catch (err) {
|
|
49
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
50
|
-
if (msg === "timeout") {
|
|
51
|
-
return {
|
|
52
|
-
content: `Command timed out after ${timeoutSec}s.`,
|
|
53
|
-
exitCode: -1,
|
|
54
|
-
isError: true,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
return { content: `Error: ${msg}`, exitCode: -1, isError: true };
|
|
58
|
-
}
|
|
59
|
-
const exitCode = result.exitCode ?? 0;
|
|
60
|
-
const isError = exitCode !== 0 && exitCode !== null;
|
|
61
|
-
return {
|
|
62
|
-
content: isError
|
|
63
|
-
? `Command failed with exit code ${exitCode}.`
|
|
64
|
-
: "Output displayed to user.",
|
|
65
|
-
exitCode,
|
|
66
|
-
isError,
|
|
67
|
-
};
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
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;
|
|
@@ -1,87 +0,0 @@
|
|
|
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 complete, non-interactive command in the user's live shell (cd, export, install packages, start servers, git commands). " +
|
|
12
|
-
"Use this for commands that have side effects or that the user wants to see. Output is shown directly to the user but NOT returned " +
|
|
13
|
-
"to you by default — set return_output=true if you need to inspect the result. " +
|
|
14
|
-
"Do NOT use this to interact with programs that are already running in the terminal — use terminal_keys/terminal_read instead.",
|
|
15
|
-
input_schema: {
|
|
16
|
-
type: "object",
|
|
17
|
-
properties: {
|
|
18
|
-
command: {
|
|
19
|
-
type: "string",
|
|
20
|
-
description: "Command to execute in user's shell",
|
|
21
|
-
},
|
|
22
|
-
timeout: {
|
|
23
|
-
type: "number",
|
|
24
|
-
description: "Timeout in seconds (default: 30)",
|
|
25
|
-
},
|
|
26
|
-
return_output: {
|
|
27
|
-
type: "boolean",
|
|
28
|
-
default: false,
|
|
29
|
-
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.",
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
required: ["command"],
|
|
33
|
-
},
|
|
34
|
-
showOutput: false,
|
|
35
|
-
modifiesFiles: true,
|
|
36
|
-
getDisplayInfo: () => ({
|
|
37
|
-
kind: "execute",
|
|
38
|
-
icon: "▷",
|
|
39
|
-
locations: [],
|
|
40
|
-
}),
|
|
41
|
-
async execute(args) {
|
|
42
|
-
const command = args.command;
|
|
43
|
-
const timeoutSec = args.timeout ?? 30;
|
|
44
|
-
const returnOutput = args.return_output ?? false;
|
|
45
|
-
// Execute via the shell-exec extension's async pipe with timeout
|
|
46
|
-
let result;
|
|
47
|
-
try {
|
|
48
|
-
const execPromise = opts.bus.emitPipeAsync("shell:exec-request", {
|
|
49
|
-
command,
|
|
50
|
-
output: "",
|
|
51
|
-
cwd: opts.getCwd(),
|
|
52
|
-
exitCode: null,
|
|
53
|
-
done: false,
|
|
54
|
-
});
|
|
55
|
-
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), timeoutSec * 1000));
|
|
56
|
-
result = await Promise.race([execPromise, timeoutPromise]);
|
|
57
|
-
}
|
|
58
|
-
catch (err) {
|
|
59
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
60
|
-
if (msg === "timeout") {
|
|
61
|
-
return {
|
|
62
|
-
content: `Command timed out after ${timeoutSec}s.`,
|
|
63
|
-
exitCode: -1,
|
|
64
|
-
isError: true,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
return { content: `Error: ${msg}`, exitCode: -1, isError: true };
|
|
68
|
-
}
|
|
69
|
-
const exitCode = result.exitCode ?? 0;
|
|
70
|
-
const isError = exitCode !== 0 && exitCode !== null;
|
|
71
|
-
if (returnOutput) {
|
|
72
|
-
return {
|
|
73
|
-
content: result.output || "(no output)",
|
|
74
|
-
exitCode,
|
|
75
|
-
isError,
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
return {
|
|
79
|
-
content: isError
|
|
80
|
-
? `Command failed with exit code ${exitCode}.`
|
|
81
|
-
: "Command executed.",
|
|
82
|
-
exitCode,
|
|
83
|
-
isError,
|
|
84
|
-
};
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Built-in terminal buffer extension.
|
|
3
|
-
*
|
|
4
|
-
* Registers two agent tools:
|
|
5
|
-
* - terminal_read: get the current screen contents + cursor position
|
|
6
|
-
* - terminal_keys: send raw keystrokes into the user's live PTY
|
|
7
|
-
*
|
|
8
|
-
* Together these let the agent operate inside interactive programs
|
|
9
|
-
* (vim, htop, less, etc.) by reading the screen and typing keys.
|
|
10
|
-
*
|
|
11
|
-
* Requires: npm install @xterm/headless@5.5.0 @xterm/addon-serialize@0.13.0
|
|
12
|
-
*/
|
|
13
|
-
import type { ExtensionContext } from "../types.js";
|
|
14
|
-
export default function activate(ctx: ExtensionContext): void;
|