agent-sh 0.14.5 → 0.14.7
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/dist/agent/agent-loop.js +3 -1
- package/dist/agent/events.d.ts +4 -0
- package/dist/agent/index.js +6 -2
- package/dist/core/extension-loader.js +28 -15
- package/dist/shell/shell.js +3 -1
- package/dist/shell/terminal.d.ts +26 -0
- package/dist/shell/terminal.js +44 -0
- package/examples/extensions/ash-scheme/index.ts +4 -0
- package/examples/extensions/ashi/src/status-footer.ts +9 -20
- package/package.json +5 -1
- /package/examples/extensions/{ash-scheme-render.ts → ashi-scheme-render.ts} +0 -0
package/dist/agent/agent-loop.js
CHANGED
|
@@ -98,7 +98,9 @@ export class AgentLoop {
|
|
|
98
98
|
this.conversation = new ConversationState(this.handlers, this.instanceId);
|
|
99
99
|
this.activeMode = config.initialMode ?? { model: config.llmClient.model };
|
|
100
100
|
// Tool protocol — controls how tools are presented to the LLM
|
|
101
|
-
|
|
101
|
+
const { names: fromExtensions } = this.bus.emitPipe("agent:core-tools:collect", { names: [] });
|
|
102
|
+
const coreTools = Array.from(new Set([...(getSettings().coreTools ?? []), ...fromExtensions]));
|
|
103
|
+
this.toolProtocol = createToolProtocol(getSettings().toolMode ?? "api", coreTools);
|
|
102
104
|
// Register core tools
|
|
103
105
|
this.registerCoreTools();
|
|
104
106
|
// Register any protocol-provided tools (e.g. load_tool for deferred-lookup).
|
package/dist/agent/events.d.ts
CHANGED
|
@@ -9,6 +9,10 @@ export interface AgentIdentity {
|
|
|
9
9
|
}
|
|
10
10
|
declare module "../core/event-bus.js" {
|
|
11
11
|
interface BusEvents {
|
|
12
|
+
/** Sync pipe: extensions append core tool names; unioned with settings.coreTools. */
|
|
13
|
+
"agent:core-tools:collect": {
|
|
14
|
+
names: string[];
|
|
15
|
+
};
|
|
12
16
|
"agent:providers": {
|
|
13
17
|
providers: ProviderRegistration[];
|
|
14
18
|
};
|
package/dist/agent/index.js
CHANGED
|
@@ -343,8 +343,12 @@ export default function agentBackend(ctx) {
|
|
|
343
343
|
loadedExtensionNames = names;
|
|
344
344
|
resolvedProviders = computeResolvedProviders();
|
|
345
345
|
const settings = getSettings();
|
|
346
|
-
|
|
347
|
-
|
|
346
|
+
// Built-ins register unconditionally so `auth list` can enumerate them;
|
|
347
|
+
// the fallback must skip keyless entries or it lands on openrouter and
|
|
348
|
+
// bails at the `!effectiveApiKey` guard below.
|
|
349
|
+
const providerName = config.provider
|
|
350
|
+
?? settings.defaultProvider
|
|
351
|
+
?? [...resolvedProviders].find(([, p]) => p.apiKey)?.[0];
|
|
348
352
|
const activeProvider = providerName ? resolvedProviders.get(providerName) ?? null : null;
|
|
349
353
|
// Persisted defaultModel wins over openrouter's hardcoded DEFAULT_MODELS[0].
|
|
350
354
|
const effectiveApiKey = config.apiKey ?? activeProvider?.apiKey;
|
|
@@ -153,7 +153,7 @@ export async function loadExtensions(ctx, cliExtensions) {
|
|
|
153
153
|
if (settings.extensions.length > 0) {
|
|
154
154
|
specifiers.push(...settings.extensions);
|
|
155
155
|
}
|
|
156
|
-
const userSpecifiers = await discoverUserExtensions();
|
|
156
|
+
const userSpecifiers = await discoverUserExtensions(ctx);
|
|
157
157
|
specifiers.push(...userSpecifiers);
|
|
158
158
|
const seen = new Set();
|
|
159
159
|
const unique = specifiers.filter((s) => {
|
|
@@ -165,19 +165,30 @@ export async function loadExtensions(ctx, cliExtensions) {
|
|
|
165
165
|
const loaded = await loadSpecifiers(unique, ctx, false);
|
|
166
166
|
return loaded;
|
|
167
167
|
}
|
|
168
|
-
async function discoverUserExtensions() {
|
|
168
|
+
async function discoverUserExtensions(ctx) {
|
|
169
169
|
const specifiers = [];
|
|
170
170
|
const disabled = new Set(getSettings().disabledExtensions ?? []);
|
|
171
|
+
let entries;
|
|
171
172
|
try {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
173
|
+
entries = await fs.readdir(EXT_DIR, { withFileTypes: true });
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
if (err.code === "ENOENT")
|
|
177
|
+
return specifiers;
|
|
178
|
+
ctx.bus.emit("ui:error", {
|
|
179
|
+
message: `Failed to read extensions directory ${EXT_DIR}: ${err instanceof Error ? err.message : String(err)}`,
|
|
180
|
+
});
|
|
181
|
+
return specifiers;
|
|
182
|
+
}
|
|
183
|
+
for (const entry of entries) {
|
|
184
|
+
// Disable check: directory name for dir-extensions, or basename sans
|
|
185
|
+
// extension for file-extensions. Lets settings.json turn one off
|
|
186
|
+
// without renaming it.
|
|
187
|
+
const nameForDisable = entry.name.replace(/\.[^.]+$/, "");
|
|
188
|
+
if (disabled.has(nameForDisable))
|
|
189
|
+
continue;
|
|
190
|
+
const fullPath = path.join(EXT_DIR, entry.name);
|
|
191
|
+
try {
|
|
181
192
|
const isDir = entry.isDirectory() ||
|
|
182
193
|
(entry.isSymbolicLink() && (await fs.stat(fullPath)).isDirectory());
|
|
183
194
|
if (isDir) {
|
|
@@ -189,9 +200,11 @@ async function discoverUserExtensions() {
|
|
|
189
200
|
specifiers.push(fullPath);
|
|
190
201
|
}
|
|
191
202
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
203
|
+
catch (err) {
|
|
204
|
+
ctx.bus.emit("ui:error", {
|
|
205
|
+
message: `Failed to inspect extension ${fullPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
195
208
|
}
|
|
196
209
|
return specifiers;
|
|
197
210
|
}
|
|
@@ -240,7 +253,7 @@ async function loadSpecifiers(specifiers, ctx, bustCache) {
|
|
|
240
253
|
* Tears down old registrations, busts the module cache, and re-activates.
|
|
241
254
|
*/
|
|
242
255
|
export async function reloadExtensions(ctx) {
|
|
243
|
-
const specifiers = await discoverUserExtensions();
|
|
256
|
+
const specifiers = await discoverUserExtensions(ctx);
|
|
244
257
|
return loadSpecifiers(specifiers, ctx, true);
|
|
245
258
|
}
|
|
246
259
|
/**
|
package/dist/shell/shell.js
CHANGED
|
@@ -5,7 +5,8 @@ import { InputHandler } from "./input-handler.js";
|
|
|
5
5
|
import { OutputParser } from "./output-parser.js";
|
|
6
6
|
import { getSettings } from "../core/settings.js";
|
|
7
7
|
import { clearOpost } from "../utils/tty.js";
|
|
8
|
-
import { processTerminal } from "./terminal.js";
|
|
8
|
+
import { processTerminal, surfaceFromTerminal } from "./terminal.js";
|
|
9
|
+
import { TuiInputView } from "./tui-input-view.js";
|
|
9
10
|
import { pickStrategy, FALLBACK_STRATEGY, SUPPORTED_SHELL_NAMES, } from "./strategies/index.js";
|
|
10
11
|
export class Shell {
|
|
11
12
|
ptyProcess;
|
|
@@ -96,6 +97,7 @@ export class Shell {
|
|
|
96
97
|
bus: opts.bus,
|
|
97
98
|
handlers: opts.handlers,
|
|
98
99
|
onShowAgentInfo: opts.onShowAgentInfo ?? (() => ({ info: "" })),
|
|
100
|
+
view: new TuiInputView(surfaceFromTerminal(this.terminal)),
|
|
99
101
|
});
|
|
100
102
|
this.setupOutput();
|
|
101
103
|
this.setupInput();
|
package/dist/shell/terminal.d.ts
CHANGED
|
@@ -25,6 +25,32 @@ export interface Terminal {
|
|
|
25
25
|
}
|
|
26
26
|
/** Default Terminal: wraps process.stdin/stdout. */
|
|
27
27
|
export declare function processTerminal(): Terminal;
|
|
28
|
+
/**
|
|
29
|
+
* No-op terminal for non-rendering hosts (tests, agent-only embeds).
|
|
30
|
+
* Writes are discarded; input/resize never fire.
|
|
31
|
+
*/
|
|
32
|
+
export declare function headlessTerminal(cols?: number, rows?: number): Terminal;
|
|
33
|
+
/**
|
|
34
|
+
* Pipe-based terminal for embedders that own their own renderer (web hubs
|
|
35
|
+
* via xterm.js, electron windows, recording harnesses). Bytes from the
|
|
36
|
+
* Shell flow through `onWrite`; the host drives `pushInput`/`pushResize`
|
|
37
|
+
* to forward keystrokes and viewport changes back.
|
|
38
|
+
*/
|
|
39
|
+
export declare class BridgedTerminal implements Terminal {
|
|
40
|
+
private readonly onWrite;
|
|
41
|
+
private inputCbs;
|
|
42
|
+
private resizeCbs;
|
|
43
|
+
private _cols;
|
|
44
|
+
private _rows;
|
|
45
|
+
constructor(onWrite: (data: string) => void, cols?: number, rows?: number);
|
|
46
|
+
write(data: string): void;
|
|
47
|
+
onInput(cb: (d: string) => void): () => void;
|
|
48
|
+
onResize(cb: (c: number, r: number) => void): () => void;
|
|
49
|
+
cols(): number;
|
|
50
|
+
rows(): number;
|
|
51
|
+
pushInput(data: string): void;
|
|
52
|
+
pushResize(cols: number, rows: number): void;
|
|
53
|
+
}
|
|
28
54
|
/**
|
|
29
55
|
* Adapt a Terminal to a RenderSurface (the compositor's sink type). Adds
|
|
30
56
|
* the OPOST-cleared `\n` → `\r\n` translation that StdoutSurface applies,
|
package/dist/shell/terminal.js
CHANGED
|
@@ -45,6 +45,50 @@ export function processTerminal() {
|
|
|
45
45
|
},
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* No-op terminal for non-rendering hosts (tests, agent-only embeds).
|
|
50
|
+
* Writes are discarded; input/resize never fire.
|
|
51
|
+
*/
|
|
52
|
+
export function headlessTerminal(cols = 100, rows = 30) {
|
|
53
|
+
return {
|
|
54
|
+
write() { },
|
|
55
|
+
onInput: () => () => { },
|
|
56
|
+
onResize: () => () => { },
|
|
57
|
+
cols: () => cols,
|
|
58
|
+
rows: () => rows,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Pipe-based terminal for embedders that own their own renderer (web hubs
|
|
63
|
+
* via xterm.js, electron windows, recording harnesses). Bytes from the
|
|
64
|
+
* Shell flow through `onWrite`; the host drives `pushInput`/`pushResize`
|
|
65
|
+
* to forward keystrokes and viewport changes back.
|
|
66
|
+
*/
|
|
67
|
+
export class BridgedTerminal {
|
|
68
|
+
onWrite;
|
|
69
|
+
inputCbs = new Set();
|
|
70
|
+
resizeCbs = new Set();
|
|
71
|
+
_cols;
|
|
72
|
+
_rows;
|
|
73
|
+
constructor(onWrite, cols = 100, rows = 30) {
|
|
74
|
+
this.onWrite = onWrite;
|
|
75
|
+
this._cols = cols;
|
|
76
|
+
this._rows = rows;
|
|
77
|
+
}
|
|
78
|
+
write(data) { this.onWrite(data); }
|
|
79
|
+
onInput(cb) { this.inputCbs.add(cb); return () => { this.inputCbs.delete(cb); }; }
|
|
80
|
+
onResize(cb) { this.resizeCbs.add(cb); return () => { this.resizeCbs.delete(cb); }; }
|
|
81
|
+
cols() { return this._cols; }
|
|
82
|
+
rows() { return this._rows; }
|
|
83
|
+
pushInput(data) { for (const cb of this.inputCbs)
|
|
84
|
+
cb(data); }
|
|
85
|
+
pushResize(cols, rows) {
|
|
86
|
+
this._cols = cols;
|
|
87
|
+
this._rows = rows;
|
|
88
|
+
for (const cb of this.resizeCbs)
|
|
89
|
+
cb(cols, rows);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
48
92
|
/**
|
|
49
93
|
* Adapt a Terminal to a RenderSurface (the compositor's sink type). Adds
|
|
50
94
|
* the OPOST-cleared `\n` → `\r\n` translation that StdoutSurface applies,
|
|
@@ -2058,6 +2058,10 @@ export default function activate(ctx: AgentContext): void {
|
|
|
2058
2058
|
for (const name of HIDDEN_IN_SCHEME_ONLY) {
|
|
2059
2059
|
try { ctx.agent.unregisterTool(name); } catch { /* not registered — fine */ }
|
|
2060
2060
|
}
|
|
2061
|
+
ctx.bus.onPipe("agent:core-tools:collect", (ev) => ({
|
|
2062
|
+
...ev,
|
|
2063
|
+
names: [...ev.names, "scheme_eval"],
|
|
2064
|
+
}));
|
|
2061
2065
|
}
|
|
2062
2066
|
|
|
2063
2067
|
ctx.agent.registerTool({
|
|
@@ -43,16 +43,13 @@ export class StatusFooter extends Container {
|
|
|
43
43
|
const contentWidth = width > 0 ? Math.max(1, width - 2) : 0;
|
|
44
44
|
const right = this.buildRight();
|
|
45
45
|
const rightWidth = visibleWidth(right);
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const fullFits = contentWidth === 0
|
|
54
|
-
|| visibleWidth(full) + (right ? rightWidth + 1 : 0) <= contentWidth;
|
|
55
|
-
this.text.setText(fullFits ? join(full) : join(this.buildLine("basename")));
|
|
46
|
+
const left = this.buildLine();
|
|
47
|
+
if (!right) {
|
|
48
|
+
this.text.setText(left);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const gap = Math.max(1, contentWidth - visibleWidth(left) - rightWidth);
|
|
52
|
+
this.text.setText(`${left}${" ".repeat(gap)}${right}`);
|
|
56
53
|
}
|
|
57
54
|
|
|
58
55
|
private buildRight(): string {
|
|
@@ -62,7 +59,7 @@ export class StatusFooter extends Container {
|
|
|
62
59
|
return "";
|
|
63
60
|
}
|
|
64
61
|
|
|
65
|
-
private buildLine(
|
|
62
|
+
private buildLine(): string {
|
|
66
63
|
const { model, provider, contextWindow, cwd, branch, leaf, tokens, compactions, thinking } = this.fields;
|
|
67
64
|
const sep = theme.fg("dim", " | ");
|
|
68
65
|
const parts: string[] = [];
|
|
@@ -73,7 +70,7 @@ export class StatusFooter extends Container {
|
|
|
73
70
|
} else if (provider) {
|
|
74
71
|
parts.push(theme.fg("muted", `@${provider}`));
|
|
75
72
|
}
|
|
76
|
-
if (cwd) parts.push(theme.fg("muted",
|
|
73
|
+
if (cwd) parts.push(theme.fg("muted", basename(cwd) || cwd));
|
|
77
74
|
if (branch) parts.push(theme.fg("muted", `⎇ ${branch}`));
|
|
78
75
|
if (leaf != null && leaf > 0) parts.push(theme.fg("muted", `#${leaf}`));
|
|
79
76
|
if (tokens != null) {
|
|
@@ -86,14 +83,6 @@ export class StatusFooter extends Container {
|
|
|
86
83
|
}
|
|
87
84
|
}
|
|
88
85
|
|
|
89
|
-
function formatCwd(cwd: string, mode: "full" | "basename"): string {
|
|
90
|
-
if (mode === "basename") return basename(cwd) || cwd;
|
|
91
|
-
const home = process.env.HOME;
|
|
92
|
-
if (home && cwd.startsWith(`${home}/`)) return `~/${cwd.slice(home.length + 1)}`;
|
|
93
|
-
if (home && cwd === home) return "~";
|
|
94
|
-
return cwd;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
86
|
function fmtTokens(n: number): string {
|
|
98
87
|
if (n < 1000) return String(n);
|
|
99
88
|
if (n < 100_000) return `${(n / 1000).toFixed(1)}k`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-sh",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.7",
|
|
4
4
|
"description": "A shell-first terminal where AI is one keystroke away",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -41,6 +41,10 @@
|
|
|
41
41
|
"types": "./dist/shell/shell.d.ts",
|
|
42
42
|
"default": "./dist/shell/shell.js"
|
|
43
43
|
},
|
|
44
|
+
"./shell/host": {
|
|
45
|
+
"types": "./dist/shell/index.d.ts",
|
|
46
|
+
"default": "./dist/shell/index.js"
|
|
47
|
+
},
|
|
44
48
|
"./shell/strategies": {
|
|
45
49
|
"types": "./dist/shell/strategies/index.d.ts",
|
|
46
50
|
"default": "./dist/shell/strategies/index.js"
|
|
File without changes
|