agent-sh 0.12.27 → 0.13.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 +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 +358 -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-client.d.ts +16 -26
- package/dist/utils/llm-client.js +15 -26
- 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 +176 -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 +327 -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 +38 -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
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
3
|
+
import { activateShell, registerShellHandlers } from "../shell/index.js";
|
|
4
|
+
import { pickStrategy, FALLBACK_STRATEGY } from "../shell/strategies/index.js";
|
|
5
|
+
import { activateAgent } from "../agent/index.js";
|
|
6
|
+
import { createCore } from "../core/index.js";
|
|
7
|
+
import { palette as p } from "../utils/palette.js";
|
|
8
|
+
import { loadBuiltinExtensions } from "../extensions/index.js";
|
|
9
|
+
import { loadExtensions } from "../core/extension-loader.js";
|
|
10
|
+
import { getSettings } from "../core/settings.js";
|
|
11
|
+
import { dispatchSubcommand } from "./subcommands.js";
|
|
12
|
+
import { suggestBridgeFor } from "./install.js";
|
|
13
|
+
import { anyProviderConfigured } from "./auth/keys.js";
|
|
14
|
+
import { PACKAGE_VERSION } from "../utils/package-version.js";
|
|
15
|
+
import { clearOpost } from "../utils/tty.js";
|
|
13
16
|
/**
|
|
14
17
|
* Capture the user's full shell environment.
|
|
15
18
|
* This picks up env vars exported in .zshrc/.bashrc that the
|
|
@@ -25,12 +28,9 @@ async function captureShellEnvAsync(shell) {
|
|
|
25
28
|
resolve(result);
|
|
26
29
|
};
|
|
27
30
|
try {
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
? 'source ~/.zshrc 2>/dev/null;'
|
|
32
|
-
: '[ -f ~/.bashrc ] && source ~/.bashrc 2>/dev/null;';
|
|
33
|
-
const child = spawn(shell, ["-l", "-c", `${sourceRc} env -0`], {
|
|
31
|
+
const strategy = pickStrategy(shell) ?? FALLBACK_STRATEGY;
|
|
32
|
+
const captureCmd = strategy.envCaptureCommand();
|
|
33
|
+
const child = spawn(shell, ["-l", "-c", captureCmd], {
|
|
34
34
|
stdio: ["ignore", "pipe", "ignore"],
|
|
35
35
|
timeout: 5000,
|
|
36
36
|
});
|
|
@@ -119,6 +119,9 @@ Usage: agent-sh [options]
|
|
|
119
119
|
agent-sh install <spec> [--force] Install an extension (bundled name, file:, npm:, github:)
|
|
120
120
|
agent-sh uninstall <name> Remove an installed extension
|
|
121
121
|
agent-sh list List installed extensions
|
|
122
|
+
agent-sh auth login [provider] Store an API key for a built-in provider
|
|
123
|
+
agent-sh auth logout <provider> Remove a stored key
|
|
124
|
+
agent-sh auth list Show configured providers
|
|
122
125
|
|
|
123
126
|
Provider Profiles:
|
|
124
127
|
--provider <name> Use a provider from ~/.agent-sh/settings.json
|
|
@@ -161,24 +164,9 @@ Inside the shell:
|
|
|
161
164
|
return { shell, model, extensions, apiKey, baseURL, provider, backend };
|
|
162
165
|
}
|
|
163
166
|
async function main() {
|
|
164
|
-
// Subcommands — handled before the shell-launch path.
|
|
165
167
|
const rawArgs = process.argv.slice(2);
|
|
166
|
-
if (rawArgs
|
|
167
|
-
runInit({ force: rawArgs.includes("--force") });
|
|
168
|
+
if (await dispatchSubcommand(rawArgs))
|
|
168
169
|
return;
|
|
169
|
-
}
|
|
170
|
-
if (rawArgs[0] === "install") {
|
|
171
|
-
await runInstall(rawArgs[1] ?? "", { force: rawArgs.includes("--force") });
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
if (rawArgs[0] === "uninstall") {
|
|
175
|
-
await runUninstall(rawArgs[1] ?? "");
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
if (rawArgs[0] === "list") {
|
|
179
|
-
runList();
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
170
|
if (process.env.AGENT_SH) {
|
|
183
171
|
console.error("agent-sh: already running inside an agent-sh session (nested sessions are not supported).");
|
|
184
172
|
process.exit(1);
|
|
@@ -212,6 +200,13 @@ async function main() {
|
|
|
212
200
|
catch {
|
|
213
201
|
// Ignore errors, we already have process.env as fallback
|
|
214
202
|
}
|
|
203
|
+
if (!config.apiKey && !config.provider && !anyProviderConfigured()) {
|
|
204
|
+
console.error("\nagent-sh: no LLM provider configured.\n\n" +
|
|
205
|
+
" Run `agent-sh auth login` to store an API key, or\n" +
|
|
206
|
+
" export OPENAI_API_KEY / OPENROUTER_API_KEY / DEEPSEEK_API_KEY, or\n" +
|
|
207
|
+
" run `agent-sh init` for a settings.json template.\n");
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
215
210
|
// ── Core (frontend-agnostic) ──────────────────────────────────
|
|
216
211
|
const core = createCore(config);
|
|
217
212
|
const { bus } = core;
|
|
@@ -239,6 +234,7 @@ async function main() {
|
|
|
239
234
|
const extCtx = core.extensionContext({ quit: cleanup });
|
|
240
235
|
// Before loadExtensions: extensions look up shell handlers at activation.
|
|
241
236
|
registerShellHandlers(extCtx);
|
|
237
|
+
activateAgent(extCtx);
|
|
242
238
|
// Load before spawning the shell so PS1 lands below the banner.
|
|
243
239
|
await loadBuiltinExtensions(extCtx, getSettings().disabledBuiltins);
|
|
244
240
|
const loadExtensionsTimeoutMs = 10000;
|
|
@@ -340,6 +336,7 @@ async function main() {
|
|
|
340
336
|
if (process.stdin.isTTY) {
|
|
341
337
|
try {
|
|
342
338
|
process.stdin.setRawMode(true);
|
|
339
|
+
clearOpost();
|
|
343
340
|
}
|
|
344
341
|
catch {
|
|
345
342
|
// May fail if stdin is not a TTY
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { CONFIG_DIR } from "
|
|
3
|
+
import { CONFIG_DIR } from "../core/settings.js";
|
|
4
4
|
const EXTENSIONS_DIR = path.join(CONFIG_DIR, "extensions");
|
|
5
5
|
const SETTINGS_PATH = path.join(CONFIG_DIR, "settings.json");
|
|
6
6
|
const EXAMPLE_PATH = path.join(CONFIG_DIR, "settings.example.json");
|
|
@@ -2,13 +2,13 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
|
-
import { CONFIG_DIR, getSettings } from "
|
|
5
|
+
import { CONFIG_DIR, getSettings } from "../core/settings.js";
|
|
6
6
|
// Kept in sync with extension-loader.ts SCRIPT_EXTS.
|
|
7
7
|
const SCRIPT_EXTS = [".js", ".mjs", ".ts", ".tsx", ".mts"];
|
|
8
8
|
function hasIndexFile(dir) {
|
|
9
9
|
return SCRIPT_EXTS.some((ext) => fs.existsSync(path.join(dir, `index${ext}`)));
|
|
10
10
|
}
|
|
11
|
-
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "
|
|
11
|
+
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../");
|
|
12
12
|
const BUNDLED_DIR = path.join(PACKAGE_ROOT, "examples/extensions");
|
|
13
13
|
const EXT_DIR = path.join(CONFIG_DIR, "extensions");
|
|
14
14
|
export function listBundled() {
|
|
@@ -75,6 +75,34 @@ function readPackageJson(target) {
|
|
|
75
75
|
return null;
|
|
76
76
|
return JSON.parse(fs.readFileSync(pkgJson, "utf-8"));
|
|
77
77
|
}
|
|
78
|
+
/** Relative `file:` deps in bundled extensions (e.g. `"agent-sh": "file:../../.."`)
|
|
79
|
+
* point at the wrong location after the source is copied into ~/.agent-sh/extensions/.
|
|
80
|
+
* Resolve them against the original source dir so npm install in the target succeeds. */
|
|
81
|
+
function rewriteFileDeps(target, sourcePath) {
|
|
82
|
+
const pkgJson = path.join(target, "package.json");
|
|
83
|
+
if (!fs.existsSync(pkgJson))
|
|
84
|
+
return;
|
|
85
|
+
const raw = fs.readFileSync(pkgJson, "utf-8");
|
|
86
|
+
const pkg = JSON.parse(raw);
|
|
87
|
+
const sections = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
|
|
88
|
+
let changed = false;
|
|
89
|
+
for (const section of sections) {
|
|
90
|
+
const deps = pkg[section];
|
|
91
|
+
if (!deps || typeof deps !== "object")
|
|
92
|
+
continue;
|
|
93
|
+
for (const [name, spec] of Object.entries(deps)) {
|
|
94
|
+
if (typeof spec !== "string" || !spec.startsWith("file:"))
|
|
95
|
+
continue;
|
|
96
|
+
const rel = spec.slice("file:".length);
|
|
97
|
+
if (path.isAbsolute(rel))
|
|
98
|
+
continue;
|
|
99
|
+
deps[name] = `file:${path.resolve(sourcePath, rel)}`;
|
|
100
|
+
changed = true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (changed)
|
|
104
|
+
fs.writeFileSync(pkgJson, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
105
|
+
}
|
|
78
106
|
function maybeNpmInstall(target, pkg) {
|
|
79
107
|
const deps = { ...(pkg.dependencies ?? {}), ...(pkg.peerDependencies ?? {}) };
|
|
80
108
|
if (Object.keys(deps).length === 0)
|
|
@@ -170,6 +198,7 @@ export async function runInstall(spec, opts = {}) {
|
|
|
170
198
|
if (resolved.isDirectory) {
|
|
171
199
|
fs.cpSync(resolved.sourcePath, target, { recursive: true });
|
|
172
200
|
try {
|
|
201
|
+
rewriteFileDeps(target, resolved.sourcePath);
|
|
173
202
|
const pkg = readPackageJson(target);
|
|
174
203
|
if (pkg) {
|
|
175
204
|
maybeNpmInstall(target, pkg);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function dispatchSubcommand(argv: string[]): Promise<boolean>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { runInit } from "./init.js";
|
|
2
|
+
import { runInstall, runUninstall, runList } from "./install.js";
|
|
3
|
+
import { runAuth } from "./auth/cli.js";
|
|
4
|
+
const SUBCOMMANDS = {
|
|
5
|
+
init: (args) => runInit({ force: args.includes("--force") }),
|
|
6
|
+
install: (args) => runInstall(args[0] ?? "", { force: args.includes("--force") }),
|
|
7
|
+
uninstall: (args) => runUninstall(args[0] ?? ""),
|
|
8
|
+
list: () => runList(),
|
|
9
|
+
auth: (args) => runAuth(args),
|
|
10
|
+
};
|
|
11
|
+
export async function dispatchSubcommand(argv) {
|
|
12
|
+
const handler = SUBCOMMANDS[argv[0] ?? ""];
|
|
13
|
+
if (!handler)
|
|
14
|
+
return false;
|
|
15
|
+
await handler(argv.slice(1));
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { AgentMode } from "
|
|
2
|
-
import type { ToolResultDisplay } from "
|
|
1
|
+
import type { AgentMode } from "../agent/host-types.js";
|
|
2
|
+
import type { ToolResultDisplay } from "../agent/types.js";
|
|
3
3
|
/**
|
|
4
4
|
* Typed event map — every event has a known payload shape.
|
|
5
5
|
*/
|
|
@@ -50,7 +50,7 @@ export interface ShellEvents {
|
|
|
50
50
|
"agent:append-user-message": {
|
|
51
51
|
text: string;
|
|
52
52
|
};
|
|
53
|
-
"input-mode:register": import("
|
|
53
|
+
"input-mode:register": import("../shell/host-types.js").InputModeConfig;
|
|
54
54
|
"agent:query": {
|
|
55
55
|
query: string;
|
|
56
56
|
};
|
|
@@ -136,6 +136,8 @@ export interface ShellEvents {
|
|
|
136
136
|
rawInput?: unknown;
|
|
137
137
|
/** Pre-formatted display detail from tool's formatCall(). */
|
|
138
138
|
displayDetail?: string;
|
|
139
|
+
/** highlight.js-style identifier for syntax-highlighting `rawInput.source`. */
|
|
140
|
+
sourceLanguage?: string;
|
|
139
141
|
batchIndex?: number;
|
|
140
142
|
batchTotal?: number;
|
|
141
143
|
};
|
|
@@ -162,14 +164,6 @@ export interface ShellEvents {
|
|
|
162
164
|
};
|
|
163
165
|
"tool:interactive-start": Record<string, never>;
|
|
164
166
|
"tool:interactive-end": Record<string, never>;
|
|
165
|
-
"permission:request": {
|
|
166
|
-
kind: string;
|
|
167
|
-
title: string;
|
|
168
|
-
metadata: Record<string, unknown>;
|
|
169
|
-
/** Interactive UI capability — available when the built-in agent is active. */
|
|
170
|
-
ui?: unknown;
|
|
171
|
-
decision: Record<string, unknown>;
|
|
172
|
-
};
|
|
173
167
|
"command:register": {
|
|
174
168
|
name: string;
|
|
175
169
|
description: string;
|
|
@@ -336,14 +330,14 @@ export interface ShellEvents {
|
|
|
336
330
|
reasoningParams?: (level: string, model?: string) => Record<string, unknown>;
|
|
337
331
|
};
|
|
338
332
|
"agent:register-tool": {
|
|
339
|
-
tool: import("
|
|
333
|
+
tool: import("../agent/types.js").ToolDefinition;
|
|
340
334
|
extensionName?: string;
|
|
341
335
|
};
|
|
342
336
|
"agent:unregister-tool": {
|
|
343
337
|
name: string;
|
|
344
338
|
};
|
|
345
339
|
"agent:get-tools": {
|
|
346
|
-
tools: import("
|
|
340
|
+
tools: import("../agent/types.js").ToolDefinition[];
|
|
347
341
|
};
|
|
348
342
|
"agent:register-instruction": {
|
|
349
343
|
name: string;
|
|
@@ -23,7 +23,6 @@ async function ensureTsSupport(force = false) {
|
|
|
23
23
|
if (tsRegistered && !force)
|
|
24
24
|
return;
|
|
25
25
|
try {
|
|
26
|
-
// Unregister previous loader if reloading
|
|
27
26
|
if (tsxUnregister) {
|
|
28
27
|
try {
|
|
29
28
|
await tsxUnregister();
|
|
@@ -39,70 +38,84 @@ async function ensureTsSupport(force = false) {
|
|
|
39
38
|
}
|
|
40
39
|
}
|
|
41
40
|
/**
|
|
42
|
-
* Wrap an ExtensionContext to track all registrations (bus.on,
|
|
43
|
-
*
|
|
44
|
-
*
|
|
41
|
+
* Wrap an ExtensionContext to track all registrations (bus.on, advise,
|
|
42
|
+
* command:register, plus all agent/shell surface registrars). Returns
|
|
43
|
+
* the wrapped context and a dispose() that tears down everything
|
|
44
|
+
* registered through it.
|
|
45
45
|
*/
|
|
46
46
|
function createScopedContext(ctx, extensionName) {
|
|
47
47
|
const cleanups = [];
|
|
48
48
|
const bus = ctx.bus;
|
|
49
49
|
const scopedBus = Object.create(bus);
|
|
50
|
-
// Track bus.on registrations
|
|
51
50
|
scopedBus.on = ((event, fn) => {
|
|
52
51
|
bus.on(event, fn);
|
|
53
52
|
cleanups.push(() => bus.off(event, fn));
|
|
54
53
|
});
|
|
55
|
-
// Track bus.onPipe registrations
|
|
56
54
|
scopedBus.onPipe = ((event, fn) => {
|
|
57
55
|
bus.onPipe(event, fn);
|
|
58
56
|
cleanups.push(() => bus.offPipe(event, fn));
|
|
59
57
|
});
|
|
60
|
-
//
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
cleanups.push(
|
|
64
|
-
return
|
|
58
|
+
// Wrap any (name, fn) → unsubscribe registrar so its disposer runs on teardown.
|
|
59
|
+
const trackUnsub = (fn) => (a, b) => {
|
|
60
|
+
const unsub = fn(a, b);
|
|
61
|
+
cleanups.push(unsub);
|
|
62
|
+
return unsub;
|
|
65
63
|
};
|
|
66
|
-
//
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
};
|
|
71
|
-
// Track skill registrations — extension name captured in scope
|
|
72
|
-
const scopedRegisterSkill = (name, description, filePath) => {
|
|
73
|
-
bus.emit("agent:register-skill", { name, description, filePath, extensionName });
|
|
74
|
-
cleanups.push(() => bus.emit("agent:remove-skill", { name }));
|
|
75
|
-
};
|
|
76
|
-
// Track dynamic-context producer registrations
|
|
77
|
-
const scopedRegisterContextProducer = (name, producer) => {
|
|
78
|
-
const dispose = ctx.registerContextProducer(name, producer);
|
|
79
|
-
cleanups.push(dispose);
|
|
80
|
-
return dispose;
|
|
81
|
-
};
|
|
82
|
-
// Track tool registrations — extension name captured in scope
|
|
83
|
-
const scopedRegisterTool = (tool) => {
|
|
84
|
-
bus.emit("agent:register-tool", { tool, extensionName });
|
|
85
|
-
cleanups.push(() => bus.emit("agent:unregister-tool", { name: tool.name }));
|
|
86
|
-
};
|
|
87
|
-
// Track slash command registrations — without this, reloading an
|
|
88
|
-
// extension stacks its commands (old `/status` + new `/status`) in
|
|
89
|
-
// the slash-commands registry.
|
|
64
|
+
// ── substrate / sugar ──────────────────────────────────────
|
|
65
|
+
const scopedAdvise = trackUnsub(ctx.advise);
|
|
66
|
+
// Without this, reloading an extension stacks its commands (old + new)
|
|
67
|
+
// in the slash-commands registry.
|
|
90
68
|
const scopedRegisterCommand = (name, description, handler) => {
|
|
91
69
|
ctx.registerCommand(name, description, handler);
|
|
92
70
|
cleanups.push(() => bus.emit("command:unregister", { name }));
|
|
93
71
|
};
|
|
72
|
+
const scopedAdviseCommand = trackUnsub(ctx.adviseCommand);
|
|
73
|
+
// ── agent surface (optional — bridge backends omit it) ───
|
|
74
|
+
const agent = ctx.agent;
|
|
75
|
+
let scopedAgent;
|
|
76
|
+
if (agent) {
|
|
77
|
+
scopedAgent = {
|
|
78
|
+
...agent,
|
|
79
|
+
registerTool: (tool) => {
|
|
80
|
+
bus.emit("agent:register-tool", { tool, extensionName });
|
|
81
|
+
cleanups.push(() => bus.emit("agent:unregister-tool", { name: tool.name }));
|
|
82
|
+
},
|
|
83
|
+
adviseTool: trackUnsub(agent.adviseTool),
|
|
84
|
+
adviseToolSchema: trackUnsub(agent.adviseToolSchema),
|
|
85
|
+
registerInstruction: (name, text) => {
|
|
86
|
+
bus.emit("agent:register-instruction", { name, text, extensionName });
|
|
87
|
+
cleanups.push(() => bus.emit("agent:remove-instruction", { name }));
|
|
88
|
+
},
|
|
89
|
+
adviseInstruction: trackUnsub(agent.adviseInstruction),
|
|
90
|
+
registerSkill: (name, description, filePath) => {
|
|
91
|
+
bus.emit("agent:register-skill", { name, description, filePath, extensionName });
|
|
92
|
+
cleanups.push(() => bus.emit("agent:remove-skill", { name }));
|
|
93
|
+
},
|
|
94
|
+
adviseSkill: trackUnsub(agent.adviseSkill),
|
|
95
|
+
registerContextProducer: (name, producer, opts) => {
|
|
96
|
+
const dispose = agent.registerContextProducer(name, producer, opts);
|
|
97
|
+
cleanups.push(dispose);
|
|
98
|
+
return dispose;
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
// ── shell surface (optional — headless backends omit it) ──
|
|
103
|
+
const shell = ctx.shell;
|
|
104
|
+
let scopedShell;
|
|
105
|
+
if (shell) {
|
|
106
|
+
scopedShell = {
|
|
107
|
+
...shell,
|
|
108
|
+
adviseInputMode: trackUnsub(shell.adviseInputMode),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
94
111
|
const scoped = {
|
|
95
112
|
...ctx,
|
|
96
113
|
bus: scopedBus,
|
|
97
114
|
advise: scopedAdvise,
|
|
98
|
-
registerInstruction: scopedRegisterInstruction,
|
|
99
|
-
removeInstruction: ctx.removeInstruction,
|
|
100
|
-
registerSkill: scopedRegisterSkill,
|
|
101
|
-
removeSkill: ctx.removeSkill,
|
|
102
|
-
registerContextProducer: scopedRegisterContextProducer,
|
|
103
|
-
registerTool: scopedRegisterTool,
|
|
104
|
-
unregisterTool: ctx.unregisterTool,
|
|
105
115
|
registerCommand: scopedRegisterCommand,
|
|
116
|
+
adviseCommand: scopedAdviseCommand,
|
|
117
|
+
agent: scopedAgent,
|
|
118
|
+
shell: scopedShell,
|
|
106
119
|
onDispose: (fn) => { cleanups.push(fn); },
|
|
107
120
|
};
|
|
108
121
|
const dispose = () => {
|
|
@@ -116,7 +129,6 @@ function createScopedContext(ctx, extensionName) {
|
|
|
116
129
|
};
|
|
117
130
|
return { scoped, dispose };
|
|
118
131
|
}
|
|
119
|
-
// Track disposers for user extensions so reload can tear them down
|
|
120
132
|
const extensionDisposers = new Map();
|
|
121
133
|
/**
|
|
122
134
|
* Load extensions from three sources (merged, deduplicated):
|
|
@@ -134,19 +146,15 @@ const extensionDisposers = new Map();
|
|
|
134
146
|
*/
|
|
135
147
|
export async function loadExtensions(ctx, cliExtensions) {
|
|
136
148
|
const specifiers = [];
|
|
137
|
-
// 1. CLI -e / --extensions
|
|
138
149
|
if (cliExtensions) {
|
|
139
150
|
specifiers.push(...cliExtensions);
|
|
140
151
|
}
|
|
141
|
-
// 2. settings.json
|
|
142
152
|
const settings = getSettings();
|
|
143
153
|
if (settings.extensions.length > 0) {
|
|
144
154
|
specifiers.push(...settings.extensions);
|
|
145
155
|
}
|
|
146
|
-
// 3. ~/.agent-sh/extensions/ directory
|
|
147
156
|
const userSpecifiers = await discoverUserExtensions();
|
|
148
157
|
specifiers.push(...userSpecifiers);
|
|
149
|
-
// Deduplicate
|
|
150
158
|
const seen = new Set();
|
|
151
159
|
const unique = specifiers.filter((s) => {
|
|
152
160
|
if (seen.has(s))
|
|
@@ -154,8 +162,7 @@ export async function loadExtensions(ctx, cliExtensions) {
|
|
|
154
162
|
seen.add(s);
|
|
155
163
|
return true;
|
|
156
164
|
});
|
|
157
|
-
|
|
158
|
-
const loaded = await loadSpecifiers(unique, ctx, false, userSpecifiers);
|
|
165
|
+
const loaded = await loadSpecifiers(unique, ctx, false);
|
|
159
166
|
return loaded;
|
|
160
167
|
}
|
|
161
168
|
async function discoverUserExtensions() {
|
|
@@ -188,8 +195,7 @@ async function discoverUserExtensions() {
|
|
|
188
195
|
}
|
|
189
196
|
return specifiers;
|
|
190
197
|
}
|
|
191
|
-
async function loadSpecifiers(specifiers, ctx, bustCache
|
|
192
|
-
const userSet = new Set(userSpecifiers ?? []);
|
|
198
|
+
async function loadSpecifiers(specifiers, ctx, bustCache) {
|
|
193
199
|
const loaded = [];
|
|
194
200
|
for (const specifier of specifiers) {
|
|
195
201
|
try {
|
|
@@ -197,7 +203,6 @@ async function loadSpecifiers(specifiers, ctx, bustCache, userSpecifiers) {
|
|
|
197
203
|
if (TS_EXTS.some((ext) => importPath.endsWith(ext))) {
|
|
198
204
|
await ensureTsSupport(bustCache);
|
|
199
205
|
}
|
|
200
|
-
// Append timestamp query to bust Node's module cache on reload
|
|
201
206
|
if (bustCache) {
|
|
202
207
|
const sep = importPath.includes("?") ? "&" : "?";
|
|
203
208
|
importPath += `${sep}t=${Date.now()}`;
|
|
@@ -212,23 +217,13 @@ async function loadSpecifiers(specifiers, ctx, bustCache, userSpecifiers) {
|
|
|
212
217
|
if (typeof activate === "function") {
|
|
213
218
|
const base = path.basename(specifier).replace(/\.(ts|js|mjs|mts|tsx)$/, "");
|
|
214
219
|
const name = base === "index" ? path.basename(path.dirname(specifier)) : base;
|
|
215
|
-
// Scoped context so /reload can tear user extensions down.
|
|
216
220
|
// Awaiting activate() lets extensions with async setup (e.g.
|
|
217
221
|
// openrouter fetching its model catalog) finish before we move
|
|
218
222
|
// on; a 10s outer timeout in index.ts guards against hangs.
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
await activate(scoped);
|
|
224
|
-
extensionDisposers.set(name, dispose);
|
|
225
|
-
}
|
|
226
|
-
else {
|
|
227
|
-
const { scoped, dispose } = createScopedContext(ctx, name);
|
|
228
|
-
await activate(scoped);
|
|
229
|
-
// Non-user extensions aren't reloadable, but track for cleanup on shutdown
|
|
230
|
-
extensionDisposers.set(name, dispose);
|
|
231
|
-
}
|
|
223
|
+
extensionDisposers.get(name)?.();
|
|
224
|
+
const { scoped, dispose } = createScopedContext(ctx, name);
|
|
225
|
+
await activate(scoped);
|
|
226
|
+
extensionDisposers.set(name, dispose);
|
|
232
227
|
loaded.push(name);
|
|
233
228
|
}
|
|
234
229
|
}
|
|
@@ -246,7 +241,7 @@ async function loadSpecifiers(specifiers, ctx, bustCache, userSpecifiers) {
|
|
|
246
241
|
*/
|
|
247
242
|
export async function reloadExtensions(ctx) {
|
|
248
243
|
const specifiers = await discoverUserExtensions();
|
|
249
|
-
return loadSpecifiers(specifiers, ctx, true
|
|
244
|
+
return loadSpecifiers(specifiers, ctx, true);
|
|
250
245
|
}
|
|
251
246
|
/**
|
|
252
247
|
* Find an index file in a directory extension.
|
|
@@ -284,15 +279,12 @@ async function resolveSpecifier(specifier) {
|
|
|
284
279
|
// Scoped packages ("@scope/pkg") contain "/" but are npm specifiers,
|
|
285
280
|
// so the "@" prefix takes precedence over the "/" heuristic.
|
|
286
281
|
if (specifier.includes("/") && !specifier.startsWith("@")) {
|
|
287
|
-
// Treat as relative path from cwd
|
|
288
282
|
resolved = path.resolve(process.cwd(), specifier);
|
|
289
283
|
}
|
|
290
284
|
else {
|
|
291
|
-
// Bare specifier — npm package (including @scope/pkg)
|
|
292
285
|
return specifier;
|
|
293
286
|
}
|
|
294
287
|
}
|
|
295
|
-
// If it's a directory, find the index file
|
|
296
288
|
try {
|
|
297
289
|
const stat = await fs.stat(resolved);
|
|
298
290
|
if (stat.isDirectory()) {
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* subscribing to bus events. Shell-specific tracking lives in the
|
|
7
7
|
* shell-context built-in extension.
|
|
8
8
|
*
|
|
9
|
-
* Agent backends
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Agent backends register themselves via the agent:register-backend bus
|
|
10
|
+
* event. The built-in "ash" backend lives in src/agent/ and is activated
|
|
11
|
+
* by hosts via activateAgent().
|
|
12
12
|
*
|
|
13
13
|
* Usage:
|
|
14
14
|
* import { createCore } from "agent-sh";
|
|
@@ -18,18 +18,21 @@
|
|
|
18
18
|
* const response = await core.query("hello");
|
|
19
19
|
*/
|
|
20
20
|
import { EventBus } from "./event-bus.js";
|
|
21
|
-
import type {
|
|
22
|
-
import { HandlerRegistry } from "
|
|
21
|
+
import type { AppConfig, ExtensionContext } from "../shell/host-types.js";
|
|
22
|
+
import { HandlerRegistry } from "../utils/handler-registry.js";
|
|
23
23
|
export { EventBus } from "./event-bus.js";
|
|
24
|
-
export type { ShellEvents } from "./event-bus.js";
|
|
25
|
-
export type {
|
|
26
|
-
export {
|
|
27
|
-
export type {
|
|
28
|
-
export
|
|
29
|
-
export {
|
|
30
|
-
export {
|
|
31
|
-
export {
|
|
32
|
-
export
|
|
24
|
+
export type { ShellEvents, ContentBlock } from "./event-bus.js";
|
|
25
|
+
export type { CoreContext, CoreConfig } from "./types.js";
|
|
26
|
+
export type { AgentContext, AgentConfig, AgentSurface, AgentConfigSurface, AgentMode, LlmInterface, LlmMessage, LlmSession } from "../agent/host-types.js";
|
|
27
|
+
export type { ShellContext, ShellConfig, ShellSurface, ShellConfigSurface, ExtensionContext, RemoteSession, RemoteSessionOptions, RenderSurface, InputModeConfig, TerminalSession, BlockTransformOptions, FencedBlockTransformOptions, AppConfig } from "../shell/host-types.js";
|
|
28
|
+
export { palette, setPalette, resetPalette } from "../utils/palette.js";
|
|
29
|
+
export type { ColorPalette } from "../utils/palette.js";
|
|
30
|
+
export type { AgentBackend, ToolDefinition } from "../agent/types.js";
|
|
31
|
+
export { runSubagent, type SubagentOptions } from "../agent/subagent.js";
|
|
32
|
+
export { LlmClient } from "../utils/llm-client.js";
|
|
33
|
+
export { HistoryFile, InMemoryHistory, NoopHistory, type HistoryAdapter } from "../agent/history-file.js";
|
|
34
|
+
export type { NuclearEntry } from "../agent/nuclear-form.js";
|
|
35
|
+
export { compileSearchRegex, matchEntry, formatNuclearLine } from "../agent/nuclear-form.js";
|
|
33
36
|
export interface AgentShellCore {
|
|
34
37
|
bus: EventBus;
|
|
35
38
|
/** Handler registry for define/advise/call. */
|
|
@@ -51,4 +54,4 @@ export interface AgentShellCore {
|
|
|
51
54
|
/** Tear down the agent and clean up. */
|
|
52
55
|
kill(): void;
|
|
53
56
|
}
|
|
54
|
-
export declare function createCore(config:
|
|
57
|
+
export declare function createCore(config: AppConfig): AgentShellCore;
|