agent-sh 0.12.27 → 0.13.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 +13 -2
- package/dist/agent/agent-loop.d.ts +3 -5
- package/dist/agent/agent-loop.js +42 -98
- package/dist/agent/conversation-state.d.ts +9 -0
- package/dist/agent/conversation-state.js +16 -0
- package/dist/agent/history-file.d.ts +6 -0
- package/dist/agent/history-file.js +1 -1
- package/dist/agent/host-types.d.ts +125 -0
- package/dist/agent/index.d.ts +12 -4
- package/dist/agent/index.js +357 -6
- package/dist/agent/nuclear-form.d.ts +7 -0
- package/dist/{extensions → agent}/providers/deepseek.d.ts +2 -2
- package/dist/{extensions → agent}/providers/deepseek.js +5 -4
- package/dist/{extensions → agent}/providers/openai-compatible.d.ts +2 -2
- package/dist/{extensions → agent}/providers/openai.d.ts +2 -2
- package/dist/{extensions → agent}/providers/openai.js +3 -2
- package/dist/{extensions → agent}/providers/openrouter.d.ts +2 -2
- package/dist/{extensions → agent}/providers/openrouter.js +4 -3
- package/dist/agent/skills.js +51 -7
- package/dist/agent/subagent.d.ts +1 -1
- package/dist/agent/system-prompt.js +14 -17
- package/dist/agent/tool-protocol.d.ts +1 -1
- package/dist/agent/tool-protocol.js +5 -3
- package/dist/agent/tool-registry.d.ts +9 -4
- package/dist/agent/tool-registry.js +27 -4
- package/dist/agent/tools/bash.d.ts +1 -1
- package/dist/agent/tools/bash.js +3 -2
- package/dist/agent/tools/edit-file.js +0 -1
- package/dist/agent/tools/glob.js +1 -1
- package/dist/agent/tools/grep.js +1 -1
- package/dist/agent/tools/pwsh.d.ts +1 -1
- package/dist/agent/tools/pwsh.js +1 -2
- package/dist/agent/tools/read-file.js +7 -4
- package/dist/agent/tools/write-file.js +0 -1
- package/dist/agent/types.d.ts +17 -2
- package/dist/cli/auth/cli.d.ts +1 -0
- package/dist/cli/auth/cli.js +216 -0
- package/dist/cli/auth/keys.d.ts +31 -0
- package/dist/cli/auth/keys.js +102 -0
- package/dist/{index.js → cli/index.js} +29 -32
- package/dist/{init.js → cli/init.js} +1 -1
- package/dist/{install.js → cli/install.js} +31 -2
- package/dist/cli/subcommands.d.ts +1 -0
- package/dist/cli/subcommands.js +17 -0
- package/dist/{event-bus.d.ts → core/event-bus.d.ts} +7 -13
- package/dist/{extension-loader.d.ts → core/extension-loader.d.ts} +1 -1
- package/dist/{extension-loader.js → core/extension-loader.js} +62 -70
- package/dist/{core.d.ts → core/index.d.ts} +18 -15
- package/dist/{core.js → core/index.js} +18 -92
- package/dist/{settings.d.ts → core/settings.d.ts} +7 -0
- package/dist/{settings.js → core/settings.js} +1 -0
- package/dist/core/types.d.ts +49 -0
- package/dist/core/types.js +1 -0
- package/dist/extensions/file-autocomplete.d.ts +1 -1
- package/dist/extensions/index.d.ts +7 -14
- package/dist/extensions/index.js +2 -19
- package/dist/extensions/slash-commands.d.ts +1 -1
- package/dist/extensions/slash-commands.js +7 -2
- package/dist/shell/host-types.d.ts +114 -0
- package/dist/shell/host-types.js +1 -0
- package/dist/shell/index.d.ts +8 -7
- package/dist/shell/index.js +58 -9
- package/dist/shell/input-handler.d.ts +7 -1
- package/dist/shell/input-handler.js +5 -2
- package/dist/shell/output-parser.d.ts +1 -1
- package/dist/{extensions → shell}/shell-context.d.ts +1 -1
- package/dist/{extensions → shell}/shell-context.js +18 -12
- package/dist/shell/shell.d.ts +6 -4
- package/dist/shell/shell.js +33 -109
- package/dist/shell/strategies/bash.d.ts +2 -0
- package/dist/shell/strategies/bash.js +68 -0
- package/dist/shell/strategies/fish.d.ts +2 -0
- package/dist/shell/strategies/fish.js +65 -0
- package/dist/shell/strategies/index.d.ts +13 -0
- package/dist/shell/strategies/index.js +17 -0
- package/dist/shell/strategies/types.d.ts +50 -0
- package/dist/shell/strategies/types.js +9 -0
- package/dist/shell/strategies/zsh.d.ts +2 -0
- package/dist/shell/strategies/zsh.js +72 -0
- package/dist/shell/tui-input-view.js +14 -3
- package/dist/{extensions → shell}/tui-renderer.d.ts +1 -1
- package/dist/{extensions → shell}/tui-renderer.js +27 -55
- package/dist/utils/box-frame.d.ts +4 -0
- package/dist/utils/box-frame.js +17 -6
- package/dist/utils/compositor.d.ts +1 -1
- package/dist/utils/compositor.js +2 -1
- package/dist/{executor.js → utils/executor.js} +1 -1
- package/dist/utils/floating-panel.d.ts +1 -1
- package/dist/utils/floating-panel.js +9 -4
- package/dist/utils/llm-facade.d.ts +7 -3
- package/dist/utils/stream-transform.d.ts +1 -1
- package/dist/utils/terminal-buffer.d.ts +1 -1
- package/dist/utils/tool-display.js +4 -0
- package/dist/utils/tool-interactive.d.ts +1 -1
- package/dist/utils/tty.d.ts +7 -0
- package/dist/utils/tty.js +15 -0
- package/examples/extensions/ash-acp-bridge/README.md +4 -1
- package/examples/extensions/ash-acp-bridge/src/index.ts +654 -0
- package/examples/extensions/ash-mcp-bridge/index.ts +1 -1
- package/examples/extensions/ashi/README.md +250 -0
- package/examples/extensions/ashi/package.json +60 -0
- package/examples/extensions/ashi/src/autocomplete.ts +91 -0
- package/examples/extensions/ashi/src/capture.ts +34 -0
- package/examples/extensions/ashi/src/cli.ts +126 -0
- package/examples/extensions/ashi/src/commands.ts +82 -0
- package/examples/extensions/ashi/src/compaction.ts +157 -0
- package/examples/extensions/ashi/src/components.ts +332 -0
- package/examples/extensions/ashi/src/default-renderers.ts +153 -0
- package/examples/extensions/ashi/src/display-config.ts +62 -0
- package/examples/extensions/ashi/src/frontend.ts +735 -0
- package/examples/extensions/ashi/src/hooks.ts +136 -0
- package/examples/extensions/ashi/src/multi-session-store.ts +146 -0
- package/examples/extensions/ashi/src/session-commands.ts +76 -0
- package/examples/extensions/ashi/src/session-store.ts +264 -0
- package/examples/extensions/ashi/src/status-footer.ts +66 -0
- package/examples/extensions/ashi/src/theme.ts +151 -0
- package/examples/extensions/ashi/tsconfig.json +14 -0
- package/examples/extensions/emacs-buffer.ts +1 -1
- package/examples/extensions/interactive-prompts.ts +114 -69
- package/examples/extensions/latex-images.ts +3 -3
- package/examples/extensions/opencode-bridge/index.ts +1 -1
- package/examples/extensions/overlay-agent.ts +7 -5
- package/examples/extensions/peer-mesh.ts +1 -1
- package/examples/extensions/pi-bridge/index.ts +0 -1
- package/examples/extensions/questionnaire.ts +2 -1
- package/examples/extensions/rtk-proxy.ts +3 -3
- package/examples/extensions/solarized-theme.ts +3 -3
- package/examples/extensions/subagents.ts +6 -6
- package/examples/extensions/terminal-buffer.ts +1 -1
- package/examples/extensions/tmux-pane.ts +6 -4
- package/examples/extensions/tunnel-vision.ts +5 -5
- package/examples/extensions/user-shell.ts +1 -1
- package/examples/extensions/web-access.ts +5 -5
- package/package.json +26 -22
- package/dist/extensions/agent-backend.d.ts +0 -14
- package/dist/extensions/agent-backend.js +0 -307
- package/dist/types.d.ts +0 -227
- /package/dist/{types.js → agent/host-types.js} +0 -0
- /package/dist/{extensions → agent}/providers/openai-compatible.js +0 -0
- /package/dist/{index.d.ts → cli/index.d.ts} +0 -0
- /package/dist/{init.d.ts → cli/init.d.ts} +0 -0
- /package/dist/{install.d.ts → cli/install.d.ts} +0 -0
- /package/dist/{event-bus.js → core/event-bus.js} +0 -0
- /package/dist/{executor.d.ts → utils/executor.d.ts} +0 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { highlight, supportsLanguage } from "cli-highlight";
|
|
3
|
+
import type { EditorTheme, MarkdownTheme, SelectListTheme } from "@earendil-works/pi-tui";
|
|
4
|
+
|
|
5
|
+
/** Bundled dark palette, lifted from pi-coding-agent's dark.json. */
|
|
6
|
+
const VARS: Record<string, string> = {
|
|
7
|
+
cyan: "#00d7ff",
|
|
8
|
+
blue: "#5f87ff",
|
|
9
|
+
green: "#b5bd68",
|
|
10
|
+
red: "#cc6666",
|
|
11
|
+
yellow: "#ffff00",
|
|
12
|
+
gray: "#808080",
|
|
13
|
+
dimGray: "#666666",
|
|
14
|
+
darkGray: "#505050",
|
|
15
|
+
accent: "#8abeb7",
|
|
16
|
+
selectedBg: "#3a3a4a",
|
|
17
|
+
userMsgBg: "#343541",
|
|
18
|
+
toolPendingBg: "#282832",
|
|
19
|
+
toolSuccessBg: "#283228",
|
|
20
|
+
toolErrorBg: "#3c2828",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const RAW = {
|
|
24
|
+
accent: "accent",
|
|
25
|
+
border: "blue",
|
|
26
|
+
borderAccent: "cyan",
|
|
27
|
+
borderMuted: "darkGray",
|
|
28
|
+
success: "green",
|
|
29
|
+
error: "red",
|
|
30
|
+
warning: "yellow",
|
|
31
|
+
muted: "gray",
|
|
32
|
+
dim: "dimGray",
|
|
33
|
+
text: "",
|
|
34
|
+
thinkingText: "gray",
|
|
35
|
+
selectedBg: "selectedBg",
|
|
36
|
+
userMessageBg: "userMsgBg",
|
|
37
|
+
userMessageText: "",
|
|
38
|
+
toolPendingBg: "toolPendingBg",
|
|
39
|
+
toolSuccessBg: "toolSuccessBg",
|
|
40
|
+
toolErrorBg: "toolErrorBg",
|
|
41
|
+
toolTitle: "",
|
|
42
|
+
toolOutput: "gray",
|
|
43
|
+
mdHeading: "#f0c674",
|
|
44
|
+
mdLink: "#81a2be",
|
|
45
|
+
mdLinkUrl: "dimGray",
|
|
46
|
+
mdCode: "accent",
|
|
47
|
+
mdCodeBlock: "green",
|
|
48
|
+
mdCodeBlockBorder: "gray",
|
|
49
|
+
mdQuote: "gray",
|
|
50
|
+
mdQuoteBorder: "gray",
|
|
51
|
+
mdHr: "gray",
|
|
52
|
+
mdListBullet: "accent",
|
|
53
|
+
toolDiffAdded: "green",
|
|
54
|
+
toolDiffRemoved: "red",
|
|
55
|
+
toolDiffContext: "gray",
|
|
56
|
+
bashMode: "green",
|
|
57
|
+
} as const;
|
|
58
|
+
|
|
59
|
+
export type ThemeColor = keyof typeof RAW;
|
|
60
|
+
|
|
61
|
+
function resolve(v: string): string {
|
|
62
|
+
if (v === "") return "";
|
|
63
|
+
if (v.startsWith("#")) return v;
|
|
64
|
+
const hex = VARS[v];
|
|
65
|
+
if (!hex) throw new Error(`Unknown theme var: ${v}`);
|
|
66
|
+
return hex;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function hexToRgb(hex: string): [number, number, number] {
|
|
70
|
+
const c = hex.replace("#", "");
|
|
71
|
+
return [parseInt(c.slice(0, 2), 16), parseInt(c.slice(2, 4), 16), parseInt(c.slice(4, 6), 16)];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function fgAnsi(hex: string): string {
|
|
75
|
+
if (hex === "") return "\x1b[39m";
|
|
76
|
+
const [r, g, b] = hexToRgb(hex);
|
|
77
|
+
return `\x1b[38;2;${r};${g};${b}m`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function bgAnsi(hex: string): string {
|
|
81
|
+
if (hex === "") return "\x1b[49m";
|
|
82
|
+
const [r, g, b] = hexToRgb(hex);
|
|
83
|
+
return `\x1b[48;2;${r};${g};${b}m`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
class Theme {
|
|
87
|
+
private fgCodes = new Map<ThemeColor, string>();
|
|
88
|
+
private bgCodes = new Map<ThemeColor, string>();
|
|
89
|
+
constructor() {
|
|
90
|
+
for (const k of Object.keys(RAW) as ThemeColor[]) {
|
|
91
|
+
const hex = resolve(RAW[k]);
|
|
92
|
+
this.fgCodes.set(k, fgAnsi(hex));
|
|
93
|
+
this.bgCodes.set(k, bgAnsi(hex));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
fg(color: ThemeColor, text: string): string { return `${this.fgCodes.get(color)}${text}\x1b[39m`; }
|
|
97
|
+
bg(color: ThemeColor, text: string): string { return `${this.bgCodes.get(color)}${text}\x1b[49m`; }
|
|
98
|
+
bgCode(color: ThemeColor): string { return this.bgCodes.get(color) ?? ""; }
|
|
99
|
+
bold(text: string): string { return chalk.bold(text); }
|
|
100
|
+
italic(text: string): string { return chalk.italic(text); }
|
|
101
|
+
underline(text: string): string { return chalk.underline(text); }
|
|
102
|
+
strikethrough(text: string): string { return chalk.strikethrough(text); }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const theme = new Theme();
|
|
106
|
+
|
|
107
|
+
export function markdownTheme(): MarkdownTheme {
|
|
108
|
+
return {
|
|
109
|
+
heading: (t) => theme.fg("mdHeading", t),
|
|
110
|
+
link: (t) => theme.fg("mdLink", t),
|
|
111
|
+
linkUrl: (t) => theme.fg("mdLinkUrl", t),
|
|
112
|
+
code: (t) => theme.fg("mdCode", t),
|
|
113
|
+
codeBlock: (t) => theme.fg("mdCodeBlock", t),
|
|
114
|
+
codeBlockBorder: (t) => theme.fg("mdCodeBlockBorder", t),
|
|
115
|
+
quote: (t) => theme.fg("mdQuote", t),
|
|
116
|
+
quoteBorder: (t) => theme.fg("mdQuoteBorder", t),
|
|
117
|
+
hr: (t) => theme.fg("mdHr", t),
|
|
118
|
+
listBullet: (t) => theme.fg("mdListBullet", t),
|
|
119
|
+
bold: (t) => theme.bold(t),
|
|
120
|
+
italic: (t) => theme.italic(t),
|
|
121
|
+
underline: (t) => theme.underline(t),
|
|
122
|
+
strikethrough: (t) => theme.strikethrough(t),
|
|
123
|
+
highlightCode: (src: string, lang?: string): string[] => {
|
|
124
|
+
const validLang = lang && supportsLanguage(lang) ? lang : undefined;
|
|
125
|
+
if (!validLang) return src.split("\n").map((l) => theme.fg("mdCodeBlock", l));
|
|
126
|
+
try {
|
|
127
|
+
return highlight(src, { language: validLang, ignoreIllegals: true }).split("\n");
|
|
128
|
+
} catch {
|
|
129
|
+
return src.split("\n").map((l) => theme.fg("mdCodeBlock", l));
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function selectListTheme(): SelectListTheme {
|
|
136
|
+
return {
|
|
137
|
+
selectedPrefix: (t) => theme.fg("accent", t),
|
|
138
|
+
selectedText: (t) => theme.fg("accent", t),
|
|
139
|
+
description: (t) => theme.fg("muted", t),
|
|
140
|
+
scrollInfo: (t) => theme.fg("muted", t),
|
|
141
|
+
noMatch: (t) => theme.fg("muted", t),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function editorTheme(): EditorTheme {
|
|
146
|
+
return {
|
|
147
|
+
borderColor: (t) => theme.fg("borderMuted", t),
|
|
148
|
+
selectList: selectListTheme(),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"outDir": "dist",
|
|
7
|
+
"rootDir": "src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"skipLibCheck": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src"]
|
|
14
|
+
}
|
|
@@ -187,7 +187,7 @@ function renderSnapshot(snap: EmacsSnapshot): string {
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
export default function activate(ctx: ExtensionContext): void {
|
|
190
|
-
const { registerTool } = ctx;
|
|
190
|
+
const { registerTool } = ctx.agent;
|
|
191
191
|
if (!emacsclientAvailable()) return;
|
|
192
192
|
|
|
193
193
|
registerTool({
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Interactive permission prompts extension.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Without this extension, agent-sh runs in
|
|
6
|
-
*
|
|
7
|
-
* Uses the interactive UI primitive for compositor-aware, themed rendering.
|
|
4
|
+
* Gates the four built-in side-effect tools (bash, pwsh, write_file,
|
|
5
|
+
* edit_file) via tool advisors. Without this extension, agent-sh runs in
|
|
6
|
+
* yolo mode — tools execute without confirmation.
|
|
8
7
|
*
|
|
9
8
|
* Usage:
|
|
10
9
|
* agent-sh -e ./examples/extensions/interactive-prompts.ts
|
|
@@ -12,19 +11,22 @@
|
|
|
12
11
|
* # Or copy to ~/.agent-sh/extensions/ for permanent use:
|
|
13
12
|
* cp examples/extensions/interactive-prompts.ts ~/.agent-sh/extensions/
|
|
14
13
|
*/
|
|
14
|
+
import * as fs from "node:fs/promises";
|
|
15
|
+
import * as path from "node:path";
|
|
15
16
|
import { renderDiff } from "agent-sh/utils/diff-renderer.js";
|
|
16
17
|
import { renderBoxFrame } from "agent-sh/utils/box-frame.js";
|
|
17
18
|
import { palette as p } from "agent-sh/utils/palette.js";
|
|
18
|
-
import type
|
|
19
|
+
import { computeDiff, computeEditDiff, computeInputDiff, type DiffResult } from "agent-sh/utils/diff.js";
|
|
20
|
+
import type { AgentContext } from "agent-sh/types";
|
|
19
21
|
import type { ToolUI } from "agent-sh/agent/types.js";
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
const GATED_TOOLS = ["bash", "pwsh", "write_file", "edit_file"] as const;
|
|
24
|
+
|
|
25
|
+
export default function activate(ctx: AgentContext) {
|
|
22
26
|
let autoApproveWrites = false;
|
|
23
27
|
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
// border and key hints, so only one diff box is shown (not two).
|
|
27
|
-
ctx.advise("tui:render-diff", (next, filePath: string, diff: any, width: number) => {
|
|
28
|
+
// Frame pre-execute diff previews as a permission prompt.
|
|
29
|
+
ctx.advise("tui:render-diff", (_next, filePath: string, diff: DiffResult, width: number) => {
|
|
28
30
|
const boxW = Math.min(84, width);
|
|
29
31
|
const contentW = boxW - 4;
|
|
30
32
|
const MAX_DISPLAY = 25;
|
|
@@ -54,94 +56,137 @@ export default function activate(ctx: ExtensionContext) {
|
|
|
54
56
|
});
|
|
55
57
|
});
|
|
56
58
|
|
|
57
|
-
const
|
|
59
|
+
for (const name of GATED_TOOLS) {
|
|
60
|
+
ctx.agent.adviseTool(name, async (next, args, onChunk, toolCtx) => {
|
|
61
|
+
const ui = toolCtx?.ui;
|
|
62
|
+
if (!ui) return next(args, onChunk, toolCtx);
|
|
58
63
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (!ui) return payload;
|
|
64
|
+
const isFileWrite = name === "write_file" || name === "edit_file";
|
|
65
|
+
let diffPreRendered = false;
|
|
62
66
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
ctx.bus.emit("shell:stdout-show", {});
|
|
68
|
+
try {
|
|
69
|
+
if (isFileWrite) {
|
|
70
|
+
if (autoApproveWrites) {
|
|
71
|
+
// Skip prompt; tool's own post-execute diff renders as usual.
|
|
72
|
+
return next(args, onChunk, toolCtx);
|
|
73
|
+
}
|
|
74
|
+
await renderPreviewDiff(ctx, name, args);
|
|
75
|
+
diffPreRendered = true;
|
|
76
|
+
|
|
77
|
+
const answer = await promptWrite(ui);
|
|
78
|
+
if (answer === "reject") {
|
|
79
|
+
return { content: "Permission denied by user.", exitCode: 1, isError: true };
|
|
80
|
+
}
|
|
81
|
+
if (answer === "approve_all") autoApproveWrites = true;
|
|
82
|
+
} else {
|
|
83
|
+
const answer = await promptCommand(ui, name, args);
|
|
84
|
+
if (answer === "deny") {
|
|
85
|
+
return { content: "Permission denied by user.", exitCode: 1, isError: true };
|
|
86
|
+
}
|
|
73
87
|
}
|
|
74
|
-
|
|
88
|
+
} finally {
|
|
89
|
+
ctx.bus.emit("shell:stdout-hide", {});
|
|
75
90
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
91
|
+
|
|
92
|
+
const result = await next(args, onChunk, toolCtx);
|
|
93
|
+
if (diffPreRendered && result.display?.body?.kind === "diff") {
|
|
94
|
+
// Strip the redundant post-execute diff body since we already showed it.
|
|
95
|
+
return { ...result, display: { ...result.display, body: undefined } };
|
|
96
|
+
}
|
|
97
|
+
return result;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
80
100
|
}
|
|
81
101
|
|
|
82
|
-
async function
|
|
83
|
-
|
|
102
|
+
async function renderPreviewDiff(
|
|
103
|
+
ctx: AgentContext,
|
|
104
|
+
toolName: string,
|
|
105
|
+
args: Record<string, unknown>,
|
|
106
|
+
): Promise<void> {
|
|
107
|
+
const rawPath = args.path;
|
|
108
|
+
if (typeof rawPath !== "string") return;
|
|
109
|
+
const absPath = path.resolve(process.cwd(), rawPath);
|
|
84
110
|
|
|
85
|
-
|
|
111
|
+
let diff: DiffResult | undefined;
|
|
112
|
+
if (toolName === "edit_file" && typeof args.old_text === "string" && typeof args.new_text === "string") {
|
|
113
|
+
const normalizedOld = (args.old_text as string).replace(/\r\n/g, "\n");
|
|
114
|
+
const normalizedNew = (args.new_text as string).replace(/\r\n/g, "\n");
|
|
115
|
+
try {
|
|
116
|
+
const oldFileContent = await fs.readFile(absPath, "utf-8");
|
|
117
|
+
diff = computeEditDiff(
|
|
118
|
+
oldFileContent, normalizedOld, normalizedNew,
|
|
119
|
+
args.replace_all === true,
|
|
120
|
+
);
|
|
121
|
+
} catch {
|
|
122
|
+
diff = computeInputDiff(normalizedOld, normalizedNew);
|
|
123
|
+
}
|
|
124
|
+
} else if (toolName === "write_file" && typeof args.content === "string") {
|
|
125
|
+
let oldContent: string | null = null;
|
|
126
|
+
try { oldContent = await fs.readFile(absPath, "utf-8"); } catch { /* new file */ }
|
|
127
|
+
diff = computeDiff(oldContent, args.content as string);
|
|
128
|
+
}
|
|
129
|
+
if (!diff || diff.isIdentical) return;
|
|
130
|
+
|
|
131
|
+
const cwd = process.cwd();
|
|
132
|
+
const home = process.env.HOME ?? "";
|
|
133
|
+
let displayPath = absPath;
|
|
134
|
+
if (absPath.startsWith(cwd + "/")) displayPath = absPath.slice(cwd.length + 1);
|
|
135
|
+
else if (home && absPath.startsWith(home + "/")) displayPath = "~/" + absPath.slice(home.length + 1);
|
|
136
|
+
|
|
137
|
+
ctx.call("tui:show-diff", displayPath, diff);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function promptWrite(ui: ToolUI): Promise<"approve" | "approve_all" | "reject"> {
|
|
141
|
+
return ui.custom<"approve" | "approve_all" | "reject">({
|
|
86
142
|
render(width) {
|
|
87
143
|
const boxW = Math.min(84, width);
|
|
88
|
-
return renderBoxFrame(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
title: "Permission required",
|
|
95
|
-
footer: [` ${p.dim}[y]es / [n]o / [a]llow all${p.reset}`],
|
|
96
|
-
},
|
|
97
|
-
);
|
|
144
|
+
return renderBoxFrame([], {
|
|
145
|
+
width: boxW,
|
|
146
|
+
style: "rounded",
|
|
147
|
+
borderColor: p.warning,
|
|
148
|
+
footer: [` ${p.bold}[y] Apply [n] Skip [a] Don't ask again${p.reset}`],
|
|
149
|
+
});
|
|
98
150
|
},
|
|
99
151
|
handleInput(data, done) {
|
|
100
152
|
const ch = data.toLowerCase();
|
|
101
153
|
if (ch === "y") done("approve");
|
|
102
154
|
else if (ch === "a") done("approve_all");
|
|
103
|
-
else if (ch === "n" || ch === "\x1b") done("
|
|
155
|
+
else if (ch === "n" || ch === "\x1b") done("reject");
|
|
104
156
|
},
|
|
105
157
|
});
|
|
106
|
-
|
|
107
|
-
if (answer === "approve" || answer === "approve_all") {
|
|
108
|
-
const kind = answer === "approve_all" ? "allow_always" : "allow_once";
|
|
109
|
-
const option = options?.find((o: any) => o.kind === kind)
|
|
110
|
-
?? options?.find((o: any) => o.kind === "allow_once" || o.kind === "allow_always");
|
|
111
|
-
if (option) {
|
|
112
|
-
return { ...payload, decision: { outcome: "selected", optionId: option.optionId } };
|
|
113
|
-
}
|
|
114
|
-
return { ...payload, decision: { outcome: "approved" } };
|
|
115
|
-
}
|
|
116
|
-
return { ...payload, decision: { outcome: "cancelled" } };
|
|
117
158
|
}
|
|
118
159
|
|
|
119
|
-
async function
|
|
120
|
-
|
|
160
|
+
async function promptCommand(
|
|
161
|
+
ui: ToolUI,
|
|
162
|
+
toolName: string,
|
|
163
|
+
args: Record<string, unknown>,
|
|
164
|
+
): Promise<"approve" | "deny"> {
|
|
165
|
+
const command = typeof args.command === "string" ? args.command : "";
|
|
166
|
+
const description = typeof args.description === "string" ? args.description : "";
|
|
167
|
+
const title = description ? `${toolName}: ${description}` : toolName;
|
|
168
|
+
const body = command
|
|
169
|
+
? `${p.bold}${title}${p.reset}\n${p.dim}${truncate(command, 200)}${p.reset}`
|
|
170
|
+
: `${p.bold}${title}${p.reset}`;
|
|
171
|
+
return ui.custom<"approve" | "deny">({
|
|
121
172
|
render(width) {
|
|
122
173
|
const boxW = Math.min(84, width);
|
|
123
|
-
|
|
124
|
-
// by our advise on "tui:render-diff".
|
|
125
|
-
return renderBoxFrame([], {
|
|
174
|
+
return renderBoxFrame(body.split("\n"), {
|
|
126
175
|
width: boxW,
|
|
127
176
|
style: "rounded",
|
|
128
177
|
borderColor: p.warning,
|
|
129
|
-
|
|
178
|
+
title: "Permission required",
|
|
179
|
+
footer: [` ${p.dim}[y]es / [n]o${p.reset}`],
|
|
130
180
|
});
|
|
131
181
|
},
|
|
132
182
|
handleInput(data, done) {
|
|
133
183
|
const ch = data.toLowerCase();
|
|
134
184
|
if (ch === "y") done("approve");
|
|
135
|
-
else if (ch === "
|
|
136
|
-
else if (ch === "n" || ch === "\x1b") done("reject");
|
|
185
|
+
else if (ch === "n" || ch === "\x1b") done("deny");
|
|
137
186
|
},
|
|
138
187
|
});
|
|
188
|
+
}
|
|
139
189
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
if (answer === "approve_all") {
|
|
144
|
-
return { ...payload, decision: { outcome: "approved", autoApprove: true } };
|
|
145
|
-
}
|
|
146
|
-
return { ...payload, decision: { outcome: "cancelled" } };
|
|
190
|
+
function truncate(s: string, max: number): string {
|
|
191
|
+
return s.length <= max ? s : s.slice(0, max - 1) + "…";
|
|
147
192
|
}
|
|
@@ -19,7 +19,7 @@ import { execSync } from "node:child_process";
|
|
|
19
19
|
import * as fs from "node:fs";
|
|
20
20
|
import * as os from "node:os";
|
|
21
21
|
import * as path from "node:path";
|
|
22
|
-
import type {
|
|
22
|
+
import type { ShellContext } from "agent-sh/types";
|
|
23
23
|
|
|
24
24
|
// Settings loaded in activate() via ctx.getExtensionSettings
|
|
25
25
|
let config = { dpi: 300, fgColor: "d4d4d4" };
|
|
@@ -98,7 +98,7 @@ function renderEquation(equation: string): Buffer | null {
|
|
|
98
98
|
|
|
99
99
|
// ── Extension entry point ────────────────────────────────────────
|
|
100
100
|
|
|
101
|
-
export default function activate(ctx:
|
|
101
|
+
export default function activate(ctx: ShellContext) {
|
|
102
102
|
const { bus } = ctx;
|
|
103
103
|
|
|
104
104
|
// Load settings: ~/.agent-sh/settings.json → "latex-images": { dpi, fgColor }
|
|
@@ -116,7 +116,7 @@ export default function activate(ctx: ExtensionContext) {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
// Handle inline $$...$$ display math
|
|
119
|
-
ctx.createBlockTransform({
|
|
119
|
+
ctx.shell.createBlockTransform({
|
|
120
120
|
open: "$$",
|
|
121
121
|
close: "$$",
|
|
122
122
|
transform(latex) {
|
|
@@ -60,7 +60,7 @@ function parseUnifiedDiff(patch: string): DiffResult | null {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
export default function activate(ctx: ExtensionContext): void {
|
|
63
|
-
const { bus, call
|
|
63
|
+
const { bus, call } = ctx; const { compositor } = ctx.shell;
|
|
64
64
|
|
|
65
65
|
const cwd = (): string => {
|
|
66
66
|
const v = call("cwd");
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* - terminal-buffer.ts → terminal_read / terminal_keys tools
|
|
21
21
|
* - user-shell.ts → user_shell tool (run new shell commands)
|
|
22
22
|
*/
|
|
23
|
-
import type {
|
|
23
|
+
import type { AgentContext, ShellContext, RemoteSession } from "agent-sh/types";
|
|
24
24
|
import type { RenderSurface } from "agent-sh/utils/compositor";
|
|
25
25
|
import { FloatingPanel } from "agent-sh/utils/floating-panel";
|
|
26
26
|
import { formatScreenContext, type TerminalBuffer } from "agent-sh/utils/terminal-buffer";
|
|
@@ -66,8 +66,10 @@ function createPanelSurface(panel: FloatingPanel): RenderSurface {
|
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
export default function activate(ctx:
|
|
70
|
-
const { bus
|
|
69
|
+
export default function activate(ctx: AgentContext & ShellContext): void {
|
|
70
|
+
const { bus } = ctx;
|
|
71
|
+
const { registerInstruction } = ctx.agent;
|
|
72
|
+
const { createRemoteSession } = ctx.shell;
|
|
71
73
|
const terminalBuffer: TerminalBuffer | null = ctx.call("terminal-buffer");
|
|
72
74
|
|
|
73
75
|
const panel = new FloatingPanel(bus, {
|
|
@@ -79,13 +81,13 @@ export default function activate(ctx: ExtensionContext): void {
|
|
|
79
81
|
const panelSurface = createPanelSurface(panel);
|
|
80
82
|
let session: RemoteSession | null = null;
|
|
81
83
|
|
|
82
|
-
ctx.registerContextProducer("interactive-session", () =>
|
|
84
|
+
ctx.agent.registerContextProducer("interactive-session", () =>
|
|
83
85
|
session?.active ? "interactive-session: true" : null,
|
|
84
86
|
);
|
|
85
87
|
|
|
86
88
|
// Inject the live screen for TUI / REPL programs. At a plain shell prompt
|
|
87
89
|
// `<shell_events>` already covers the visible scrollback — skip to dedupe.
|
|
88
|
-
ctx.registerContextProducer("terminal-screen", () => {
|
|
90
|
+
ctx.agent.registerContextProducer("terminal-screen", () => {
|
|
89
91
|
if (!session?.active || !terminalBuffer?.altScreen) return null;
|
|
90
92
|
return formatScreenContext(terminalBuffer.readScreen(), 80);
|
|
91
93
|
});
|
|
@@ -190,7 +190,7 @@ class PeerServer {
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
export default function activate(ctx: ExtensionContext): void {
|
|
193
|
-
const { bus, registerCommand, registerTool, registerInstruction
|
|
193
|
+
const { bus, registerCommand, define } = ctx; const { registerTool, registerInstruction } = ctx.agent;
|
|
194
194
|
const getCwd = () => ctx.call("cwd") as string;
|
|
195
195
|
const startTime = Date.now();
|
|
196
196
|
|
|
@@ -55,7 +55,8 @@ interface QuestionnaireResult {
|
|
|
55
55
|
|
|
56
56
|
// ── Extension ────────────────────────────────────────────────────
|
|
57
57
|
|
|
58
|
-
export default function activate(
|
|
58
|
+
export default function activate(ctx: ExtensionContext) {
|
|
59
|
+
const { registerTool, registerInstruction } = ctx.agent;
|
|
59
60
|
registerInstruction("questionnaire", [
|
|
60
61
|
"# When to use the questionnaire tool",
|
|
61
62
|
"ALWAYS use the `questionnaire` tool instead of asking the user a question in plain text when:",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* cp examples/extensions/rtk-proxy.ts ~/.agent-sh/extensions/
|
|
21
21
|
*/
|
|
22
22
|
import { execSync } from "node:child_process";
|
|
23
|
-
import type {
|
|
23
|
+
import type { AgentContext } from "agent-sh/types";
|
|
24
24
|
|
|
25
25
|
const DEFAULT_PREFIXES = new Set([
|
|
26
26
|
"git", "gh",
|
|
@@ -73,7 +73,7 @@ function rewriteForRtk(cmd: string, prefixes: Set<string>, flag: string): string
|
|
|
73
73
|
return `RTK_TELEMETRY_DISABLED=1 rtk ${flag}${cmd}`;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
export default function activate(ctx:
|
|
76
|
+
export default function activate(ctx: AgentContext) {
|
|
77
77
|
const config = ctx.getExtensionSettings("rtk-proxy", {
|
|
78
78
|
enabled: true,
|
|
79
79
|
ultraCompact: false,
|
|
@@ -95,7 +95,7 @@ export default function activate(ctx: ExtensionContext) {
|
|
|
95
95
|
for (const p of config.excludePrefixes) prefixes.delete(p);
|
|
96
96
|
const flag = config.ultraCompact ? "--ultra-compact " : "";
|
|
97
97
|
|
|
98
|
-
ctx.registerInstruction("rtk-proxy",
|
|
98
|
+
ctx.agent.registerInstruction("rtk-proxy",
|
|
99
99
|
"The rtk-proxy extension transparently rewrites bash commands like " +
|
|
100
100
|
"`git status`, `cargo test`, `pytest` to their rtk-compressed equivalents " +
|
|
101
101
|
"before execution. Output will be condensed (errors/failures first, " +
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
* # Or copy to ~/.agent-sh/extensions/ for permanent use:
|
|
10
10
|
* cp examples/extensions/solarized-theme.ts ~/.agent-sh/extensions/
|
|
11
11
|
*/
|
|
12
|
-
import type {
|
|
12
|
+
import type { ShellContext } from "agent-sh/types";
|
|
13
13
|
|
|
14
|
-
export default function activate(
|
|
15
|
-
setPalette({
|
|
14
|
+
export default function activate(ctx: ShellContext) {
|
|
15
|
+
ctx.shell.setPalette({
|
|
16
16
|
accent: "\x1b[38;2;38;139;210m", // blue (#268bd2)
|
|
17
17
|
success: "\x1b[38;2;133;153;0m", // green (#859900)
|
|
18
18
|
warning: "\x1b[38;2;181;137;0m", // yellow (#b58900)
|
|
@@ -8,22 +8,22 @@
|
|
|
8
8
|
* Usage:
|
|
9
9
|
* agent-sh -e ./examples/extensions/subagents.ts
|
|
10
10
|
*/
|
|
11
|
-
import type {
|
|
11
|
+
import type { AgentContext } from "agent-sh/types";
|
|
12
12
|
import { runSubagent } from "agent-sh/agent/subagent";
|
|
13
13
|
|
|
14
|
-
export default function activate(ctx:
|
|
14
|
+
export default function activate(ctx: AgentContext): void {
|
|
15
15
|
const { bus, llmClient } = ctx;
|
|
16
16
|
if (!llmClient) return;
|
|
17
17
|
|
|
18
|
-
const allToolNames = () => ctx.getTools().map(t => t.name);
|
|
18
|
+
const allToolNames = () => ctx.agent.getTools().map(t => t.name);
|
|
19
19
|
|
|
20
|
-
ctx.registerInstruction("subagent-guide", [
|
|
20
|
+
ctx.agent.registerInstruction("subagent-guide", [
|
|
21
21
|
"You have a spawn_agent tool for delegating work to a subagent with its own context.",
|
|
22
22
|
"The subagent inherits your session history, so write a short directive (what to do), not a briefing (what happened).",
|
|
23
23
|
"Use it for tasks that need multiple tool calls you don't need to see — research, exploration, independent implementation.",
|
|
24
24
|
].join("\n"));
|
|
25
25
|
|
|
26
|
-
ctx.registerTool({
|
|
26
|
+
ctx.agent.registerTool({
|
|
27
27
|
name: "spawn_agent",
|
|
28
28
|
description:
|
|
29
29
|
"Spawn a subagent with its own fresh context to handle a focused task. " +
|
|
@@ -63,7 +63,7 @@ export default function activate(ctx: ExtensionContext): void {
|
|
|
63
63
|
const task = args.task as string;
|
|
64
64
|
const toolNames = args.tools as string[] | undefined;
|
|
65
65
|
|
|
66
|
-
const allTools = ctx.getTools();
|
|
66
|
+
const allTools = ctx.agent.getTools();
|
|
67
67
|
// Filter to requested tools, or give all tools (minus spawn_agent to prevent recursion)
|
|
68
68
|
const tools = toolNames
|
|
69
69
|
? allTools.filter(t => toolNames.includes(t.name))
|
|
@@ -138,7 +138,7 @@ function diffScreens(before: string, after: string): string {
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
export default function activate(ctx: ExtensionContext): void {
|
|
141
|
-
const { bus
|
|
141
|
+
const { bus } = ctx; const { registerTool } = ctx.agent;
|
|
142
142
|
const tb = ctx.call("terminal-buffer");
|
|
143
143
|
if (!tb) return; // @xterm/headless not installed, or shell frontend not loaded
|
|
144
144
|
|
|
@@ -22,7 +22,7 @@ import * as net from "node:net";
|
|
|
22
22
|
import * as os from "node:os";
|
|
23
23
|
import * as path from "node:path";
|
|
24
24
|
import { execSync, spawn } from "node:child_process";
|
|
25
|
-
import type {
|
|
25
|
+
import type { AgentContext, ShellContext, RenderSurface, RemoteSession } from "agent-sh/types";
|
|
26
26
|
|
|
27
27
|
// ── Helpers ─────────────────────────────────────────────────────
|
|
28
28
|
|
|
@@ -142,15 +142,17 @@ interface PaneState {
|
|
|
142
142
|
|
|
143
143
|
// ── Extension ───────────────────────────────────────────────────
|
|
144
144
|
|
|
145
|
-
export default function activate(ctx:
|
|
146
|
-
const { bus, registerCommand
|
|
145
|
+
export default function activate(ctx: AgentContext & ShellContext): void {
|
|
146
|
+
const { bus, registerCommand } = ctx;
|
|
147
|
+
const { registerInstruction } = ctx.agent;
|
|
148
|
+
const { createRemoteSession } = ctx.shell;
|
|
147
149
|
|
|
148
150
|
if (!inTmux()) return;
|
|
149
151
|
|
|
150
152
|
let state: PaneState | null = null;
|
|
151
153
|
|
|
152
154
|
// Tell the LLM it's running inside an interactive pane session.
|
|
153
|
-
ctx.registerContextProducer("interactive-session", () =>
|
|
155
|
+
ctx.agent.registerContextProducer("interactive-session", () =>
|
|
154
156
|
state?.mode === "rsplit" ? "interactive-session: true" : null,
|
|
155
157
|
);
|
|
156
158
|
|