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.
- package/README.md +11 -9
- package/dist/agent/agent-loop.d.ts +0 -3
- package/dist/agent/agent-loop.js +12 -35
- package/dist/agent/conversation-state.js +8 -2
- package/dist/agent/token-budget.d.ts +8 -12
- package/dist/agent/token-budget.js +5 -40
- package/dist/agent/types.d.ts +0 -1
- package/dist/context-manager.d.ts +1 -21
- package/dist/context-manager.js +26 -163
- package/dist/event-bus.d.ts +0 -1
- package/dist/extension-loader.js +25 -4
- package/dist/extensions/agent-backend.js +3 -2
- package/dist/extensions/index.js +0 -1
- package/dist/extensions/tui-renderer.js +5 -2
- package/dist/settings.d.ts +3 -11
- package/dist/settings.js +0 -4
- package/dist/shell/input-handler.js +8 -9
- package/dist/types.d.ts +3 -0
- package/dist/utils/ansi.d.ts +3 -1
- package/dist/utils/ansi.js +68 -7
- package/dist/utils/box-frame.js +8 -2
- package/dist/utils/markdown.js +23 -8
- package/dist/utils/package-version.d.ts +1 -0
- package/dist/utils/package-version.js +10 -0
- package/dist/utils/shell-output-spill.d.ts +2 -0
- package/dist/utils/shell-output-spill.js +81 -0
- package/examples/extensions/claude-code-bridge/README.md +14 -0
- package/examples/extensions/claude-code-bridge/index.ts +13 -101
- package/examples/extensions/pi-bridge/README.md +16 -0
- package/examples/extensions/pi-bridge/index.ts +8 -154
- package/package.json +1 -1
- package/dist/extensions/shell-recall.d.ts +0 -9
- package/dist/extensions/shell-recall.js +0 -8
|
@@ -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
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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 === "
|
|
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 === "
|
|
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,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
|
-
}
|