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.
Files changed (106) hide show
  1. package/README.md +27 -43
  2. package/dist/agent/agent-loop.d.ts +69 -6
  3. package/dist/agent/agent-loop.js +954 -153
  4. package/dist/agent/conversation-state.d.ts +74 -21
  5. package/dist/agent/conversation-state.js +361 -150
  6. package/dist/agent/history-file.d.ts +13 -4
  7. package/dist/agent/history-file.js +110 -36
  8. package/dist/agent/nuclear-form.d.ts +28 -3
  9. package/dist/agent/nuclear-form.js +88 -6
  10. package/dist/agent/skills.d.ts +2 -4
  11. package/dist/agent/skills.js +10 -4
  12. package/dist/agent/subagent.d.ts +23 -0
  13. package/dist/agent/subagent.js +53 -11
  14. package/dist/agent/system-prompt.d.ts +37 -5
  15. package/dist/agent/system-prompt.js +100 -67
  16. package/dist/{token-budget.d.ts → agent/token-budget.d.ts} +5 -4
  17. package/dist/{token-budget.js → agent/token-budget.js} +15 -20
  18. package/dist/agent/tool-protocol.d.ts +105 -0
  19. package/dist/agent/tool-protocol.js +551 -0
  20. package/dist/agent/tools/bash.js +3 -3
  21. package/dist/agent/tools/edit-file.js +9 -6
  22. package/dist/agent/tools/glob.js +4 -2
  23. package/dist/agent/tools/grep.js +27 -3
  24. package/dist/agent/tools/ls.js +5 -6
  25. package/dist/agent/types.d.ts +22 -2
  26. package/dist/context-manager.d.ts +17 -0
  27. package/dist/context-manager.js +37 -4
  28. package/dist/core.d.ts +7 -7
  29. package/dist/core.js +99 -196
  30. package/dist/event-bus.d.ts +85 -2
  31. package/dist/event-bus.js +20 -1
  32. package/dist/executor.d.ts +4 -3
  33. package/dist/executor.js +18 -15
  34. package/dist/extension-loader.d.ts +5 -0
  35. package/dist/extension-loader.js +143 -19
  36. package/dist/extensions/agent-backend.d.ts +14 -0
  37. package/dist/extensions/agent-backend.js +188 -0
  38. package/dist/extensions/command-suggest.d.ts +3 -3
  39. package/dist/extensions/command-suggest.js +4 -3
  40. package/dist/extensions/index.d.ts +19 -0
  41. package/dist/extensions/index.js +24 -0
  42. package/dist/extensions/slash-commands.d.ts +1 -1
  43. package/dist/extensions/slash-commands.js +30 -10
  44. package/dist/extensions/tui-renderer.js +117 -113
  45. package/dist/index.js +39 -26
  46. package/dist/settings.d.ts +40 -3
  47. package/dist/settings.js +57 -10
  48. package/dist/{input-handler.d.ts → shell/input-handler.d.ts} +3 -2
  49. package/dist/{input-handler.js → shell/input-handler.js} +111 -85
  50. package/dist/{output-parser.d.ts → shell/output-parser.d.ts} +1 -1
  51. package/dist/{output-parser.js → shell/output-parser.js} +1 -1
  52. package/dist/{shell.d.ts → shell/shell.d.ts} +8 -2
  53. package/dist/{shell.js → shell/shell.js} +39 -8
  54. package/dist/types.d.ts +61 -10
  55. package/dist/utils/ansi.d.ts +5 -0
  56. package/dist/utils/ansi.js +1 -1
  57. package/dist/utils/compositor.d.ts +67 -0
  58. package/dist/utils/compositor.js +116 -0
  59. package/dist/utils/diff-renderer.d.ts +9 -0
  60. package/dist/utils/diff-renderer.js +312 -146
  61. package/dist/utils/diff.d.ts +21 -2
  62. package/dist/utils/diff.js +165 -89
  63. package/dist/utils/floating-panel.d.ts +2 -0
  64. package/dist/utils/floating-panel.js +30 -14
  65. package/dist/utils/handler-registry.d.ts +31 -10
  66. package/dist/utils/handler-registry.js +58 -16
  67. package/dist/utils/line-editor.d.ts +33 -3
  68. package/dist/utils/line-editor.js +221 -44
  69. package/dist/utils/markdown.d.ts +1 -0
  70. package/dist/utils/markdown.js +1 -1
  71. package/dist/utils/message-utils.d.ts +35 -0
  72. package/dist/utils/message-utils.js +75 -0
  73. package/dist/utils/terminal-buffer.d.ts +5 -1
  74. package/dist/utils/terminal-buffer.js +18 -2
  75. package/dist/utils/tool-display.d.ts +1 -1
  76. package/dist/utils/tool-display.js +4 -4
  77. package/dist/utils/tool-interactive.d.ts +12 -0
  78. package/dist/utils/tool-interactive.js +53 -0
  79. package/examples/extensions/ash-acp-bridge/README.md +39 -0
  80. package/examples/extensions/ash-acp-bridge/package.json +23 -0
  81. package/examples/extensions/ash-acp-bridge/src/index.ts +574 -0
  82. package/examples/extensions/ash-acp-bridge/tsconfig.json +14 -0
  83. package/examples/extensions/ash-mcp-bridge/README.md +72 -0
  84. package/examples/extensions/ash-mcp-bridge/index.ts +164 -0
  85. package/examples/extensions/ash-mcp-bridge/package.json +9 -0
  86. package/examples/extensions/claude-code-bridge/index.ts +198 -51
  87. package/examples/extensions/claude-code-bridge/package.json +1 -0
  88. package/examples/extensions/interactive-prompts.ts +98 -112
  89. package/examples/extensions/overlay-agent.ts +84 -38
  90. package/examples/extensions/peer-mesh.ts +565 -0
  91. package/examples/extensions/pi-bridge/index.ts +2 -2
  92. package/examples/extensions/questionnaire.ts +260 -0
  93. package/examples/extensions/subagents.ts +19 -4
  94. package/examples/extensions/terminal-buffer.ts +32 -53
  95. package/examples/extensions/tmux-pane.ts +307 -0
  96. package/examples/extensions/user-shell.ts +136 -0
  97. package/examples/extensions/web-access.ts +335 -0
  98. package/package.json +44 -2
  99. package/dist/agent/tools/display.d.ts +0 -13
  100. package/dist/agent/tools/display.js +0 -70
  101. package/dist/agent/tools/user-shell.d.ts +0 -13
  102. package/dist/agent/tools/user-shell.js +0 -87
  103. package/dist/extensions/overlay-agent.d.ts +0 -14
  104. package/dist/extensions/overlay-agent.js +0 -147
  105. package/dist/extensions/terminal-buffer.d.ts +0 -14
  106. package/dist/extensions/terminal-buffer.js +0 -125
@@ -0,0 +1,335 @@
1
+ /**
2
+ * Web Access extension — web search & content extraction for agent-sh.
3
+ *
4
+ * Provides two tools:
5
+ * - web_search: Search the web via Exa MCP (free, no API key)
6
+ * - web_fetch: Extract page content as clean markdown
7
+ * Fallback chain: Z.AI reader → Jina Reader → direct fetch
8
+ *
9
+ * Optional: ZAI_API_KEY environment variable (for Z.AI reader, best quality)
10
+ *
11
+ * Optional configuration (~/.agent-sh/settings.json):
12
+ * {
13
+ * "web-access": {
14
+ * "timeout": 30000,
15
+ * "searchNumResults": 5
16
+ * }
17
+ * }
18
+ *
19
+ * Inspired by: https://github.com/nicobailon/pi-web-access
20
+ */
21
+ import type { ExtensionContext } from "agent-sh/types";
22
+
23
+ // ── Constants ────────────────────────────────────────────────────────
24
+
25
+ const EXA_MCP_URL = "https://mcp.exa.ai/mcp";
26
+
27
+ const ZAI_BASE = "https://api.z.ai";
28
+ const ZAI_READER_PATH = "/api/mcp/web_reader/mcp";
29
+
30
+ const JINA_READER_URL = "https://r.jina.ai";
31
+
32
+ // ── Exa MCP search (free, no key, no session) ───────────────────────
33
+
34
+ async function exaSearch(
35
+ query: string,
36
+ numResults: number,
37
+ timeout: number,
38
+ ): Promise<string> {
39
+ const res = await fetch(EXA_MCP_URL, {
40
+ method: "POST",
41
+ headers: {
42
+ "Content-Type": "application/json",
43
+ Accept: "application/json, text/event-stream",
44
+ },
45
+ body: JSON.stringify({
46
+ jsonrpc: "2.0",
47
+ id: 1,
48
+ method: "tools/call",
49
+ params: {
50
+ name: "web_search_exa",
51
+ arguments: {
52
+ query,
53
+ numResults,
54
+ type: "auto",
55
+ livecrawl: "fallback",
56
+ contextMaxCharacters: 3000,
57
+ },
58
+ },
59
+ }),
60
+ signal: AbortSignal.timeout(timeout),
61
+ });
62
+
63
+ if (!res.ok) {
64
+ throw new Error(`Exa MCP ${res.status}: ${(await res.text()).slice(0, 200)}`);
65
+ }
66
+
67
+ const body = await res.text();
68
+
69
+ // Parse SSE or JSON response
70
+ let parsed: any = null;
71
+ for (const line of body.split("\n")) {
72
+ if (!line.startsWith("data:")) continue;
73
+ const payload = line.slice(line.charAt(5) === " " ? 6 : 5).trim();
74
+ if (!payload) continue;
75
+ try {
76
+ const candidate = JSON.parse(payload);
77
+ if (candidate?.result || candidate?.error) { parsed = candidate; break; }
78
+ } catch { /* skip */ }
79
+ }
80
+
81
+ if (!parsed) {
82
+ try { parsed = JSON.parse(body); } catch { /* skip */ }
83
+ }
84
+
85
+ if (!parsed) throw new Error("Exa MCP returned empty response");
86
+ if (parsed.error) throw new Error(parsed.error.message ?? JSON.stringify(parsed.error));
87
+ if (parsed.result?.isError) {
88
+ const msg = parsed.result.content?.find((c: any) => c.type === "text")?.text;
89
+ throw new Error(msg ?? "Exa MCP returned an error");
90
+ }
91
+
92
+ const text = parsed.result?.content?.find(
93
+ (c: any) => c.type === "text" && c.text?.trim(),
94
+ )?.text;
95
+
96
+ if (!text) throw new Error("Exa MCP returned empty content");
97
+ return text;
98
+ }
99
+
100
+ // ── Z.AI MCP reader (requires API key + session) ────────────────────
101
+
102
+ let zaiRpcId = 0;
103
+ const zaiSessionId = { current: "" };
104
+
105
+ async function zaiMcpPost(
106
+ apiKey: string,
107
+ body: Record<string, unknown>,
108
+ timeout: number,
109
+ ): Promise<any> {
110
+ const headers: Record<string, string> = {
111
+ "Content-Type": "application/json",
112
+ Accept: "application/json, text/event-stream",
113
+ Authorization: `Bearer ${apiKey}`,
114
+ };
115
+ if (zaiSessionId.current) headers["mcp-session-id"] = zaiSessionId.current;
116
+
117
+ const res = await fetch(`${ZAI_BASE}${ZAI_READER_PATH}`, {
118
+ method: "POST",
119
+ headers,
120
+ body: JSON.stringify(body),
121
+ signal: AbortSignal.timeout(timeout),
122
+ });
123
+
124
+ if (!res.ok) throw new Error(`Z.AI MCP ${res.status}`);
125
+
126
+ const sid = res.headers.get("mcp-session-id");
127
+ if (sid) zaiSessionId.current = sid;
128
+
129
+ const ct = res.headers.get("content-type") ?? "";
130
+ if (ct.includes("text/event-stream")) {
131
+ const text = await res.text();
132
+ for (const line of text.split("\n")) {
133
+ if (!line.startsWith("data:")) continue;
134
+ const payload = line.slice(line.charAt(5) === " " ? 6 : 5);
135
+ if (!payload) continue;
136
+ const parsed = JSON.parse(payload);
137
+ if (parsed.error) throw new Error(parsed.error.message);
138
+ return parsed.result;
139
+ }
140
+ throw new Error("No data in Z.AI SSE response");
141
+ }
142
+
143
+ const json = await res.json();
144
+ const response = Array.isArray(json) ? json[0] : json;
145
+ if (response?.error) throw new Error(response.error.message);
146
+ return response?.result;
147
+ }
148
+
149
+ async function zaiRead(apiKey: string, url: string, timeout: number): Promise<string> {
150
+ // Initialize session if needed
151
+ if (!zaiSessionId.current) {
152
+ await zaiMcpPost(apiKey, {
153
+ jsonrpc: "2.0", id: ++zaiRpcId, method: "initialize",
154
+ params: {
155
+ protocolVersion: "2024-11-05", capabilities: {},
156
+ clientInfo: { name: "ash-web-access", version: "1.0.0" },
157
+ },
158
+ }, timeout);
159
+ await zaiMcpPost(apiKey, {
160
+ jsonrpc: "2.0", method: "notifications/initialized",
161
+ }, timeout);
162
+ }
163
+
164
+ const result = await zaiMcpPost(apiKey, {
165
+ jsonrpc: "2.0", id: ++zaiRpcId, method: "tools/call",
166
+ params: { name: "webReader", arguments: { url } },
167
+ }, timeout);
168
+
169
+ // Unwrap double-encoded JSON response
170
+ const textBlock = result?.content?.find((c: any) => c.type === "text" && c.text);
171
+ if (!textBlock) return JSON.stringify(result, null, 2);
172
+
173
+ let data: any;
174
+ try {
175
+ data = JSON.parse(textBlock.text);
176
+ if (typeof data === "string") data = JSON.parse(data);
177
+ } catch {
178
+ return textBlock.text;
179
+ }
180
+
181
+ if (data && typeof data === "object" && !Array.isArray(data)) {
182
+ const title = data.title ? `# ${data.title}\n\n` : "";
183
+ const source = data.url ? `**Source:** ${data.url}\n\n` : "";
184
+ const body = data.content ?? data.markdown ?? data.text ?? JSON.stringify(data, null, 2);
185
+ return `${title}${source}${body}`;
186
+ }
187
+
188
+ return typeof data === "string" ? data : JSON.stringify(data, null, 2);
189
+ }
190
+
191
+ // ── Jina Reader (free, no key) ───────────────────────────────────────
192
+
193
+ async function jinaRead(url: string, timeout: number): Promise<string> {
194
+ const res = await fetch(`${JINA_READER_URL}/${url}`, {
195
+ headers: { Accept: "text/markdown", "X-Return-Format": "markdown" },
196
+ signal: AbortSignal.timeout(timeout),
197
+ });
198
+ if (!res.ok) throw new Error(`Jina Reader ${res.status}`);
199
+ return res.text();
200
+ }
201
+
202
+ // ── Direct fetch (last resort) ───────────────────────────────────────
203
+
204
+ async function directFetch(url: string, timeout: number): Promise<string> {
205
+ const res = await fetch(url, {
206
+ headers: {
207
+ "User-Agent":
208
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 " +
209
+ "(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
210
+ },
211
+ signal: AbortSignal.timeout(timeout),
212
+ redirect: "follow",
213
+ });
214
+ if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
215
+ const ct = res.headers.get("content-type") ?? "";
216
+ if (ct.includes("application/json")) return JSON.stringify(await res.json(), null, 2);
217
+ return res.text();
218
+ }
219
+
220
+ // ── Extension entry point ────────────────────────────────────────────
221
+
222
+ export default function activate(ctx: ExtensionContext) {
223
+ const apiKey = process.env.ZAI_API_KEY ?? "";
224
+
225
+ const config = ctx.getExtensionSettings("web-access", {
226
+ timeout: 30000,
227
+ searchNumResults: 5,
228
+ });
229
+
230
+ const timeout = config.timeout ?? 30000;
231
+ const numResults = config.searchNumResults ?? 5;
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
+
241
+ // ── Tool: web_search (Exa MCP, free) ────────────────────────────
242
+
243
+ ctx.registerTool({
244
+ name: "web_search",
245
+ displayName: "Web Search",
246
+ description:
247
+ "Search the web and return results with titles, URLs, and content snippets. " +
248
+ "Free, no API key required. Powered by Exa.",
249
+ input_schema: {
250
+ type: "object" as const,
251
+ properties: {
252
+ query: {
253
+ type: "string",
254
+ description: "The search query",
255
+ },
256
+ numResults: {
257
+ type: "number",
258
+ description: `Number of results (default: ${numResults}, max: 10)`,
259
+ },
260
+ },
261
+ required: ["query"],
262
+ },
263
+ async execute(args: { query: string; numResults?: number }) {
264
+ const n = Math.min(args.numResults ?? numResults, 10);
265
+ try {
266
+ const results = await exaSearch(args.query, n, timeout);
267
+ return { content: results, exitCode: 0, isError: false };
268
+ } catch (err: any) {
269
+ return { content: `Search failed: ${err.message}`, exitCode: 1, isError: true };
270
+ }
271
+ },
272
+ formatCall(args: { query: string }) {
273
+ return `Searching: "${args.query}"`;
274
+ },
275
+ });
276
+
277
+ // ── Tool: web_fetch ─────────────────────────────────────────────
278
+
279
+ ctx.registerTool({
280
+ name: "web_fetch",
281
+ displayName: "Web Fetch",
282
+ description:
283
+ "Fetch a URL and extract its content as clean markdown. " +
284
+ "Handles web pages, articles, and documentation. " +
285
+ "Uses Z.AI reader (best quality), Jina Reader, or direct fetch as fallback.",
286
+ input_schema: {
287
+ type: "object" as const,
288
+ properties: {
289
+ url: {
290
+ type: "string",
291
+ description: "The URL to fetch",
292
+ },
293
+ raw: {
294
+ type: "boolean",
295
+ description:
296
+ "If true, fetch raw content directly (useful for JSON APIs, raw text files)",
297
+ },
298
+ },
299
+ required: ["url"],
300
+ },
301
+ async execute(args: { url: string; raw?: boolean }) {
302
+ if (args.raw) {
303
+ try {
304
+ const content = await directFetch(args.url, timeout);
305
+ return { content, exitCode: 0, isError: false };
306
+ } catch (err: any) {
307
+ return { content: `Fetch failed: ${err.message}`, exitCode: 1, isError: true };
308
+ }
309
+ }
310
+
311
+ // Fallback chain: Z.AI reader → Jina Reader → direct fetch
312
+ if (apiKey) {
313
+ try {
314
+ const content = await zaiRead(apiKey, args.url, timeout);
315
+ return { content, exitCode: 0, isError: false };
316
+ } catch { /* fall through */ }
317
+ }
318
+
319
+ try {
320
+ const content = await jinaRead(args.url, timeout);
321
+ return { content, exitCode: 0, isError: false };
322
+ } catch { /* fall through */ }
323
+
324
+ try {
325
+ const content = await directFetch(args.url, timeout);
326
+ return { content, exitCode: 0, isError: false };
327
+ } catch (err: any) {
328
+ return { content: `All fetch methods failed: ${err.message}`, exitCode: 1, isError: true };
329
+ }
330
+ },
331
+ formatCall(args: { url: string }) {
332
+ return `Fetching: ${args.url}`;
333
+ },
334
+ });
335
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-sh",
3
- "version": "0.8.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/*.js",
20
+ "./utils/*": "./dist/utils/*",
21
21
  "./types": {
22
22
  "types": "./dist/types.d.ts",
23
23
  "default": "./dist/types.js"
@@ -26,6 +26,14 @@
26
26
  "types": "./dist/settings.d.ts",
27
27
  "default": "./dist/settings.js"
28
28
  },
29
+ "./extension-loader": {
30
+ "types": "./dist/extension-loader.d.ts",
31
+ "default": "./dist/extension-loader.js"
32
+ },
33
+ "./extensions": {
34
+ "types": "./dist/extensions/index.d.ts",
35
+ "default": "./dist/extensions/index.js"
36
+ },
29
37
  "./utils/stream-transform": {
30
38
  "types": "./dist/utils/stream-transform.d.ts",
31
39
  "default": "./dist/utils/stream-transform.js"
@@ -33,6 +41,38 @@
33
41
  "./utils/terminal-buffer": {
34
42
  "types": "./dist/utils/terminal-buffer.d.ts",
35
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"
36
76
  }
37
77
  },
38
78
  "files": [
@@ -70,12 +110,14 @@
70
110
  },
71
111
  "dependencies": {
72
112
  "cli-highlight": "^2.1.11",
113
+ "diff": "^9.0.0",
73
114
  "marked": "^17.0.6",
74
115
  "node-pty": "^1.2.0-beta.12",
75
116
  "openai": "^6.34.0",
76
117
  "tsx": "^4.19.0"
77
118
  },
78
119
  "devDependencies": {
120
+ "@types/diff": "^8.0.0",
79
121
  "@types/node": "^22.0.0",
80
122
  "typescript": "^5.7.0"
81
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 overlay agent.
3
- *
4
- * Provides a hotkey (Ctrl+\) to summon the agent from anywhere — even
5
- * inside vim, htop, or ssh. Composites a floating response box on top
6
- * of the current terminal content.
7
- *
8
- * Rendering reuses the shared tui:render-* handlers so that extensions
9
- * advising those handlers affect both the main TUI and the overlay.
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;