aether-code 0.8.0 → 0.9.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/bin/aether-code.js +40 -2
- package/package.json +6 -3
- package/src/agent.js +21 -2
- package/src/mcp.js +259 -0
- package/src/repl.js +2 -1
package/bin/aether-code.js
CHANGED
|
@@ -14,9 +14,43 @@ import { runRepl } from "../src/repl.js";
|
|
|
14
14
|
import { runSetup } from "../src/setup.js";
|
|
15
15
|
import { fetchBalance, AetherError } from "../src/api.js";
|
|
16
16
|
import { writeConfigFile, getConfig, CONFIG_PATH } from "../src/config.js";
|
|
17
|
+
import { loadMcpConfig, MCPManager } from "../src/mcp.js";
|
|
17
18
|
import { c, errorLine, divider } from "../src/render.js";
|
|
18
19
|
|
|
19
|
-
const VERSION = "0.
|
|
20
|
+
const VERSION = "0.9.0";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Try to start MCP servers from ~/.aether/mcp.json. Returns a started
|
|
24
|
+
* MCPManager (possibly with zero servers) or null if no config exists.
|
|
25
|
+
* Prints a one-line summary so the user can see what attached.
|
|
26
|
+
*/
|
|
27
|
+
async function bootMcp() {
|
|
28
|
+
let config;
|
|
29
|
+
try {
|
|
30
|
+
config = loadMcpConfig();
|
|
31
|
+
} catch (e) {
|
|
32
|
+
console.log(errorLine(`MCP config: ${e.message}`));
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
if (!config) return null;
|
|
36
|
+
const manager = new MCPManager();
|
|
37
|
+
const started = await manager.start(config);
|
|
38
|
+
const requested = Object.keys(config.mcpServers).length;
|
|
39
|
+
const failed = manager.startErrors.length;
|
|
40
|
+
if (started > 0 || failed > 0) {
|
|
41
|
+
const parts = [`${c.cyan("MCP")}`, `${started}/${requested} servers attached`];
|
|
42
|
+
const toolCount = manager.getToolDefinitions().length;
|
|
43
|
+
if (toolCount > 0) parts.push(`${toolCount} tools`);
|
|
44
|
+
console.log(c.gray(parts.join(" · ")));
|
|
45
|
+
for (const { serverName, error } of manager.startErrors) {
|
|
46
|
+
console.log(c.gray(` ${c.yellow("!")} ${serverName}: ${error}`));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Best-effort cleanup so child processes don't leak on normal exit.
|
|
50
|
+
process.on("exit", () => { manager.shutdown().catch(() => {}); });
|
|
51
|
+
process.on("SIGINT", () => { manager.shutdown().catch(() => {}); process.exit(130); });
|
|
52
|
+
return manager;
|
|
53
|
+
}
|
|
20
54
|
|
|
21
55
|
const HELP = `${c.bold("aether")} — uncensored AI coding agent
|
|
22
56
|
|
|
@@ -128,7 +162,8 @@ async function main() {
|
|
|
128
162
|
// No task → drop into interactive REPL (Claude-CLI-style)
|
|
129
163
|
if (!prompt) {
|
|
130
164
|
if (cwd !== process.cwd()) process.chdir(cwd);
|
|
131
|
-
|
|
165
|
+
const mcpManager = await bootMcp();
|
|
166
|
+
await runRepl({ cwd, autoYes, maxTurns, mcpManager });
|
|
132
167
|
return;
|
|
133
168
|
}
|
|
134
169
|
|
|
@@ -144,13 +179,16 @@ async function main() {
|
|
|
144
179
|
console.log(c.gray(`task: `) + prompt);
|
|
145
180
|
console.log(divider());
|
|
146
181
|
|
|
182
|
+
const mcpManager = await bootMcp();
|
|
147
183
|
const result = await runAgent({
|
|
148
184
|
initialPrompt: prompt,
|
|
149
185
|
cwd,
|
|
150
186
|
autoYes,
|
|
151
187
|
unsafePaths,
|
|
152
188
|
maxTurns,
|
|
189
|
+
mcpManager,
|
|
153
190
|
});
|
|
191
|
+
if (mcpManager) await mcpManager.shutdown().catch(() => {});
|
|
154
192
|
|
|
155
193
|
console.log("\n" + divider());
|
|
156
194
|
if (result.ok) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aether-code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Uncensored AI coding agent for your terminal. Type `aether` to launch the interactive REPL — like Claude Code, with no refusal layer.",
|
|
5
5
|
"homepage": "https://trynoguard.com",
|
|
6
6
|
"repository": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"node": ">=18"
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
|
-
"lint": "node --check bin/aether-code.js src/agent.js src/api.js src/config.js src/render.js src/tools.js src/diff.js src/repl.js",
|
|
25
|
+
"lint": "node --check bin/aether-code.js src/agent.js src/api.js src/config.js src/render.js src/tools.js src/diff.js src/repl.js src/mcp.js",
|
|
26
26
|
"test": "node --test \"test/**/*.test.js\""
|
|
27
27
|
},
|
|
28
28
|
"keywords": [
|
|
@@ -35,5 +35,8 @@
|
|
|
35
35
|
"cli",
|
|
36
36
|
"tool-use",
|
|
37
37
|
"agentic"
|
|
38
|
-
]
|
|
38
|
+
],
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
41
|
+
}
|
|
39
42
|
}
|
package/src/agent.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { agentTurnStream, AetherError } from "./api.js";
|
|
6
6
|
import { TOOL_DEFINITIONS, executeTool } from "./tools.js";
|
|
7
|
+
import { unnamespaceToolName } from "./mcp.js";
|
|
7
8
|
import { c, divider, turn, toolHeader, toolResult, errorLine } from "./render.js";
|
|
8
9
|
|
|
9
10
|
const DEFAULT_MAX_TURNS = 25;
|
|
@@ -16,7 +17,17 @@ export async function runAgent({
|
|
|
16
17
|
unsafePaths = false,
|
|
17
18
|
maxTurns = DEFAULT_MAX_TURNS,
|
|
18
19
|
onTokens = () => {},
|
|
20
|
+
// Optional MCPManager. When provided, its tools are merged into the agent's
|
|
21
|
+
// toolset and tool calls prefixed `mcp__` are routed to it instead of the
|
|
22
|
+
// built-in executeTool.
|
|
23
|
+
mcpManager = null,
|
|
19
24
|
}) {
|
|
25
|
+
// Merge built-in tools with MCP-provided tools. MCP tools come second so
|
|
26
|
+
// any name collision (unlikely given namespacing, but defense in depth)
|
|
27
|
+
// resolves to the built-in.
|
|
28
|
+
const tools = mcpManager
|
|
29
|
+
? [...TOOL_DEFINITIONS, ...mcpManager.getToolDefinitions()]
|
|
30
|
+
: TOOL_DEFINITIONS;
|
|
20
31
|
// Two callers: one-shot (initialPrompt only, fresh conversation) and REPL
|
|
21
32
|
// (priorMessages + initialPrompt to continue an ongoing chat).
|
|
22
33
|
const messages = priorMessages
|
|
@@ -40,7 +51,7 @@ export async function runAgent({
|
|
|
40
51
|
try {
|
|
41
52
|
res = await agentTurnStream({
|
|
42
53
|
messages,
|
|
43
|
-
tools
|
|
54
|
+
tools,
|
|
44
55
|
onDelta: (text) => {
|
|
45
56
|
if (!lastWasText) {
|
|
46
57
|
process.stdout.write(" ");
|
|
@@ -96,7 +107,15 @@ export async function runAgent({
|
|
|
96
107
|
console.log("");
|
|
97
108
|
console.log(toolHeader(call.function.name, args));
|
|
98
109
|
|
|
99
|
-
|
|
110
|
+
// Route to MCP if the tool name is namespaced (mcp__server__tool);
|
|
111
|
+
// otherwise execute the built-in tool. unnamespaceToolName returns
|
|
112
|
+
// null for non-MCP names, which is our cheap dispatch test.
|
|
113
|
+
let result;
|
|
114
|
+
if (mcpManager && unnamespaceToolName(call.function.name)) {
|
|
115
|
+
result = await mcpManager.callTool(call.function.name, args);
|
|
116
|
+
} else {
|
|
117
|
+
result = await executeTool(call, { cwd, autoYes, unsafePaths });
|
|
118
|
+
}
|
|
100
119
|
if (result.output) {
|
|
101
120
|
const preview = result.output.length > 800 ? result.output.slice(0, 800) + "\n…(truncated)" : result.output;
|
|
102
121
|
console.log(toolResult(preview, result.ok));
|
package/src/mcp.js
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// MCP (Model Context Protocol) client manager.
|
|
2
|
+
//
|
|
3
|
+
// Lets aether-code consume any MCP server as agent tools. Users configure
|
|
4
|
+
// servers in ~/.aether/mcp.json (mirror of Claude Code's pattern):
|
|
5
|
+
//
|
|
6
|
+
// {
|
|
7
|
+
// "mcpServers": {
|
|
8
|
+
// "filesystem": {
|
|
9
|
+
// "command": "npx",
|
|
10
|
+
// "args": ["-y", "@modelcontextprotocol/server-filesystem", "/some/path"]
|
|
11
|
+
// },
|
|
12
|
+
// "ida": {
|
|
13
|
+
// "command": "python",
|
|
14
|
+
// "args": ["-m", "ida_pro_mcp"],
|
|
15
|
+
// "env": { "IDA_PATH": "/opt/ida" }
|
|
16
|
+
// }
|
|
17
|
+
// }
|
|
18
|
+
// }
|
|
19
|
+
//
|
|
20
|
+
// Each server's tools are exposed to the model namespaced as
|
|
21
|
+
// `mcp__<serverName>__<toolName>` so a `filesystem` server's `read_file`
|
|
22
|
+
// doesn't collide with our built-in `read_file`. The manager handles the
|
|
23
|
+
// JSON-RPC handshake, tool discovery, call routing, and cleanup on exit.
|
|
24
|
+
//
|
|
25
|
+
// Failure model is fail-soft per server: if `ida` fails to start (binary
|
|
26
|
+
// missing, init handshake errors out), we log it and continue with the
|
|
27
|
+
// servers that DID start. One bad server should not break the whole CLI.
|
|
28
|
+
|
|
29
|
+
import fs from "node:fs";
|
|
30
|
+
import os from "node:os";
|
|
31
|
+
import path from "node:path";
|
|
32
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
33
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
34
|
+
|
|
35
|
+
const DEFAULT_CONFIG_PATH = path.join(os.homedir(), ".aether", "mcp.json");
|
|
36
|
+
const NAMESPACE_SEP = "__";
|
|
37
|
+
const NAMESPACE_PREFIX = "mcp" + NAMESPACE_SEP;
|
|
38
|
+
const INIT_TIMEOUT_MS = 10_000;
|
|
39
|
+
const CALL_TIMEOUT_MS = 60_000;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Read + validate the MCP config file. Returns the parsed config or null if
|
|
43
|
+
* the file is missing (which is the normal case for users who don't use MCP).
|
|
44
|
+
* Throws on malformed JSON or schema violations so the user sees the error
|
|
45
|
+
* immediately instead of silently running without their servers.
|
|
46
|
+
*/
|
|
47
|
+
export function loadMcpConfig(configPath) {
|
|
48
|
+
const p = configPath || DEFAULT_CONFIG_PATH;
|
|
49
|
+
if (!fs.existsSync(p)) return null;
|
|
50
|
+
let raw;
|
|
51
|
+
try {
|
|
52
|
+
raw = fs.readFileSync(p, "utf8");
|
|
53
|
+
} catch (e) {
|
|
54
|
+
throw new Error(`MCP config exists at ${p} but isn't readable: ${e.message}`);
|
|
55
|
+
}
|
|
56
|
+
let parsed;
|
|
57
|
+
try {
|
|
58
|
+
parsed = JSON.parse(raw);
|
|
59
|
+
} catch (e) {
|
|
60
|
+
throw new Error(`MCP config at ${p} is not valid JSON: ${e.message}`);
|
|
61
|
+
}
|
|
62
|
+
return validateMcpConfig(parsed, p);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function validateMcpConfig(parsed, sourcePath = "(inline)") {
|
|
66
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
67
|
+
throw new Error(`MCP config at ${sourcePath} must be a JSON object`);
|
|
68
|
+
}
|
|
69
|
+
const servers = parsed.mcpServers;
|
|
70
|
+
if (!servers || typeof servers !== "object" || Array.isArray(servers)) {
|
|
71
|
+
throw new Error(`MCP config at ${sourcePath} needs an "mcpServers" object`);
|
|
72
|
+
}
|
|
73
|
+
for (const [name, cfg] of Object.entries(servers)) {
|
|
74
|
+
if (!/^[a-z0-9_-]{1,40}$/i.test(name)) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`MCP server name "${name}" invalid — must be 1-40 chars of [A-Za-z0-9_-]. Used in tool namespacing.`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
if (!cfg || typeof cfg !== "object") {
|
|
80
|
+
throw new Error(`MCP server "${name}" entry must be an object`);
|
|
81
|
+
}
|
|
82
|
+
if (typeof cfg.command !== "string" || cfg.command.length === 0) {
|
|
83
|
+
throw new Error(`MCP server "${name}" needs a non-empty "command" string`);
|
|
84
|
+
}
|
|
85
|
+
if (cfg.args !== undefined && !Array.isArray(cfg.args)) {
|
|
86
|
+
throw new Error(`MCP server "${name}".args must be an array of strings`);
|
|
87
|
+
}
|
|
88
|
+
if (Array.isArray(cfg.args) && cfg.args.some((a) => typeof a !== "string")) {
|
|
89
|
+
throw new Error(`MCP server "${name}".args must be an array of STRINGS`);
|
|
90
|
+
}
|
|
91
|
+
if (cfg.env !== undefined && (typeof cfg.env !== "object" || Array.isArray(cfg.env))) {
|
|
92
|
+
throw new Error(`MCP server "${name}".env must be an object {KEY: value}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return parsed;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Build the `mcp__<server>__<tool>` namespaced tool name. Defense against
|
|
100
|
+
* underscores in the user-defined server name accidentally collapsing the
|
|
101
|
+
* boundary (config validation already rejects those, but we double-check
|
|
102
|
+
* here since this string is what the model sees).
|
|
103
|
+
*/
|
|
104
|
+
export function namespaceToolName(serverName, toolName) {
|
|
105
|
+
if (serverName.includes(NAMESPACE_SEP) || toolName.includes(NAMESPACE_SEP)) {
|
|
106
|
+
// Replace with a single underscore so the boundary stays unambiguous.
|
|
107
|
+
return (
|
|
108
|
+
NAMESPACE_PREFIX +
|
|
109
|
+
serverName.replaceAll(NAMESPACE_SEP, "_") +
|
|
110
|
+
NAMESPACE_SEP +
|
|
111
|
+
toolName.replaceAll(NAMESPACE_SEP, "_")
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
return NAMESPACE_PREFIX + serverName + NAMESPACE_SEP + toolName;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Inverse of namespaceToolName: split a `mcp__server__tool` name back into
|
|
119
|
+
* its parts. Returns null if the name doesn't follow the convention (i.e.
|
|
120
|
+
* it's a built-in tool, not an MCP one).
|
|
121
|
+
*/
|
|
122
|
+
export function unnamespaceToolName(namespaced) {
|
|
123
|
+
if (!namespaced.startsWith(NAMESPACE_PREFIX)) return null;
|
|
124
|
+
const rest = namespaced.slice(NAMESPACE_PREFIX.length);
|
|
125
|
+
const sepIdx = rest.indexOf(NAMESPACE_SEP);
|
|
126
|
+
if (sepIdx <= 0 || sepIdx >= rest.length - NAMESPACE_SEP.length) return null;
|
|
127
|
+
return {
|
|
128
|
+
serverName: rest.slice(0, sepIdx),
|
|
129
|
+
toolName: rest.slice(sepIdx + NAMESPACE_SEP.length),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export class MCPManager {
|
|
134
|
+
constructor() {
|
|
135
|
+
this.servers = new Map(); // name -> { client, transport, tools: [...] }
|
|
136
|
+
this.startErrors = []; // [{ serverName, error }]
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Start every server in the config. Failures are collected (in
|
|
141
|
+
* `startErrors`) rather than thrown so one bad server doesn't kill the
|
|
142
|
+
* CLI. Returns the count of successfully-started servers.
|
|
143
|
+
*/
|
|
144
|
+
async start(config) {
|
|
145
|
+
if (!config || !config.mcpServers) return 0;
|
|
146
|
+
const entries = Object.entries(config.mcpServers);
|
|
147
|
+
await Promise.allSettled(
|
|
148
|
+
entries.map(async ([name, cfg]) => {
|
|
149
|
+
try {
|
|
150
|
+
await this.#startOne(name, cfg);
|
|
151
|
+
} catch (e) {
|
|
152
|
+
this.startErrors.push({ serverName: name, error: e.message || String(e) });
|
|
153
|
+
}
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
156
|
+
return this.servers.size;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async #startOne(name, cfg) {
|
|
160
|
+
const transport = new StdioClientTransport({
|
|
161
|
+
command: cfg.command,
|
|
162
|
+
args: cfg.args ?? [],
|
|
163
|
+
env: { ...process.env, ...(cfg.env ?? {}) },
|
|
164
|
+
});
|
|
165
|
+
const client = new Client(
|
|
166
|
+
{ name: "aether-code", version: "0.9.0" },
|
|
167
|
+
{ capabilities: {} },
|
|
168
|
+
);
|
|
169
|
+
// Bound the init handshake so a hung server doesn't stall startup.
|
|
170
|
+
await withTimeout(client.connect(transport), INIT_TIMEOUT_MS, `MCP server "${name}" init`);
|
|
171
|
+
const toolList = await withTimeout(client.listTools(), INIT_TIMEOUT_MS, `MCP server "${name}" listTools`);
|
|
172
|
+
const tools = (toolList?.tools ?? []).map((t) => ({
|
|
173
|
+
originalName: t.name,
|
|
174
|
+
namespacedName: namespaceToolName(name, t.name),
|
|
175
|
+
description: t.description ?? "",
|
|
176
|
+
inputSchema: t.inputSchema ?? { type: "object", properties: {} },
|
|
177
|
+
}));
|
|
178
|
+
this.servers.set(name, { client, transport, tools });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Returns OpenAI-format tool definitions for every connected MCP tool,
|
|
183
|
+
* ready to merge into TOOL_DEFINITIONS for the agent loop.
|
|
184
|
+
*/
|
|
185
|
+
getToolDefinitions() {
|
|
186
|
+
const out = [];
|
|
187
|
+
for (const { tools } of this.servers.values()) {
|
|
188
|
+
for (const t of tools) {
|
|
189
|
+
out.push({
|
|
190
|
+
type: "function",
|
|
191
|
+
function: {
|
|
192
|
+
name: t.namespacedName,
|
|
193
|
+
description: t.description || `(MCP tool ${t.originalName})`,
|
|
194
|
+
parameters: t.inputSchema,
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return out;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Resolve a namespaced tool name to the right server + original name.
|
|
204
|
+
* Returns null if the name isn't an MCP tool or no server matches.
|
|
205
|
+
*/
|
|
206
|
+
resolve(namespacedName) {
|
|
207
|
+
const split = unnamespaceToolName(namespacedName);
|
|
208
|
+
if (!split) return null;
|
|
209
|
+
const server = this.servers.get(split.serverName);
|
|
210
|
+
if (!server) return null;
|
|
211
|
+
return { server, serverName: split.serverName, toolName: split.toolName };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Invoke a namespaced MCP tool. Returns { ok, output } matching the
|
|
216
|
+
* shape executeTool() uses for built-in tools.
|
|
217
|
+
*/
|
|
218
|
+
async callTool(namespacedName, args) {
|
|
219
|
+
const resolved = this.resolve(namespacedName);
|
|
220
|
+
if (!resolved) {
|
|
221
|
+
return { ok: false, output: `Unknown MCP tool: ${namespacedName}` };
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
const result = await withTimeout(
|
|
225
|
+
resolved.server.client.callTool({
|
|
226
|
+
name: resolved.toolName,
|
|
227
|
+
arguments: args,
|
|
228
|
+
}),
|
|
229
|
+
CALL_TIMEOUT_MS,
|
|
230
|
+
`MCP tool ${namespacedName}`,
|
|
231
|
+
);
|
|
232
|
+
// MCP tool results are { content: [{type, text|data, ...}], isError? }
|
|
233
|
+
const textParts = (result?.content ?? [])
|
|
234
|
+
.filter((c) => c.type === "text")
|
|
235
|
+
.map((c) => c.text);
|
|
236
|
+
const text = textParts.join("\n") || JSON.stringify(result, null, 2);
|
|
237
|
+
return { ok: !result?.isError, output: text };
|
|
238
|
+
} catch (e) {
|
|
239
|
+
return { ok: false, output: `MCP call ${namespacedName} failed: ${e.message}` };
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async shutdown() {
|
|
244
|
+
const tasks = [];
|
|
245
|
+
for (const { client } of this.servers.values()) {
|
|
246
|
+
tasks.push(client.close().catch(() => {}));
|
|
247
|
+
}
|
|
248
|
+
await Promise.allSettled(tasks);
|
|
249
|
+
this.servers.clear();
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function withTimeout(promise, ms, label) {
|
|
254
|
+
let timer;
|
|
255
|
+
const timeout = new Promise((_, reject) => {
|
|
256
|
+
timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
257
|
+
});
|
|
258
|
+
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
259
|
+
}
|
package/src/repl.js
CHANGED
|
@@ -32,7 +32,7 @@ ${c.gray("Anything else is sent to the agent as your next message.")}
|
|
|
32
32
|
${c.gray("Conversation history is kept across messages until you /clear.")}
|
|
33
33
|
`;
|
|
34
34
|
|
|
35
|
-
export async function runRepl({ cwd: initialCwd, autoYes: initialAutoYes, maxTurns: initialMaxTurns }) {
|
|
35
|
+
export async function runRepl({ cwd: initialCwd, autoYes: initialAutoYes, maxTurns: initialMaxTurns, mcpManager = null }) {
|
|
36
36
|
const state = {
|
|
37
37
|
cwd: initialCwd,
|
|
38
38
|
autoYes: !!initialAutoYes,
|
|
@@ -125,6 +125,7 @@ export async function runRepl({ cwd: initialCwd, autoYes: initialAutoYes, maxTur
|
|
|
125
125
|
cwd: state.cwd,
|
|
126
126
|
autoYes: state.autoYes,
|
|
127
127
|
maxTurns: state.maxTurns,
|
|
128
|
+
mcpManager,
|
|
128
129
|
});
|
|
129
130
|
|
|
130
131
|
state.sessionCredits += result.totalCredits ?? 0;
|