agent-sh 0.10.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,9 +5,12 @@
5
5
  * provider settings, extensions, session management, and tool system.
6
6
  * Agent-sh provides the shell frontend and TUI rendering.
7
7
  *
8
- * In addition to pi's built-in tools, this bridge registers `user_shell`
9
- * so pi can execute commands in agent-sh's live PTY (visible to the user,
10
- * affects shell state like cd/export/source).
8
+ * The bridge is a pure protocol translator between pi's event stream and
9
+ * agent-sh's bus events. Pi brings its own tools for command execution,
10
+ * file ops, etc. PTY-access tools (`terminal_read`, `terminal_keys`,
11
+ * `user_shell`) are intentionally NOT bundled here — if you want pi to
12
+ * observe or mutate the user's live terminal, load a companion extension
13
+ * that registers those tools in pi's ToolDefinition format.
11
14
  *
12
15
  * Setup:
13
16
  * npm install @mariozechner/pi-agent-core @mariozechner/pi-ai @mariozechner/pi-coding-agent
@@ -22,157 +25,13 @@ import {
22
25
  createAgentSessionRuntime,
23
26
  SessionManager,
24
27
  } from "@mariozechner/pi-coding-agent";
25
- import { Type } from "@sinclair/typebox";
26
28
  import type { ExtensionContext } from "agent-sh/types";
27
- import type { EventBus } from "agent-sh/event-bus";
28
-
29
- // ── Helpers ──────────────────────────────────────────────────────
30
- function interpretEscapes(str: string): string {
31
- return str.replace(/\\(x[0-9a-fA-F]{2}|r|n|t|\\|0)/g, (_, seq: string) => {
32
- if (seq === "r") return "\r";
33
- if (seq === "n") return "\n";
34
- if (seq === "t") return "\t";
35
- if (seq === "\\") return "\\";
36
- if (seq === "0") return "\0";
37
- if (seq.startsWith("x")) return String.fromCharCode(parseInt(seq.slice(1), 16));
38
- return seq;
39
- });
40
- }
41
-
42
- function settle(ms = 100): Promise<void> {
43
- return new Promise((resolve) => setTimeout(resolve, ms));
44
- }
45
-
46
- // ── user_shell as a pi ToolDefinition ─────────────────────────────
47
- function createUserShellToolDef(bus: EventBus) {
48
- // Track agent-sh's live cwd so user_shell always runs in the right place
49
- let liveCwd = process.cwd();
50
- bus.on("shell:cwd-change", ({ cwd }) => { liveCwd = cwd; });
51
-
52
- const schema = Type.Object({
53
- command: Type.String({ description: "Command to execute in user's shell" }),
54
- return_output: Type.Optional(
55
- Type.Boolean({
56
- description:
57
- "Whether to return the command output. Default false — output is shown directly to the user.",
58
- }),
59
- ),
60
- });
61
-
62
- return {
63
- name: "user_shell",
64
- label: "user_shell",
65
- description:
66
- "Run a command with lasting effects in the user's live shell (cd, export, " +
67
- "install packages, start servers) or show output the user wants to see. " +
68
- "Output is shown directly to the user. Set return_output=true only " +
69
- "if you need to inspect the result.",
70
- promptSnippet: "Execute commands in the user's live terminal (PTY).",
71
- promptGuidelines: [
72
- "You are running inside agent-sh, a terminal wrapper.",
73
- "Use your standard tools (bash, file ops) for investigation — output goes to you, not the user.",
74
- "Use user_shell to run commands in the user's live shell when they ask to see output or need lasting effects (cd, install, start servers).",
75
- "Default to standard tools. Use user_shell when the user is the intended audience for the output or the command has real effects.",
76
- ],
77
- parameters: schema,
78
-
79
- async execute(_toolCallId, params) {
80
- const command = params.command;
81
- const returnOutput = params.return_output ?? false;
82
-
83
- const result = await bus.emitPipeAsync("shell:exec-request", {
84
- command,
85
- output: "",
86
- cwd: liveCwd,
87
- done: false,
88
- });
89
-
90
- const text = returnOutput
91
- ? result.output || "(no output)"
92
- : "Command executed.";
93
-
94
- return { content: [{ type: "text", text }], details: undefined };
95
- },
96
- };
97
- }
98
-
99
- // ── terminal_read as a pi ToolDefinition ─────────────────────────
100
- function createTerminalReadToolDef(ctx: ExtensionContext) {
101
- return {
102
- name: "terminal_read",
103
- label: "terminal_read",
104
- description:
105
- "Read the current terminal screen contents. Returns clean text (ANSI stripped) " +
106
- "with cursor position and whether an alternate-screen program (vim, htop, less) is active.",
107
- promptSnippet: "Read the terminal screen to see what the user sees.",
108
- promptGuidelines: [
109
- "Use terminal_read to see the current terminal screen before sending keystrokes.",
110
- "Check altScreen to know if a full-screen program (vim, htop) is running.",
111
- ],
112
- parameters: Type.Object({}),
113
- async execute() {
114
- const tb = ctx.terminalBuffer;
115
- if (!tb) return { content: [{ type: "text", text: "terminal buffer not available" }], details: undefined };
116
- const { text, altScreen, cursorX, cursorY } = tb.readScreen();
117
- const info = [
118
- altScreen ? "mode: alternate screen" : "mode: normal",
119
- `cursor: row=${cursorY} col=${cursorX}`,
120
- ].join(", ");
121
- return { content: [{ type: "text", text: `[${info}]\n\n${text}` }], details: undefined };
122
- },
123
- };
124
- }
125
-
126
- // ── terminal_keys as a pi ToolDefinition ─────────────────────────
127
- function createTerminalKeysToolDef(bus: EventBus, ctx: ExtensionContext) {
128
- return {
129
- name: "terminal_keys",
130
- label: "terminal_keys",
131
- description:
132
- "Send keystrokes to the user's live terminal as if the user typed them. " +
133
- "Use escape sequences: \\x1b for Escape, \\r for Enter, \\t for Tab, " +
134
- "\\x03 for Ctrl+C, \\x1b[A/B/C/D for arrow keys, \\x7f for Backspace. " +
135
- "Example: \\x1b:q!\\r to quit vim. Always call terminal_read after.",
136
- promptSnippet: "Send keystrokes to interactive programs in the terminal.",
137
- promptGuidelines: [
138
- "Use terminal_keys to type into interactive programs (vim, htop, less).",
139
- "Always call terminal_read after sending keys to verify the result.",
140
- ],
141
- parameters: Type.Object({
142
- keys: Type.String({ description: "Keystrokes to send (use \\x1b for Escape, \\r for Enter, etc.)" }),
143
- settle_ms: Type.Optional(
144
- Type.Number({ description: "Wait time in ms after sending keys (default: 150)" }),
145
- ),
146
- }),
147
- async execute(_toolCallId: string, params: any) {
148
- const keys = interpretEscapes(params.keys);
149
- const settleMs = params.settle_ms ?? 150;
150
- bus.emit("shell:stdout-show", {});
151
- process.stdout.write("\n");
152
- bus.emit("shell:pty-write", { data: keys });
153
- await settle(settleMs);
154
-
155
- const tb = ctx.terminalBuffer;
156
- if (!tb) return { content: [{ type: "text", text: "Keys sent." }], details: undefined };
157
- const { text, altScreen, cursorX, cursorY } = tb.readScreen();
158
- const info = [
159
- altScreen ? "mode: alternate screen" : "mode: normal",
160
- `cursor: row=${cursorY} col=${cursorX}`,
161
- ].join(", ");
162
- return { content: [{ type: "text", text: `Keys sent. Screen after:\n[${info}]\n\n${text}` }], details: undefined };
163
- },
164
- };
165
- }
166
29
 
167
30
  // ── Extension entry point ─────────────────────────────────────────
168
31
  export default function activate(ctx: ExtensionContext): void {
169
32
  const { bus } = ctx;
170
33
  const cwd = process.cwd();
171
34
 
172
- const userShellTool = createUserShellToolDef(bus);
173
- const termReadTool = createTerminalReadToolDef(ctx);
174
- const termKeysTool = createTerminalKeysToolDef(bus, ctx);
175
-
176
35
  // ── Boot pi session (async — register backend synchronously first) ──
177
36
  let session: any = null;
178
37
  let runtime: any = null;
@@ -190,7 +49,6 @@ export default function activate(ctx: ExtensionContext): void {
190
49
  const result = await createAgentSessionFromServices({
191
50
  services,
192
51
  sessionManager: opts.sessionManager ?? sessionManager,
193
- customTools: [userShellTool, termReadTool, termKeysTool],
194
52
  });
195
53
  return { ...result, services };
196
54
  };
@@ -227,9 +85,7 @@ export default function activate(ctx: ExtensionContext): void {
227
85
  bus.emit("agent:tool-started", {
228
86
  title: (event as any).toolName,
229
87
  toolCallId: (event as any).toolCallId,
230
- kind: (event as any).toolName === "user_shell" || (event as any).toolName === "bash"
231
- ? "execute"
232
- : "read",
88
+ kind: (event as any).toolName === "bash" ? "execute" : "read",
233
89
  });
234
90
  break;
235
91
 
@@ -251,9 +107,7 @@ export default function activate(ctx: ExtensionContext): void {
251
107
  bus.emit("agent:tool-completed", {
252
108
  toolCallId: (event as any).toolCallId,
253
109
  exitCode: (event as any).isError ? 1 : 0,
254
- kind: (event as any).toolName === "user_shell" || (event as any).toolName === "bash"
255
- ? "execute"
256
- : "read",
110
+ kind: (event as any).toolName === "bash" ? "execute" : "read",
257
111
  });
258
112
  break;
259
113
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-sh",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "description": "A shell-first terminal where AI is one keystroke away",
5
5
  "type": "module",
6
6
  "main": "dist/core.js",
@@ -1,9 +0,0 @@
1
- /**
2
- * Shell recall extension.
3
- *
4
- * Intercepts __shell_recall terminal commands via the
5
- * "agent:terminal-intercept" pipe, returning virtual output from
6
- * ContextManager's recall API without spawning a subprocess.
7
- */
8
- import type { ExtensionContext } from "../types.js";
9
- export default function activate({ bus, contextManager }: ExtensionContext): void;
@@ -1,8 +0,0 @@
1
- export default function activate({ bus, contextManager }) {
2
- bus.onPipe("agent:terminal-intercept", (payload) => {
3
- if (!payload.command.trimStart().startsWith("__shell_recall"))
4
- return payload;
5
- const output = contextManager.handleRecallCommand(payload.command.trim());
6
- return { ...payload, intercepted: true, output };
7
- });
8
- }