agent-sh 0.12.26 → 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 +44 -100
- package/dist/agent/conversation-state.d.ts +9 -0
- package/dist/agent/conversation-state.js +38 -1
- 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} +114 -5
- 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 +17 -5
- package/dist/utils/floating-panel.js +218 -70
- 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 +364 -0
- 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 +35 -10
- 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 +174 -33
- package/examples/extensions/tmux-pane.ts +6 -4
- package/examples/extensions/tunnel-vision.ts +405 -0
- package/examples/extensions/user-shell.ts +1 -1
- package/examples/extensions/web-access.ts +8 -113
- 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
package/README.md
CHANGED
|
@@ -43,9 +43,9 @@ npm run build # produces dist/
|
|
|
43
43
|
npm link # exposes `agent-sh` globally
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
Requires Node.js 18+.
|
|
46
|
+
Requires Node.js 18+. Supports **bash**, **zsh**, and **fish**; other shells (nushell, etc.) are not yet wired up.
|
|
47
47
|
|
|
48
|
-
**Windows:** the interactive shell layer is bash/zsh-only. Run agent-sh inside **WSL** for the full experience. Native Windows (cmd.exe / PowerShell) is not supported as the host shell, though headless / library / ACP-bridge usage may work — file an issue if you hit a gap.
|
|
48
|
+
**Windows:** the interactive shell layer is bash/zsh/fish-only. Run agent-sh inside **WSL** for the full experience. Native Windows (cmd.exe / PowerShell) is not supported as the host shell, though headless / library / ACP-bridge usage may work — file an issue if you hit a gap.
|
|
49
49
|
|
|
50
50
|
Tip — add a shell alias:
|
|
51
51
|
|
|
@@ -74,6 +74,17 @@ See [Bring your own agent](#bring-your-own-agent) below for full details and the
|
|
|
74
74
|
|
|
75
75
|
`ash` is agent-sh's own lightweight agent. It works with any OpenAI-compatible API — pick one of the zero-config paths below, no settings file needed. agent-sh auto-activates a built-in provider when it sees a known key.
|
|
76
76
|
|
|
77
|
+
**Quickest path** — store a key once via the auth subcommand:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
agent-sh auth login # picks a provider interactively
|
|
81
|
+
agent-sh # launches with the saved key
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Keys are written to `~/.agent-sh/keys.json` (chmod 0600). Resolution order is `settings.json` → `keys.json` → env var, so an env var or settings entry will still win when present. `auth login` also accepts any provider you declare under `providers` in `settings.json` — useful for custom OpenAI-compatible endpoints where the URL is committable but the key shouldn't be.
|
|
85
|
+
|
|
86
|
+
Or export the env var directly:
|
|
87
|
+
|
|
77
88
|
**Hosted models via OpenRouter** (300+ models, one key):
|
|
78
89
|
|
|
79
90
|
```bash
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
* agent:tool-completed, agent:tool-output
|
|
12
12
|
* - agent:thinking-chunk, agent:cancelled, agent:error
|
|
13
13
|
*/
|
|
14
|
-
import type { EventBus } from "../event-bus.js";
|
|
15
|
-
import type { AgentMode } from "
|
|
14
|
+
import type { EventBus } from "../core/event-bus.js";
|
|
15
|
+
import type { AgentMode } from "./host-types.js";
|
|
16
16
|
import type { LlmClient } from "../utils/llm-client.js";
|
|
17
17
|
import type { HandlerFunctions } from "../utils/handler-registry.js";
|
|
18
18
|
import type { AgentBackend, ToolDefinition } from "./types.js";
|
|
@@ -67,7 +67,7 @@ export declare class AgentLoop implements AgentBackend {
|
|
|
67
67
|
wire(): void;
|
|
68
68
|
/** Unsubscribe from bus events — deactivates this backend. */
|
|
69
69
|
unwire(): void;
|
|
70
|
-
/** Register a tool (used by extensions via ctx.registerTool). */
|
|
70
|
+
/** Register a tool (used by extensions via ctx.agent.registerTool). */
|
|
71
71
|
registerTool(tool: ToolDefinition): void;
|
|
72
72
|
/** Unregister a tool by name. */
|
|
73
73
|
unregisterTool(name: string): void;
|
|
@@ -81,11 +81,9 @@ export declare class AgentLoop implements AgentBackend {
|
|
|
81
81
|
private toolExtensions;
|
|
82
82
|
/** Register a named instruction block for the system prompt. */
|
|
83
83
|
registerInstruction(name: string, text: string, extensionName: string): void;
|
|
84
|
-
/** Remove a named instruction block. */
|
|
85
84
|
removeInstruction(name: string): void;
|
|
86
85
|
/** Register a named skill (on-demand reference material). */
|
|
87
86
|
registerSkill(name: string, description: string, filePath: string, extensionName: string): void;
|
|
88
|
-
/** Remove a registered skill. */
|
|
89
87
|
removeSkill(name: string): void;
|
|
90
88
|
/**
|
|
91
89
|
* Build the system prompt grouped by extension.
|
package/dist/agent/agent-loop.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { setMaxListeners } from "node:events";
|
|
2
|
-
import * as fs from "node:fs/promises";
|
|
3
2
|
import * as path from "node:path";
|
|
4
|
-
import * as os from "node:os";
|
|
5
|
-
import { computeDiff, computeEditDiff, computeInputDiff } from "../utils/diff.js";
|
|
6
3
|
import { ToolRegistry } from "./tool-registry.js";
|
|
7
4
|
import { normalizeToolArgs } from "./normalize-args.js";
|
|
8
5
|
import { ConversationState } from "./conversation-state.js";
|
|
@@ -13,12 +10,12 @@ import { createToolUI } from "../utils/tool-interactive.js";
|
|
|
13
10
|
import { RESPONSE_RESERVE, DEFAULT_CONTEXT_WINDOW } from "./token-budget.js";
|
|
14
11
|
import { PACKAGE_VERSION } from "../utils/package-version.js";
|
|
15
12
|
import { wrapTrailingWithDynamicContext } from "../utils/message-utils.js";
|
|
16
|
-
import { getSettings, updateSettings } from "../settings.js";
|
|
13
|
+
import { getSettings, updateSettings } from "../core/settings.js";
|
|
17
14
|
import { createToolProtocol } from "./tool-protocol.js";
|
|
18
15
|
// Core tool factories
|
|
19
16
|
import { createBashTool } from "./tools/bash.js";
|
|
20
17
|
import { createPwshTool } from "./tools/pwsh.js";
|
|
21
|
-
import { findBash } from "../executor.js";
|
|
18
|
+
import { findBash } from "../utils/executor.js";
|
|
22
19
|
import { createReadFileTool } from "./tools/read-file.js";
|
|
23
20
|
import { createWriteFileTool } from "./tools/write-file.js";
|
|
24
21
|
import { createEditFileTool } from "./tools/edit-file.js";
|
|
@@ -52,7 +49,7 @@ function summarizeDescription(desc) {
|
|
|
52
49
|
}
|
|
53
50
|
export class AgentLoop {
|
|
54
51
|
abortController = null;
|
|
55
|
-
toolRegistry
|
|
52
|
+
toolRegistry;
|
|
56
53
|
history;
|
|
57
54
|
conversation;
|
|
58
55
|
fileReadCache = new Map();
|
|
@@ -104,6 +101,7 @@ export class AgentLoop {
|
|
|
104
101
|
this.handlers = config.handlers;
|
|
105
102
|
this.compositor = config.compositor ?? null;
|
|
106
103
|
this.instanceId = config.instanceId ?? "unknown";
|
|
104
|
+
this.toolRegistry = new ToolRegistry(this.handlers);
|
|
107
105
|
// Shell-history-shaped log. Default writes go through the advisable
|
|
108
106
|
// `history:append` handler registered below; extensions swap the
|
|
109
107
|
// backend without touching this wiring.
|
|
@@ -117,7 +115,7 @@ export class AgentLoop {
|
|
|
117
115
|
: [{ model: config.llmClient.model }];
|
|
118
116
|
this.currentModeIndex = config.initialModeIndex ?? 0;
|
|
119
117
|
// Tool protocol — controls how tools are presented to the LLM
|
|
120
|
-
this.toolProtocol = createToolProtocol(getSettings().toolMode ?? "api");
|
|
118
|
+
this.toolProtocol = createToolProtocol(getSettings().toolMode ?? "api", getSettings().coreTools ?? []);
|
|
121
119
|
// Register core tools
|
|
122
120
|
this.registerCoreTools();
|
|
123
121
|
// Register any protocol-provided tools (e.g. load_tool for deferred-lookup).
|
|
@@ -291,7 +289,6 @@ export class AgentLoop {
|
|
|
291
289
|
return;
|
|
292
290
|
}
|
|
293
291
|
this.thinkingLevel = level;
|
|
294
|
-
this.bus.emit("ui:info", { message: `Thinking: ${level}` });
|
|
295
292
|
this.bus.emit("config:changed", {});
|
|
296
293
|
});
|
|
297
294
|
onPipe("config:get-thinking", () => {
|
|
@@ -387,7 +384,7 @@ export class AgentLoop {
|
|
|
387
384
|
}
|
|
388
385
|
this.boundPipeListeners = [];
|
|
389
386
|
}
|
|
390
|
-
/** Register a tool (used by extensions via ctx.registerTool). */
|
|
387
|
+
/** Register a tool (used by extensions via ctx.agent.registerTool). */
|
|
391
388
|
registerTool(tool) {
|
|
392
389
|
this.toolRegistry.register(tool);
|
|
393
390
|
}
|
|
@@ -409,18 +406,23 @@ export class AgentLoop {
|
|
|
409
406
|
/** Register a named instruction block for the system prompt. */
|
|
410
407
|
registerInstruction(name, text, extensionName) {
|
|
411
408
|
this.instructions.set(name, { text, extensionName });
|
|
409
|
+
this.handlers.define(`instruction:${name}`, () => this.instructions.get(name)?.text ?? "");
|
|
412
410
|
}
|
|
413
|
-
/** Remove a named instruction block. */
|
|
414
411
|
removeInstruction(name) {
|
|
415
412
|
this.instructions.delete(name);
|
|
413
|
+
// Handler entry retained so external advisors survive a reload of the owner.
|
|
416
414
|
}
|
|
417
415
|
/** Register a named skill (on-demand reference material). */
|
|
418
416
|
registerSkill(name, description, filePath, extensionName) {
|
|
419
417
|
this.skills.set(name, { description, filePath, extensionName });
|
|
418
|
+
this.handlers.define(`skill:${name}:view`, () => {
|
|
419
|
+
const s = this.skills.get(name);
|
|
420
|
+
return { description: s?.description ?? "", filePath: s?.filePath ?? "" };
|
|
421
|
+
});
|
|
420
422
|
}
|
|
421
|
-
/** Remove a registered skill. */
|
|
422
423
|
removeSkill(name) {
|
|
423
424
|
this.skills.delete(name);
|
|
425
|
+
// Handler entry retained so external advisors survive a reload of the owner.
|
|
424
426
|
}
|
|
425
427
|
/**
|
|
426
428
|
* Build the system prompt grouped by extension.
|
|
@@ -434,13 +436,15 @@ export class AgentLoop {
|
|
|
434
436
|
buildExtensionSections() {
|
|
435
437
|
const groups = new Map();
|
|
436
438
|
const ensure = (name) => groups.get(name) ?? (groups.set(name, { tools: [], skills: [], instructions: [] }).get(name));
|
|
437
|
-
// Attribute instructions
|
|
438
|
-
for (const {
|
|
439
|
+
// Attribute instructions — read text through the advisor chain
|
|
440
|
+
for (const [name, { extensionName }] of this.instructions) {
|
|
441
|
+
const text = this.handlers.call(`instruction:${name}`);
|
|
439
442
|
ensure(extensionName).instructions.push({ text });
|
|
440
443
|
}
|
|
441
|
-
// Attribute skills
|
|
442
|
-
for (const [skillName, {
|
|
443
|
-
|
|
444
|
+
// Attribute skills — read description/filePath through the advisor chain
|
|
445
|
+
for (const [skillName, { extensionName }] of this.skills) {
|
|
446
|
+
const view = this.handlers.call(`skill:${skillName}:view`);
|
|
447
|
+
ensure(extensionName).skills.push({ name: skillName, description: view.description, filePath: view.filePath });
|
|
444
448
|
}
|
|
445
449
|
// Attribute tools (skip built-in scratchpad tools).
|
|
446
450
|
// In "api" mode the full tool schemas are in the API `tools` param,
|
|
@@ -453,7 +457,7 @@ export class AgentLoop {
|
|
|
453
457
|
"bash", "read_file", "write_file", "edit_file", "grep", "glob", "ls",
|
|
454
458
|
"list_skills",
|
|
455
459
|
]);
|
|
456
|
-
for (const tool of this.toolRegistry.
|
|
460
|
+
for (const tool of this.toolRegistry.allView()) {
|
|
457
461
|
if (builtinTools.has(tool.name))
|
|
458
462
|
continue;
|
|
459
463
|
const extName = this.toolExtensions.get(tool.name);
|
|
@@ -832,6 +836,8 @@ export class AgentLoop {
|
|
|
832
836
|
h.define("conversation:nucleate-user", (text, iid, seq) => nucleate("user", text, iid, seq));
|
|
833
837
|
h.define("conversation:nucleate-agent", (text, iid, seq) => nucleate("agent", text, iid, seq));
|
|
834
838
|
h.define("conversation:nucleate-tool", (toolName, args, content, isError, iid, seq) => nucleate(isError ? "error" : "tool", toolName, args, content, isError, iid, seq));
|
|
839
|
+
h.define("conversation:allocate-seq", () => this.conversation.allocateSeq());
|
|
840
|
+
h.define("conversation:reset-for-session", (nextSeq) => this.conversation.resetForSession(nextSeq));
|
|
835
841
|
// Read-only views into the nuclear state, for compact strategies
|
|
836
842
|
// and introspect that read without replacing.
|
|
837
843
|
h.define("conversation:get-nuclear-entries", () => this.conversation.getNuclearEntries());
|
|
@@ -858,6 +864,11 @@ export class AgentLoop {
|
|
|
858
864
|
h.define("history:search", async (query) => this.history.search(query));
|
|
859
865
|
h.define("history:find-by-seq", async (seq) => this.history.findBySeq(seq));
|
|
860
866
|
h.define("history:read-recent", async (max) => this.history.readRecent(max));
|
|
867
|
+
h.define("history:get-branch", async (leafSeq) => this.history.getBranch ? this.history.getBranch(leafSeq) : this.history.readRecent());
|
|
868
|
+
h.define("history:get-tree", async () => this.history.getTree ? this.history.getTree() : this.history.readRecent());
|
|
869
|
+
h.define("history:set-leaf", (seq) => {
|
|
870
|
+
this.history.setLeaf?.(seq);
|
|
871
|
+
});
|
|
861
872
|
// Prior-session preamble renderer. Default: flat chronological list.
|
|
862
873
|
h.define("conversation:format-prior-history", (entries) => {
|
|
863
874
|
if (!entries || entries.length === 0)
|
|
@@ -910,74 +921,6 @@ export class AgentLoop {
|
|
|
910
921
|
return { content: msg, exitCode: 1, isError: true };
|
|
911
922
|
}
|
|
912
923
|
const display = tool.getDisplayInfo?.(args) ?? { kind: "execute" };
|
|
913
|
-
let diffShown = false;
|
|
914
|
-
// Permission gating
|
|
915
|
-
if (tool.requiresPermission) {
|
|
916
|
-
let permKind = "tool-call";
|
|
917
|
-
let permTitle = typeof args.description === "string"
|
|
918
|
-
? `${name}: ${args.description}`
|
|
919
|
-
: name;
|
|
920
|
-
let metadata = { args };
|
|
921
|
-
// For file-modifying tools, pre-compute diff for display
|
|
922
|
-
if (tool.modifiesFiles && typeof args.path === "string") {
|
|
923
|
-
try {
|
|
924
|
-
const absPath = path.resolve(process.cwd(), args.path);
|
|
925
|
-
let diff;
|
|
926
|
-
if (typeof args.old_text === "string" && typeof args.new_text === "string") {
|
|
927
|
-
// edit_file — read the file so line numbers are real (not relative to the edit region)
|
|
928
|
-
const normalizedOld = args.old_text.replace(/\r\n/g, "\n");
|
|
929
|
-
const normalizedNew = args.new_text.replace(/\r\n/g, "\n");
|
|
930
|
-
try {
|
|
931
|
-
const oldFileContent = await fs.readFile(absPath, "utf-8");
|
|
932
|
-
diff = computeEditDiff(oldFileContent, normalizedOld, normalizedNew, args.replace_all === true);
|
|
933
|
-
}
|
|
934
|
-
catch {
|
|
935
|
-
// File doesn't exist yet — fall back to input-only diff
|
|
936
|
-
diff = computeInputDiff(normalizedOld, normalizedNew);
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
else if (typeof args.content === "string") {
|
|
940
|
-
// write_file — still need to read the old file for comparison
|
|
941
|
-
let oldContent = null;
|
|
942
|
-
try {
|
|
943
|
-
oldContent = await fs.readFile(absPath, "utf-8");
|
|
944
|
-
}
|
|
945
|
-
catch { /* new file */ }
|
|
946
|
-
if (oldContent !== null) {
|
|
947
|
-
diff = computeDiff(oldContent, args.content);
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
if (diff && !diff.isIdentical) {
|
|
951
|
-
permKind = "file-write";
|
|
952
|
-
// Shorten path for display
|
|
953
|
-
const cwd = process.cwd();
|
|
954
|
-
const home = process.env.HOME ?? os.homedir();
|
|
955
|
-
let displayPath = absPath;
|
|
956
|
-
if (absPath.startsWith(cwd + "/"))
|
|
957
|
-
displayPath = absPath.slice(cwd.length + 1);
|
|
958
|
-
else if (home && absPath.startsWith(home + "/"))
|
|
959
|
-
displayPath = "~/" + absPath.slice(home.length + 1);
|
|
960
|
-
permTitle = displayPath;
|
|
961
|
-
metadata = { args, diff };
|
|
962
|
-
diffShown = true;
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
catch { /* fall back to generic permission */ }
|
|
966
|
-
}
|
|
967
|
-
const ui = this.compositor
|
|
968
|
-
? createToolUI(this.bus, this.compositor.surface("agent"))
|
|
969
|
-
: undefined;
|
|
970
|
-
const perm = await this.bus.emitPipeAsync("permission:request", {
|
|
971
|
-
kind: permKind,
|
|
972
|
-
title: permTitle,
|
|
973
|
-
metadata,
|
|
974
|
-
ui,
|
|
975
|
-
decision: { outcome: "approved" },
|
|
976
|
-
});
|
|
977
|
-
if (perm.decision.outcome !== "approved") {
|
|
978
|
-
return { content: "Permission denied by user.", exitCode: 1, isError: true };
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
924
|
// Emit tool-started for TUI
|
|
982
925
|
const label = tool.displayName ?? name;
|
|
983
926
|
this.bus.emit("agent:tool-started", {
|
|
@@ -985,21 +928,19 @@ export class AgentLoop {
|
|
|
985
928
|
toolCallId: id,
|
|
986
929
|
kind: display.kind, icon: display.icon, locations: display.locations, rawInput: args,
|
|
987
930
|
displayDetail: tool.formatCall?.(args),
|
|
931
|
+
sourceLanguage: display.sourceLanguage,
|
|
988
932
|
batchIndex: ctx.batchIndex, batchTotal: ctx.batchTotal,
|
|
989
933
|
});
|
|
990
934
|
this.bus.emit("agent:tool-call", { tool: name, args });
|
|
991
935
|
// Execute — use ctx.onChunk so advisors can wrap the streaming callback.
|
|
992
|
-
|
|
993
|
-
const onChunk = (tool.showOutput !== false && !diffShown)
|
|
994
|
-
? ctx.onChunk
|
|
995
|
-
: undefined;
|
|
936
|
+
const onChunk = tool.showOutput !== false ? ctx.onChunk : undefined;
|
|
996
937
|
const toolCtx = { signal };
|
|
997
938
|
if (this.compositor) {
|
|
998
939
|
toolCtx.ui = createToolUI(this.bus, this.compositor.surface("agent"));
|
|
999
940
|
}
|
|
1000
941
|
let result;
|
|
1001
942
|
try {
|
|
1002
|
-
result = await raceAbort(
|
|
943
|
+
result = await raceAbort(this.toolRegistry.call(name, args, onChunk, toolCtx), signal);
|
|
1003
944
|
}
|
|
1004
945
|
catch (err) {
|
|
1005
946
|
if (signal.aborted) {
|
|
@@ -1105,7 +1046,7 @@ export class AgentLoop {
|
|
|
1105
1046
|
// Compact deeply — shallow targets buy only 1–2 turns of runway on
|
|
1106
1047
|
// tool-heavy workloads.
|
|
1107
1048
|
const target = Math.floor(threshold * 0.25);
|
|
1108
|
-
const result = this.compactWithHooks(target,
|
|
1049
|
+
const result = this.compactWithHooks(target, 1);
|
|
1109
1050
|
if (!result) {
|
|
1110
1051
|
// Auto-compact fired but nothing was evictable. This can happen
|
|
1111
1052
|
// in short conversations with heavy tool output where the pin
|
|
@@ -1192,9 +1133,11 @@ export class AgentLoop {
|
|
|
1192
1133
|
catch { /* not an error payload, continue */ }
|
|
1193
1134
|
const tool = this.toolRegistry.get(tc.name);
|
|
1194
1135
|
if (!tool) {
|
|
1136
|
+
const available = this.toolRegistry.all().map((t) => t.name).join(", ");
|
|
1195
1137
|
collectedResults.push({
|
|
1196
1138
|
callId: tc.id, toolName: tc.name,
|
|
1197
|
-
content: `Unknown tool "${tc.name}"
|
|
1139
|
+
content: `Unknown tool "${tc.name}". Available tools: ${available}`,
|
|
1140
|
+
isError: true,
|
|
1198
1141
|
});
|
|
1199
1142
|
return;
|
|
1200
1143
|
}
|
|
@@ -1214,7 +1157,7 @@ export class AgentLoop {
|
|
|
1214
1157
|
// normalize-args.ts for the diagnostic that uncovered this.
|
|
1215
1158
|
args = normalizeToolArgs(args, tool.input_schema);
|
|
1216
1159
|
// ── Round-scoped cache for cacheable read-only tools ──
|
|
1217
|
-
const cacheable = !tool.modifiesFiles &&
|
|
1160
|
+
const cacheable = !tool.modifiesFiles && tool.showOutput !== true;
|
|
1218
1161
|
const cacheKey = cacheable ? `${tc.name}:${JSON.stringify(args)}` : null;
|
|
1219
1162
|
if (cacheKey) {
|
|
1220
1163
|
const cached = roundCache.get(cacheKey);
|
|
@@ -1225,6 +1168,7 @@ export class AgentLoop {
|
|
|
1225
1168
|
toolCallId: tc.id,
|
|
1226
1169
|
kind: display.kind, icon: display.icon, locations: display.locations, rawInput: args,
|
|
1227
1170
|
displayDetail: tool.formatCall?.(args),
|
|
1171
|
+
sourceLanguage: display.sourceLanguage,
|
|
1228
1172
|
batchIndex, batchTotal: batchTotal > 1 ? batchTotal : undefined,
|
|
1229
1173
|
});
|
|
1230
1174
|
this.bus.emit("agent:tool-call", { tool: tc.name, args });
|
|
@@ -1254,9 +1198,9 @@ export class AgentLoop {
|
|
|
1254
1198
|
const result = await this.handlers.call("tool:execute", { name: tc.name, id: tc.id, args, tool, onChunk: defaultOnChunk,
|
|
1255
1199
|
batchIndex, batchTotal: batchTotal > 1 ? batchTotal : undefined,
|
|
1256
1200
|
signal });
|
|
1257
|
-
// Truncate large outputs to avoid blowing context
|
|
1201
|
+
// Truncate large outputs to avoid blowing context.
|
|
1258
1202
|
let content = result.content;
|
|
1259
|
-
const maxBytes = 16_384; // ~4k tokens
|
|
1203
|
+
const maxBytes = tool.maxResultBytes ?? 16_384; // ~4k tokens
|
|
1260
1204
|
if (content.length > maxBytes) {
|
|
1261
1205
|
const headBytes = Math.floor(maxBytes * 0.6);
|
|
1262
1206
|
const tailBytes = maxBytes - headBytes;
|
|
@@ -1287,12 +1231,11 @@ export class AgentLoop {
|
|
|
1287
1231
|
}
|
|
1288
1232
|
collectedResults.push(finalResult);
|
|
1289
1233
|
};
|
|
1290
|
-
// Partition into parallel-safe (read-only) and sequential (needs permission)
|
|
1291
1234
|
const parallel = [];
|
|
1292
1235
|
const sequential = [];
|
|
1293
1236
|
for (const tc of toolCalls) {
|
|
1294
1237
|
const tool = this.toolRegistry.get(tc.name);
|
|
1295
|
-
if (tool && !tool.
|
|
1238
|
+
if (tool && !tool.modifiesFiles) {
|
|
1296
1239
|
parallel.push(tc);
|
|
1297
1240
|
}
|
|
1298
1241
|
else {
|
|
@@ -1488,7 +1431,7 @@ export class AgentLoop {
|
|
|
1488
1431
|
if (this.isContextOverflow(e)) {
|
|
1489
1432
|
const contextWindow = this.currentMode.contextWindow ?? DEFAULT_CONTEXT_WINDOW;
|
|
1490
1433
|
const target = Math.floor((contextWindow - RESPONSE_RESERVE) * 0.6);
|
|
1491
|
-
const stats = this.compactWithHooks(target,
|
|
1434
|
+
const stats = this.compactWithHooks(target, 1);
|
|
1492
1435
|
// If compaction freed nothing, retrying will hit the same error.
|
|
1493
1436
|
// Surface the real failure instead of looping until exhaustion.
|
|
1494
1437
|
if (!stats || stats.after >= stats.before) {
|
|
@@ -1536,8 +1479,9 @@ export class AgentLoop {
|
|
|
1536
1479
|
const reasoningDetailsByIndex = new Map();
|
|
1537
1480
|
const pendingToolCalls = [];
|
|
1538
1481
|
// Tool protocol controls what goes in the API tools param vs dynamic context
|
|
1539
|
-
const
|
|
1540
|
-
const
|
|
1482
|
+
const toolView = this.toolRegistry.allView();
|
|
1483
|
+
const apiTools = this.toolProtocol.getApiTools(toolView);
|
|
1484
|
+
const toolPrompt = this.toolProtocol.getToolPrompt(toolView);
|
|
1541
1485
|
// Dynamic context rides on the trailing message — see
|
|
1542
1486
|
// wrapTrailingWithDynamicContext for the cache-stability rationale.
|
|
1543
1487
|
const rawMessages = [
|
|
@@ -97,6 +97,15 @@ export declare class ConversationState {
|
|
|
97
97
|
/** Track an entry in memory (nuclear list + recall archive). */
|
|
98
98
|
private recordNuclearEntry;
|
|
99
99
|
private appendToHistory;
|
|
100
|
+
/** Bump and return the global sequence counter. For extensions that
|
|
101
|
+
* synthesize their own NuclearEntries (e.g. compaction summaries that
|
|
102
|
+
* should land in the same sequence space as kernel-produced entries). */
|
|
103
|
+
allocateSeq(): number;
|
|
104
|
+
/** Clear nuclear bookkeeping and reset the seq counter. For extensions
|
|
105
|
+
* that swap sessions (multi-session history adapters) so the in-memory
|
|
106
|
+
* nuclear list, recall archive, and seq counter don't carry over from
|
|
107
|
+
* the previous session's tree. */
|
|
108
|
+
resetForSession(nextSeq: number): void;
|
|
100
109
|
updateApiTokenCount(promptTokens: number): void;
|
|
101
110
|
estimatePromptTokens(): number;
|
|
102
111
|
estimateTokens(): number;
|
|
@@ -29,6 +29,19 @@ function firstMatchExcerpt(text, regex) {
|
|
|
29
29
|
function recencyWeight(idx, total) {
|
|
30
30
|
return Math.max(0.1, 1 - idx / total);
|
|
31
31
|
}
|
|
32
|
+
// Head+tail because the start (command, opening lines) and end (final
|
|
33
|
+
// result, exit code) are the informative parts of shell/file output.
|
|
34
|
+
function slimToolContent(content, maxLen) {
|
|
35
|
+
const exitMatch = content.match(/exit code:?\s*(\d+)/i);
|
|
36
|
+
const exitSuffix = exitMatch ? ` (exit ${exitMatch[1]})` : "";
|
|
37
|
+
const lines = content.split("\n");
|
|
38
|
+
if (lines.length > 6) {
|
|
39
|
+
const head = lines.slice(0, 3).join("\n");
|
|
40
|
+
const tail = lines.slice(-2).join("\n");
|
|
41
|
+
return `${head}\n... [${lines.length - 5} lines trimmed by compact]\n${tail}${exitSuffix}`;
|
|
42
|
+
}
|
|
43
|
+
return `${content.slice(0, maxLen)}\n... [${content.length - maxLen} chars trimmed by compact]${exitSuffix}`;
|
|
44
|
+
}
|
|
32
45
|
/**
|
|
33
46
|
* Conversation state with eager nucleation — shell-history shaped.
|
|
34
47
|
*
|
|
@@ -318,6 +331,22 @@ export class ConversationState {
|
|
|
318
331
|
return;
|
|
319
332
|
this.handlers.call("history:append", entries);
|
|
320
333
|
}
|
|
334
|
+
/** Bump and return the global sequence counter. For extensions that
|
|
335
|
+
* synthesize their own NuclearEntries (e.g. compaction summaries that
|
|
336
|
+
* should land in the same sequence space as kernel-produced entries). */
|
|
337
|
+
allocateSeq() {
|
|
338
|
+
return this.nextSeq++;
|
|
339
|
+
}
|
|
340
|
+
/** Clear nuclear bookkeeping and reset the seq counter. For extensions
|
|
341
|
+
* that swap sessions (multi-session history adapters) so the in-memory
|
|
342
|
+
* nuclear list, recall archive, and seq counter don't carry over from
|
|
343
|
+
* the previous session's tree. */
|
|
344
|
+
resetForSession(nextSeq) {
|
|
345
|
+
this.nuclearEntries = [];
|
|
346
|
+
this.nuclearBySeq.clear();
|
|
347
|
+
this.recallArchive.clear();
|
|
348
|
+
this.nextSeq = nextSeq;
|
|
349
|
+
}
|
|
321
350
|
// ── Token estimation ──────────────────────────────────────────
|
|
322
351
|
updateApiTokenCount(promptTokens) {
|
|
323
352
|
this.lastApiTokenCount = promptTokens;
|
|
@@ -611,6 +640,7 @@ export class ConversationState {
|
|
|
611
640
|
// ── Internal: Two-tier pin for recent turns ────────────────────
|
|
612
641
|
slimTurn(messages) {
|
|
613
642
|
const MAX_RESULT_LEN = 1500;
|
|
643
|
+
const MAX_ASSISTANT_LEN = 1500;
|
|
614
644
|
const result = [];
|
|
615
645
|
const droppedToolIds = new Set();
|
|
616
646
|
for (const msg of messages) {
|
|
@@ -642,13 +672,20 @@ export class ConversationState {
|
|
|
642
672
|
continue;
|
|
643
673
|
const content = typeof msg.content === "string" ? msg.content : "";
|
|
644
674
|
if (content.length > MAX_RESULT_LEN) {
|
|
645
|
-
result.push({ ...msg, content: content
|
|
675
|
+
result.push({ ...msg, content: slimToolContent(content, MAX_RESULT_LEN) });
|
|
646
676
|
}
|
|
647
677
|
else {
|
|
648
678
|
result.push(msg);
|
|
649
679
|
}
|
|
650
680
|
continue;
|
|
651
681
|
}
|
|
682
|
+
if (msg.role === "assistant" && typeof msg.content === "string" && msg.content.length > MAX_ASSISTANT_LEN) {
|
|
683
|
+
const head = msg.content.slice(0, Math.floor(MAX_ASSISTANT_LEN * 0.6));
|
|
684
|
+
const tail = msg.content.slice(-Math.floor(MAX_ASSISTANT_LEN * 0.2));
|
|
685
|
+
const trimmed = msg.content.length - head.length - tail.length;
|
|
686
|
+
result.push({ ...msg, content: `${head}\n... [${trimmed} chars trimmed by compact]\n${tail}` });
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
652
689
|
result.push(msg);
|
|
653
690
|
}
|
|
654
691
|
return result;
|
|
@@ -7,6 +7,12 @@ export interface HistoryAdapter {
|
|
|
7
7
|
line: string;
|
|
8
8
|
}[]>;
|
|
9
9
|
findBySeq(seq: number): Promise<NuclearEntry | null>;
|
|
10
|
+
/** Walk parent pointers from a leaf back to the root. Tree-aware adapters only. */
|
|
11
|
+
getBranch?(leafSeq: number): Promise<NuclearEntry[]>;
|
|
12
|
+
/** Return every entry, including sibling branches. Tree-aware adapters only. */
|
|
13
|
+
getTree?(): Promise<NuclearEntry[]>;
|
|
14
|
+
/** Move the active leaf for the next append. Tree-aware adapters only. */
|
|
15
|
+
setLeaf?(seq: number): void;
|
|
10
16
|
}
|
|
11
17
|
export declare class InMemoryHistory implements HistoryAdapter {
|
|
12
18
|
private entries;
|
|
@@ -9,7 +9,7 @@ import * as fs from "node:fs/promises";
|
|
|
9
9
|
import * as fss from "node:fs";
|
|
10
10
|
import * as path from "node:path";
|
|
11
11
|
import * as crypto from "node:crypto";
|
|
12
|
-
import { CONFIG_DIR, getSettings } from "../settings.js";
|
|
12
|
+
import { CONFIG_DIR, getSettings } from "../core/settings.js";
|
|
13
13
|
import { serializeEntry, deserializeEntry, isReadOnly, compileSearchRegex, matchEntry, } from "./nuclear-form.js";
|
|
14
14
|
const HISTORY_PATH = path.join(CONFIG_DIR, "history");
|
|
15
15
|
const LOCK_STALE_MS = 10_000; // consider lock stale after 10s
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { CoreConfig, CoreContext } from "../core/types.js";
|
|
2
|
+
import type { HistoryAdapter } from "./history-file.js";
|
|
3
|
+
import type { SkillView, ToolDefinition, ToolExecutionContext, ToolSchemaView } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Backend-agnostic LLM interface exposed via `ctx.agent.llm`. Fulfilled
|
|
6
|
+
* by defining an `llm:invoke` handler; backends without an LLM leave
|
|
7
|
+
* `available` false and calls reject.
|
|
8
|
+
*/
|
|
9
|
+
export interface LlmMessage {
|
|
10
|
+
role: "system" | "user" | "assistant";
|
|
11
|
+
content: string;
|
|
12
|
+
}
|
|
13
|
+
export interface LlmSession {
|
|
14
|
+
send(message: string): Promise<string>;
|
|
15
|
+
history(): ReadonlyArray<LlmMessage>;
|
|
16
|
+
}
|
|
17
|
+
export interface LlmInterface {
|
|
18
|
+
readonly available: boolean;
|
|
19
|
+
/** `model` overrides the globally-configured model for this call only.
|
|
20
|
+
* Provider-specific identifier (e.g. "claude-haiku-4-5"). When omitted,
|
|
21
|
+
* the active provider's configured default is used.
|
|
22
|
+
*
|
|
23
|
+
* `reasoningEffort` controls thinking-model token allocation between
|
|
24
|
+
* reasoning and final content (e.g. "low", "medium", "high", or
|
|
25
|
+
* provider-specific). For non-reasoning models it is ignored. Set to
|
|
26
|
+
* "low" for cheap structured-output calls so reasoning doesn't exhaust
|
|
27
|
+
* the max-tokens budget and leave content empty. */
|
|
28
|
+
ask(opts: {
|
|
29
|
+
query: string;
|
|
30
|
+
system?: string;
|
|
31
|
+
maxTokens?: number;
|
|
32
|
+
model?: string;
|
|
33
|
+
reasoningEffort?: string;
|
|
34
|
+
}): Promise<string>;
|
|
35
|
+
session(opts?: {
|
|
36
|
+
system?: string;
|
|
37
|
+
maxTokens?: number;
|
|
38
|
+
model?: string;
|
|
39
|
+
reasoningEffort?: string;
|
|
40
|
+
}): LlmSession;
|
|
41
|
+
}
|
|
42
|
+
/** A model entry in the cycling list, optionally tied to a provider. */
|
|
43
|
+
export interface AgentMode {
|
|
44
|
+
model: string;
|
|
45
|
+
/** Provider id — when cycling changes provider, LlmClient is reconfigured. */
|
|
46
|
+
provider?: string;
|
|
47
|
+
/** Provider-specific config for reconfiguring LlmClient on switch. */
|
|
48
|
+
providerConfig?: {
|
|
49
|
+
apiKey: string;
|
|
50
|
+
baseURL?: string;
|
|
51
|
+
};
|
|
52
|
+
/** Context window size in tokens (for usage display). */
|
|
53
|
+
contextWindow?: number;
|
|
54
|
+
/** Max output tokens for this mode. */
|
|
55
|
+
maxTokens?: number;
|
|
56
|
+
/** Model supports reasoning/thinking tokens. */
|
|
57
|
+
reasoning?: boolean;
|
|
58
|
+
/** Provider supports the reasoning_effort parameter. */
|
|
59
|
+
supportsReasoningEffort?: boolean;
|
|
60
|
+
/** Echo reasoning_content back on assistant turns. Required by DeepSeek;
|
|
61
|
+
* default off (leaky shims may forward it to the model as OOD input). */
|
|
62
|
+
echoReasoning?: boolean;
|
|
63
|
+
buildReasoningParams?: (level: string) => Record<string, unknown>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Capabilities the agent host adds on top of CoreContext. Only available
|
|
67
|
+
* when the built-in agent backend is loaded; bridge backends pass a bare
|
|
68
|
+
* CoreContext, so extensions that need these should type as AgentContext.
|
|
69
|
+
*/
|
|
70
|
+
export interface AgentSurface {
|
|
71
|
+
llm: LlmInterface;
|
|
72
|
+
providers: {
|
|
73
|
+
configure: (id: string, opts: {
|
|
74
|
+
reasoningParams?: (level: string, model?: string) => Record<string, unknown>;
|
|
75
|
+
}) => void;
|
|
76
|
+
};
|
|
77
|
+
registerTool: (tool: ToolDefinition) => void;
|
|
78
|
+
unregisterTool: (name: string) => void;
|
|
79
|
+
adviseTool: (name: string, advisor: (next: ToolDefinition["execute"], args: Record<string, unknown>, onChunk?: (chunk: string) => void, ctx?: ToolExecutionContext) => ReturnType<ToolDefinition["execute"]>) => () => void;
|
|
80
|
+
adviseToolSchema: (name: string, advisor: (next: () => ToolSchemaView) => ToolSchemaView) => () => void;
|
|
81
|
+
getTools: () => ToolDefinition[];
|
|
82
|
+
registerInstruction: (name: string, text: string) => void;
|
|
83
|
+
removeInstruction: (name: string) => void;
|
|
84
|
+
adviseInstruction: (name: string, advisor: (next: () => string) => string) => () => void;
|
|
85
|
+
registerSkill: (name: string, description: string, filePath: string) => void;
|
|
86
|
+
removeSkill: (name: string) => void;
|
|
87
|
+
adviseSkill: (name: string, advisor: (next: () => SkillView) => SkillView) => () => void;
|
|
88
|
+
/**
|
|
89
|
+
* Register a context producer — a function that contributes a string
|
|
90
|
+
* (or `null` to skip) into one of two lifecycles:
|
|
91
|
+
*
|
|
92
|
+
* - `mode: "per-request"` (default) — fires on every LLM request,
|
|
93
|
+
* including each tool-loop iteration. Output is ephemerally wrapped
|
|
94
|
+
* in `<dynamic_context>` onto the trailing message at request time;
|
|
95
|
+
* never persisted. Use for "current state" signals.
|
|
96
|
+
*
|
|
97
|
+
* - `mode: "per-query"` — fires once at user-query start. Output is
|
|
98
|
+
* wrapped in `<query_context>` and frozen into the user message;
|
|
99
|
+
* persists in conversation history.
|
|
100
|
+
*
|
|
101
|
+
* Returns a dispose fn that unregisters the producer.
|
|
102
|
+
*/
|
|
103
|
+
registerContextProducer: (name: string, producer: () => string | null, opts?: {
|
|
104
|
+
mode?: "per-request" | "per-query";
|
|
105
|
+
}) => () => void;
|
|
106
|
+
}
|
|
107
|
+
/** Substrate + agent surface. Use this when an extension only touches
|
|
108
|
+
* agent-side features (tools, instructions, LLM) and doesn't need
|
|
109
|
+
* shell rendering. */
|
|
110
|
+
export type AgentContext = CoreContext & {
|
|
111
|
+
agent: AgentSurface;
|
|
112
|
+
};
|
|
113
|
+
export interface AgentConfigSurface {
|
|
114
|
+
/** API key for OpenAI-compatible provider. */
|
|
115
|
+
apiKey?: string;
|
|
116
|
+
/** Base URL for OpenAI-compatible API. */
|
|
117
|
+
baseURL?: string;
|
|
118
|
+
/** Named provider to use from settings.json. */
|
|
119
|
+
provider?: string;
|
|
120
|
+
/** Default model id. */
|
|
121
|
+
model?: string;
|
|
122
|
+
/** Conversation history backend. Defaults to the on-disk HistoryFile. */
|
|
123
|
+
history?: HistoryAdapter;
|
|
124
|
+
}
|
|
125
|
+
export type AgentConfig = CoreConfig & AgentConfigSurface;
|
package/dist/agent/index.d.ts
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Constructs the AgentLoop synchronously with a placeholder LlmClient,
|
|
3
|
+
* so core handlers (history:append, system-prompt:build, conversation:*)
|
|
4
|
+
* are defined before user extensions activate. Mode resolution is
|
|
5
|
+
* deferred to `core:extensions-loaded`, giving runtime-registered
|
|
6
|
+
* providers (e.g. openrouter) a chance to register before we look up
|
|
7
|
+
* settings.defaultProvider. Without this deferral, a persisted
|
|
8
|
+
* `defaultProvider: "openrouter"` loses to a cold-start race and the
|
|
9
|
+
* backend bails silently.
|
|
6
10
|
*/
|
|
11
|
+
import type { ExtensionContext } from "../shell/host-types.js";
|
|
12
|
+
export default function agentBackend(ctx: ExtensionContext): void;
|
|
7
13
|
export type { AgentBackend } from "./types.js";
|
|
8
14
|
export type { ToolDefinition, ToolResult, ToolDisplayInfo } from "./types.js";
|
|
9
15
|
export { AgentLoop } from "./agent-loop.js";
|
|
10
16
|
export { ToolRegistry } from "./tool-registry.js";
|
|
11
17
|
export { runSubagent, type SubagentOptions } from "./subagent.js";
|
|
18
|
+
/** Activate the ash backend and any provider whose key is configured. */
|
|
19
|
+
export declare function activateAgent(ctx: ExtensionContext): void;
|