@moikapy/lich 0.5.1 → 0.7.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/CHANGELOG.md +33 -0
- package/README.md +84 -4
- package/dist/chunk-JYURFAGB.js +92 -0
- package/dist/chunk-JYURFAGB.js.map +1 -0
- package/dist/{chunk-CV2YH3FH.js → chunk-QVJCIZIF.js} +748 -13
- package/dist/chunk-QVJCIZIF.js.map +1 -0
- package/dist/chunk-SAEB3QL3.js +58 -0
- package/dist/chunk-SAEB3QL3.js.map +1 -0
- package/dist/cli.js +263 -22
- package/dist/cli.js.map +1 -1
- package/dist/{gateway-W6S43ETE.js → gateway-5BG3YCZF.js} +2 -2
- package/dist/index.d.ts +229 -118
- package/dist/index.js +5 -3
- package/dist/{tui-2VO6LAFM.js → tui-L6RABP2J.js} +43 -41
- package/dist/tui-L6RABP2J.js.map +1 -0
- package/docs/.vitepress/config.mts +6 -1
- package/docs/architecture/agent-loop.md +5 -3
- package/docs/architecture/overview.md +36 -22
- package/docs/architecture/plugins.md +1 -1
- package/docs/architecture/tools.md +7 -2
- package/docs/getting-started.md +9 -7
- package/docs/index.md +6 -4
- package/docs/user-guide/cli.md +24 -9
- package/docs/user-guide/games.md +93 -0
- package/docs/user-guide/godot.md +3 -1
- package/docs/user-guide/library.md +8 -2
- package/docs/user-guide/plugins.md +2 -2
- package/docs/user-guide/redot.md +93 -0
- package/docs/user-guide/tui.md +11 -11
- package/examples/game_bridge/README.md +2 -0
- package/optional-mcps/godot/manifest.json +6 -0
- package/optional-mcps/redot/manifest.json +18 -0
- package/package.json +2 -1
- package/dist/chunk-6M6OAQGN.js +0 -17
- package/dist/chunk-6M6OAQGN.js.map +0 -1
- package/dist/chunk-CV2YH3FH.js.map +0 -1
- package/dist/tui-2VO6LAFM.js.map +0 -1
- /package/dist/{gateway-W6S43ETE.js.map → gateway-5BG3YCZF.js.map} +0 -0
|
@@ -1912,12 +1912,178 @@ function read_platform_token(config, platform) {
|
|
|
1912
1912
|
return value;
|
|
1913
1913
|
}
|
|
1914
1914
|
|
|
1915
|
+
// src/mcp/mcp_pin.ts
|
|
1916
|
+
import path11 from "path";
|
|
1917
|
+
|
|
1918
|
+
// src/mcp/mcp_catalog.ts
|
|
1919
|
+
import { existsSync as existsSync2, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "fs";
|
|
1920
|
+
import path9 from "path";
|
|
1921
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1922
|
+
var cached;
|
|
1923
|
+
function catalog_dir() {
|
|
1924
|
+
let dir = path9.dirname(fileURLToPath2(import.meta.url));
|
|
1925
|
+
for (let hop = 0; hop < 6; hop += 1) {
|
|
1926
|
+
const candidate = path9.join(dir, "optional-mcps");
|
|
1927
|
+
if (existsSync2(path9.join(candidate, "redot", "manifest.json")) === true) {
|
|
1928
|
+
return candidate;
|
|
1929
|
+
}
|
|
1930
|
+
dir = path9.dirname(dir);
|
|
1931
|
+
}
|
|
1932
|
+
throw new Error("mcp catalog not found");
|
|
1933
|
+
}
|
|
1934
|
+
function read_manifest(file) {
|
|
1935
|
+
const parsed = safe_json_parse(readFileSync4(file, "utf8"));
|
|
1936
|
+
if (parsed === void 0 || typeof parsed.name !== "string") {
|
|
1937
|
+
throw new Error("mcp catalog manifest rejected");
|
|
1938
|
+
}
|
|
1939
|
+
return parsed;
|
|
1940
|
+
}
|
|
1941
|
+
function load_catalog() {
|
|
1942
|
+
if (cached !== void 0) {
|
|
1943
|
+
return cached;
|
|
1944
|
+
}
|
|
1945
|
+
const manifests = [];
|
|
1946
|
+
for (const name of readdirSync3(catalog_dir())) {
|
|
1947
|
+
const file = path9.join(catalog_dir(), name, "manifest.json");
|
|
1948
|
+
if (existsSync2(file) === true) {
|
|
1949
|
+
manifests.push(read_manifest(file));
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
cached = manifests;
|
|
1953
|
+
return cached;
|
|
1954
|
+
}
|
|
1955
|
+
function catalog_by_name(name) {
|
|
1956
|
+
return load_catalog().find((entry) => entry.name === name);
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
// src/mcp/mcp_refuse.ts
|
|
1960
|
+
import path10 from "path";
|
|
1961
|
+
var SHELL = /[;&|`$<>]/;
|
|
1962
|
+
var DOWNLOADERS = /* @__PURE__ */ new Set(["npx", "npm", "bunx", "uvx", "curl", "wget"]);
|
|
1963
|
+
function refuse_stdio_command(command) {
|
|
1964
|
+
if (command.includes("://") === true) {
|
|
1965
|
+
return "refused url; only a local binary is allowed";
|
|
1966
|
+
}
|
|
1967
|
+
if (SHELL.test(command) === true) {
|
|
1968
|
+
return "refused shell metacharacters in mcp command";
|
|
1969
|
+
}
|
|
1970
|
+
if (command.length === 0 || command.trim() !== command || /\s/.test(command) === true) {
|
|
1971
|
+
return "refused mcp command";
|
|
1972
|
+
}
|
|
1973
|
+
const base = path10.basename(command);
|
|
1974
|
+
if (DOWNLOADERS.has(base) === true) {
|
|
1975
|
+
return `refused download command '${base}'`;
|
|
1976
|
+
}
|
|
1977
|
+
return void 0;
|
|
1978
|
+
}
|
|
1979
|
+
function refuse_stdio_arg(arg) {
|
|
1980
|
+
if (arg.includes("://") === true) {
|
|
1981
|
+
return "refused url in mcp args";
|
|
1982
|
+
}
|
|
1983
|
+
if (SHELL.test(arg) === true) {
|
|
1984
|
+
return "refused shell metacharacters in mcp args";
|
|
1985
|
+
}
|
|
1986
|
+
return void 0;
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
// src/mcp/mcp_url.ts
|
|
1990
|
+
function refuse_http_url(url) {
|
|
1991
|
+
let parsed;
|
|
1992
|
+
try {
|
|
1993
|
+
parsed = new URL(url);
|
|
1994
|
+
} catch {
|
|
1995
|
+
return "refused mcp url";
|
|
1996
|
+
}
|
|
1997
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
1998
|
+
return "refused mcp url";
|
|
1999
|
+
}
|
|
2000
|
+
if (parsed.username.length > 0 || parsed.password.length > 0) {
|
|
2001
|
+
return "refused mcp url credentials";
|
|
2002
|
+
}
|
|
2003
|
+
const host = parsed.hostname.toLowerCase();
|
|
2004
|
+
if (host !== "127.0.0.1" && host !== "localhost") {
|
|
2005
|
+
return "refused mcp url; loopback only";
|
|
2006
|
+
}
|
|
2007
|
+
return void 0;
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
// src/mcp/mcp_pin.ts
|
|
2011
|
+
function refuse_catalog_stdio(name, command, args) {
|
|
2012
|
+
const pin = catalog_by_name(name);
|
|
2013
|
+
if (pin?.command_basename === void 0) {
|
|
2014
|
+
return void 0;
|
|
2015
|
+
}
|
|
2016
|
+
if (path11.basename(command) !== pin.command_basename) {
|
|
2017
|
+
return `refused command basename '${path11.basename(command)}'; only '${pin.command_basename}' is allowed`;
|
|
2018
|
+
}
|
|
2019
|
+
const prefix = pin.args_prefix ?? [];
|
|
2020
|
+
if (args.length !== prefix.length + 1) {
|
|
2021
|
+
return `refused ${name} args; expected ${prefix.join(" ")} <project>`;
|
|
2022
|
+
}
|
|
2023
|
+
for (let index = 0; index < prefix.length; index += 1) {
|
|
2024
|
+
if (args[index] !== prefix[index]) {
|
|
2025
|
+
return `refused ${name} args; expected ${prefix.join(" ")} <project>`;
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
const project = args[prefix.length];
|
|
2029
|
+
if (project === void 0 || project.length === 0 || project.includes("://") === true) {
|
|
2030
|
+
return "refused project path; pass a local project directory";
|
|
2031
|
+
}
|
|
2032
|
+
return void 0;
|
|
2033
|
+
}
|
|
2034
|
+
function refuse_arg_list(args) {
|
|
2035
|
+
for (const arg of args) {
|
|
2036
|
+
const refused = refuse_stdio_arg(arg);
|
|
2037
|
+
if (refused !== void 0) {
|
|
2038
|
+
return refused;
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
return void 0;
|
|
2042
|
+
}
|
|
2043
|
+
function refuse_mcp_entry(name, entry) {
|
|
2044
|
+
if (typeof entry.url === "string") {
|
|
2045
|
+
return refuse_http_url(entry.url);
|
|
2046
|
+
}
|
|
2047
|
+
if (typeof entry.command !== "string") {
|
|
2048
|
+
return "refused mcp entry";
|
|
2049
|
+
}
|
|
2050
|
+
const args = entry.args ?? [];
|
|
2051
|
+
return refuse_stdio_command(entry.command) ?? refuse_catalog_stdio(name, entry.command, args) ?? refuse_arg_list(args);
|
|
2052
|
+
}
|
|
2053
|
+
|
|
1915
2054
|
// src/agent/config.ts
|
|
1916
2055
|
var gateway_schema = z.object({
|
|
1917
2056
|
platforms: z.array(z.enum(["webhook", "telegram", "discord", "twitch"])).default([]),
|
|
1918
2057
|
/** Env-var names that hold tokens. Never store the secrets themselves. */
|
|
1919
2058
|
token_envs: z.record(z.string(), z.string().regex(ENV_VAR_NAME, "invalid env var name")).default({})
|
|
1920
2059
|
}).optional();
|
|
2060
|
+
var stdio_mcp_schema = z.object({
|
|
2061
|
+
enabled: z.boolean().default(false),
|
|
2062
|
+
command: z.string().min(1),
|
|
2063
|
+
args: z.array(z.string()).default([]),
|
|
2064
|
+
env: z.record(z.string().regex(ENV_VAR_NAME, "invalid env var name"), z.string()).optional()
|
|
2065
|
+
}).strict();
|
|
2066
|
+
var http_mcp_schema = z.object({
|
|
2067
|
+
enabled: z.boolean().default(false),
|
|
2068
|
+
url: z.string().min(1)
|
|
2069
|
+
}).strict();
|
|
2070
|
+
var mcp_server_schema = z.union([stdio_mcp_schema, http_mcp_schema]);
|
|
2071
|
+
var SERVER_NAME = /^[a-z][a-z0-9_]*$/;
|
|
2072
|
+
var mcp_servers_schema = z.record(z.string(), mcp_server_schema).superRefine((servers, ctx) => {
|
|
2073
|
+
for (const [name, entry] of Object.entries(servers)) {
|
|
2074
|
+
if (SERVER_NAME.test(name) === false) {
|
|
2075
|
+
ctx.addIssue({
|
|
2076
|
+
code: z.ZodIssueCode.custom,
|
|
2077
|
+
path: [name],
|
|
2078
|
+
message: "mcp server name must be snake_case"
|
|
2079
|
+
});
|
|
2080
|
+
}
|
|
2081
|
+
const refused = refuse_mcp_entry(name, entry);
|
|
2082
|
+
if (refused !== void 0) {
|
|
2083
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: [name], message: refused });
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
}).optional();
|
|
1921
2087
|
var provider_schema = z.object({
|
|
1922
2088
|
kind: z.enum(["openai_compat", "anthropic", "ollama"]),
|
|
1923
2089
|
name: z.string().min(1),
|
|
@@ -1930,7 +2096,7 @@ var provider_schema = z.object({
|
|
|
1930
2096
|
fetch_fn: z.custom(() => true).optional()
|
|
1931
2097
|
}).passthrough();
|
|
1932
2098
|
var agent_config_schema = z.object({
|
|
1933
|
-
/**
|
|
2099
|
+
/** Wizard label. The TUI banner uses the active theme welcome string. */
|
|
1934
2100
|
agent_name: z.string().min(1).default("lich"),
|
|
1935
2101
|
system_prompt: z.string().optional(),
|
|
1936
2102
|
max_turns: z.number().int().min(1).default(25),
|
|
@@ -1946,7 +2112,10 @@ var agent_config_schema = z.object({
|
|
|
1946
2112
|
/** Plugin entry module specifiers, relative to work_dir or absolute. */
|
|
1947
2113
|
plugins: z.array(z.string()).default([]),
|
|
1948
2114
|
gateway: gateway_schema,
|
|
1949
|
-
log_level: z.enum(["debug", "info", "warn", "error"]).default("info")
|
|
2115
|
+
log_level: z.enum(["debug", "info", "warn", "error"]).default("info"),
|
|
2116
|
+
theme: z.string().min(1).default("lich"),
|
|
2117
|
+
/** Named MCP servers. Each entry is stdio or loopback http. Default off. */
|
|
2118
|
+
mcp_servers: mcp_servers_schema
|
|
1950
2119
|
}).transform((config) => {
|
|
1951
2120
|
const work_dir = config.work_dir ?? process.cwd();
|
|
1952
2121
|
return {
|
|
@@ -1967,6 +2136,18 @@ function freeze_config(config) {
|
|
|
1967
2136
|
Object.freeze(config.gateway.token_envs);
|
|
1968
2137
|
Object.freeze(config.gateway);
|
|
1969
2138
|
}
|
|
2139
|
+
if (config.mcp_servers !== void 0) {
|
|
2140
|
+
for (const entry of Object.values(config.mcp_servers)) {
|
|
2141
|
+
if ("args" in entry) {
|
|
2142
|
+
Object.freeze(entry.args);
|
|
2143
|
+
}
|
|
2144
|
+
if ("env" in entry && entry.env !== void 0) {
|
|
2145
|
+
Object.freeze(entry.env);
|
|
2146
|
+
}
|
|
2147
|
+
Object.freeze(entry);
|
|
2148
|
+
}
|
|
2149
|
+
Object.freeze(config.mcp_servers);
|
|
2150
|
+
}
|
|
1970
2151
|
return config;
|
|
1971
2152
|
}
|
|
1972
2153
|
function parse_agent_config(raw) {
|
|
@@ -1999,15 +2180,543 @@ var AgentEmitter = class {
|
|
|
1999
2180
|
}
|
|
2000
2181
|
};
|
|
2001
2182
|
|
|
2002
|
-
// src/
|
|
2183
|
+
// src/mcp/mcp_http.ts
|
|
2184
|
+
async function post_rpc(url, fetch_fn, body) {
|
|
2185
|
+
const response = await fetch_fn(url, {
|
|
2186
|
+
method: "POST",
|
|
2187
|
+
redirect: "error",
|
|
2188
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
2189
|
+
body: JSON.stringify(body)
|
|
2190
|
+
});
|
|
2191
|
+
const parsed = await response.json();
|
|
2192
|
+
if (parsed.error !== void 0) {
|
|
2193
|
+
const detail = parsed.error.message;
|
|
2194
|
+
throw new Error(typeof detail === "string" && detail.length > 0 ? detail : "mcp error");
|
|
2195
|
+
}
|
|
2196
|
+
return parsed.result;
|
|
2197
|
+
}
|
|
2198
|
+
function http_pipe(url, fetch_fn) {
|
|
2199
|
+
let next_id = 1;
|
|
2200
|
+
return {
|
|
2201
|
+
request(method, params) {
|
|
2202
|
+
const id = next_id;
|
|
2203
|
+
next_id += 1;
|
|
2204
|
+
return post_rpc(url, fetch_fn, { jsonrpc: "2.0", id, method, params });
|
|
2205
|
+
},
|
|
2206
|
+
notify(method) {
|
|
2207
|
+
void fetch_fn(url, {
|
|
2208
|
+
method: "POST",
|
|
2209
|
+
redirect: "error",
|
|
2210
|
+
headers: { "content-type": "application/json" },
|
|
2211
|
+
body: JSON.stringify({ jsonrpc: "2.0", method })
|
|
2212
|
+
}).catch(() => void 0);
|
|
2213
|
+
},
|
|
2214
|
+
close() {
|
|
2215
|
+
return void 0;
|
|
2216
|
+
}
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
// src/mcp/mcp_plan.ts
|
|
2221
|
+
import { existsSync as existsSync3 } from "fs";
|
|
2222
|
+
import path12 from "path";
|
|
2223
|
+
function find_on_path(command, env_path) {
|
|
2224
|
+
if (env_path === void 0 || env_path.length === 0) {
|
|
2225
|
+
return void 0;
|
|
2226
|
+
}
|
|
2227
|
+
for (const dir of env_path.split(path12.delimiter)) {
|
|
2228
|
+
if (dir.length === 0) {
|
|
2229
|
+
continue;
|
|
2230
|
+
}
|
|
2231
|
+
const candidate = path12.join(dir, command);
|
|
2232
|
+
if (existsSync3(candidate) === true) {
|
|
2233
|
+
return candidate;
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
return void 0;
|
|
2237
|
+
}
|
|
2238
|
+
function locate(command, env_path) {
|
|
2239
|
+
if (command.includes("/") === true || command.includes("\\") === true) {
|
|
2240
|
+
return existsSync3(command) === true ? command : void 0;
|
|
2241
|
+
}
|
|
2242
|
+
return find_on_path(command, env_path);
|
|
2243
|
+
}
|
|
2244
|
+
function plan_stdio(name, command, args, env_path) {
|
|
2245
|
+
const refused = refuse_mcp_entry(name, { command, args });
|
|
2246
|
+
if (refused !== void 0) {
|
|
2247
|
+
return refused;
|
|
2248
|
+
}
|
|
2249
|
+
const binary = locate(command, env_path);
|
|
2250
|
+
if (binary === void 0) {
|
|
2251
|
+
return catalog_by_name(name)?.missing_hint ?? "mcp command not found";
|
|
2252
|
+
}
|
|
2253
|
+
return { command: binary, args };
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
// src/mcp/mcp_pipe.ts
|
|
2257
|
+
var SKIP_LIMIT = 32;
|
|
2258
|
+
function parse_rpc_line(line) {
|
|
2259
|
+
const parsed = safe_json_parse(line);
|
|
2260
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
2261
|
+
return void 0;
|
|
2262
|
+
}
|
|
2263
|
+
return parsed;
|
|
2264
|
+
}
|
|
2265
|
+
async function read_id(child, id) {
|
|
2266
|
+
for (let skipped = 0; skipped < SKIP_LIMIT; skipped += 1) {
|
|
2267
|
+
const line = await child.read_line();
|
|
2268
|
+
const failure = child.failed();
|
|
2269
|
+
if (failure !== void 0) {
|
|
2270
|
+
throw new Error(failure);
|
|
2271
|
+
}
|
|
2272
|
+
if (line === void 0) {
|
|
2273
|
+
throw new Error("mcp closed the pipe");
|
|
2274
|
+
}
|
|
2275
|
+
const parsed = parse_rpc_line(line);
|
|
2276
|
+
if (parsed === void 0 || parsed.id !== id) {
|
|
2277
|
+
continue;
|
|
2278
|
+
}
|
|
2279
|
+
if (parsed.error !== void 0) {
|
|
2280
|
+
const detail = parsed.error.message;
|
|
2281
|
+
throw new Error(typeof detail === "string" && detail.length > 0 ? detail : "mcp error");
|
|
2282
|
+
}
|
|
2283
|
+
return parsed.result;
|
|
2284
|
+
}
|
|
2285
|
+
throw new Error("mcp sent no matching response");
|
|
2286
|
+
}
|
|
2287
|
+
function stdio_pipe(child) {
|
|
2288
|
+
let next_id = 1;
|
|
2289
|
+
return {
|
|
2290
|
+
request(method, params) {
|
|
2291
|
+
const id = next_id;
|
|
2292
|
+
next_id += 1;
|
|
2293
|
+
child.write_line(JSON.stringify({ jsonrpc: "2.0", id, method, params }));
|
|
2294
|
+
return read_id(child, id);
|
|
2295
|
+
},
|
|
2296
|
+
notify(method) {
|
|
2297
|
+
child.write_line(JSON.stringify({ jsonrpc: "2.0", method }));
|
|
2298
|
+
},
|
|
2299
|
+
close() {
|
|
2300
|
+
child.stop();
|
|
2301
|
+
}
|
|
2302
|
+
};
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
// src/mcp/mcp_names.ts
|
|
2306
|
+
function mcp_tool_name(server, tool) {
|
|
2307
|
+
return `mcp_${sanitize(server)}_${sanitize(tool)}`;
|
|
2308
|
+
}
|
|
2309
|
+
function sanitize(value) {
|
|
2310
|
+
return value.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
// src/mcp/mcp_register.ts
|
|
2314
|
+
function name_allowed(enabled, name) {
|
|
2315
|
+
if (enabled === "all") {
|
|
2316
|
+
return true;
|
|
2317
|
+
}
|
|
2318
|
+
return enabled.includes(name);
|
|
2319
|
+
}
|
|
2320
|
+
function run_call(session, wire_name, args, context) {
|
|
2321
|
+
return capture_errors(async () => {
|
|
2322
|
+
if (context.signal?.aborted === true) {
|
|
2323
|
+
throw new Error("cancelled");
|
|
2324
|
+
}
|
|
2325
|
+
return { ok: true, output: await session.call_tool(wire_name, args) };
|
|
2326
|
+
});
|
|
2327
|
+
}
|
|
2328
|
+
function tool_for(registered, wire_name, spec, session) {
|
|
2329
|
+
return {
|
|
2330
|
+
name: registered,
|
|
2331
|
+
description: spec.description,
|
|
2332
|
+
parameters: spec.parameters,
|
|
2333
|
+
timeout_ms: 12e4,
|
|
2334
|
+
execute: (args, context) => run_call(session, wire_name, args, context)
|
|
2335
|
+
};
|
|
2336
|
+
}
|
|
2337
|
+
function register_listed(registry, server, listed, enabled, session) {
|
|
2338
|
+
const excluded = new Set(catalog_by_name(server)?.exclude_tools ?? []);
|
|
2339
|
+
for (const spec of listed) {
|
|
2340
|
+
if (excluded.has(spec.name) === true) {
|
|
2341
|
+
continue;
|
|
2342
|
+
}
|
|
2343
|
+
const registered = mcp_tool_name(server, spec.name);
|
|
2344
|
+
if (name_allowed(enabled, registered) === false || registry.has(registered) === true) {
|
|
2345
|
+
continue;
|
|
2346
|
+
}
|
|
2347
|
+
registry.register(tool_for(registered, spec.name, spec, session));
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
// src/mcp/mcp_handshake.ts
|
|
2352
|
+
var PROTOCOL_VERSION = "2024-11-05";
|
|
2353
|
+
function init_params() {
|
|
2354
|
+
return {
|
|
2355
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
2356
|
+
capabilities: {},
|
|
2357
|
+
clientInfo: { name: "lich", version: "1" }
|
|
2358
|
+
};
|
|
2359
|
+
}
|
|
2360
|
+
function assert_handshake(result) {
|
|
2361
|
+
if (typeof result !== "object" || result === null) {
|
|
2362
|
+
throw new Error("mcp handshake rejected");
|
|
2363
|
+
}
|
|
2364
|
+
const body = result;
|
|
2365
|
+
if (typeof body.protocolVersion !== "string" || body.protocolVersion.length === 0) {
|
|
2366
|
+
throw new Error("mcp handshake rejected");
|
|
2367
|
+
}
|
|
2368
|
+
if (typeof body.serverInfo?.name !== "string" || body.serverInfo.name.length === 0) {
|
|
2369
|
+
throw new Error("mcp handshake rejected");
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
// src/mcp/mcp_content.ts
|
|
2374
|
+
function content_text(result) {
|
|
2375
|
+
if (typeof result !== "object" || result === null) {
|
|
2376
|
+
return "";
|
|
2377
|
+
}
|
|
2378
|
+
const body = result;
|
|
2379
|
+
const parts = [];
|
|
2380
|
+
if (Array.isArray(body.content) === true) {
|
|
2381
|
+
for (const item of body.content) {
|
|
2382
|
+
if (typeof item !== "object" || item === null) {
|
|
2383
|
+
continue;
|
|
2384
|
+
}
|
|
2385
|
+
const chunk = item;
|
|
2386
|
+
if (chunk.type === "text" && typeof chunk.text === "string") {
|
|
2387
|
+
parts.push(chunk.text);
|
|
2388
|
+
}
|
|
2389
|
+
if (chunk.type === "image") {
|
|
2390
|
+
parts.push("[image omitted]");
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
const text = parts.join("\n");
|
|
2395
|
+
if (body.isError === true) {
|
|
2396
|
+
throw new Error(text.length > 0 ? text : "mcp tool failed");
|
|
2397
|
+
}
|
|
2398
|
+
return text;
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
// src/mcp/mcp_result.ts
|
|
2402
|
+
function tool_schema(raw) {
|
|
2403
|
+
if (typeof raw !== "object" || raw === null) {
|
|
2404
|
+
return { type: "object" };
|
|
2405
|
+
}
|
|
2406
|
+
const body = raw;
|
|
2407
|
+
const schema = { type: "object" };
|
|
2408
|
+
if (typeof body.properties === "object" && body.properties !== null) {
|
|
2409
|
+
schema.properties = body.properties;
|
|
2410
|
+
}
|
|
2411
|
+
if (Array.isArray(body.required) === true) {
|
|
2412
|
+
schema.required = body.required.filter((item) => typeof item === "string");
|
|
2413
|
+
}
|
|
2414
|
+
if (typeof body.additionalProperties === "boolean") {
|
|
2415
|
+
schema.additionalProperties = body.additionalProperties;
|
|
2416
|
+
}
|
|
2417
|
+
return schema;
|
|
2418
|
+
}
|
|
2419
|
+
function parse_tools(result) {
|
|
2420
|
+
if (typeof result !== "object" || result === null || Array.isArray(result.tools) === false) {
|
|
2421
|
+
throw new Error("mcp tools/list rejected");
|
|
2422
|
+
}
|
|
2423
|
+
const tools = [];
|
|
2424
|
+
for (const item of result.tools) {
|
|
2425
|
+
if (typeof item !== "object" || item === null) {
|
|
2426
|
+
continue;
|
|
2427
|
+
}
|
|
2428
|
+
const tool = item;
|
|
2429
|
+
if (typeof tool.name !== "string" || tool.name.length === 0) {
|
|
2430
|
+
continue;
|
|
2431
|
+
}
|
|
2432
|
+
tools.push({
|
|
2433
|
+
name: tool.name,
|
|
2434
|
+
description: typeof tool.description === "string" ? tool.description : tool.name,
|
|
2435
|
+
parameters: tool_schema(tool.inputSchema)
|
|
2436
|
+
});
|
|
2437
|
+
}
|
|
2438
|
+
return tools;
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
// src/mcp/mcp_session.ts
|
|
2442
|
+
var McpSession = class {
|
|
2443
|
+
constructor(pipe) {
|
|
2444
|
+
this.pipe = pipe;
|
|
2445
|
+
}
|
|
2446
|
+
pipe;
|
|
2447
|
+
ready_done = false;
|
|
2448
|
+
closed = false;
|
|
2449
|
+
close() {
|
|
2450
|
+
if (this.closed === true) {
|
|
2451
|
+
return;
|
|
2452
|
+
}
|
|
2453
|
+
this.closed = true;
|
|
2454
|
+
this.pipe.close();
|
|
2455
|
+
}
|
|
2456
|
+
async list_tools() {
|
|
2457
|
+
await this.ensure_ready();
|
|
2458
|
+
return parse_tools(await this.pipe.request("tools/list", {}));
|
|
2459
|
+
}
|
|
2460
|
+
async call_tool(name, args) {
|
|
2461
|
+
await this.ensure_ready();
|
|
2462
|
+
return content_text(await this.pipe.request("tools/call", { name, arguments: args }));
|
|
2463
|
+
}
|
|
2464
|
+
async ensure_ready() {
|
|
2465
|
+
if (this.ready_done === true) {
|
|
2466
|
+
return;
|
|
2467
|
+
}
|
|
2468
|
+
try {
|
|
2469
|
+
assert_handshake(await this.pipe.request("initialize", init_params()));
|
|
2470
|
+
this.pipe.notify("notifications/initialized");
|
|
2471
|
+
this.ready_done = true;
|
|
2472
|
+
} catch (error) {
|
|
2473
|
+
this.close();
|
|
2474
|
+
throw error;
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
};
|
|
2478
|
+
|
|
2479
|
+
// src/mcp/mcp_lines.ts
|
|
2480
|
+
function create_line_queue() {
|
|
2481
|
+
const pending = [];
|
|
2482
|
+
const waiters = [];
|
|
2483
|
+
let closed = false;
|
|
2484
|
+
return {
|
|
2485
|
+
push(line) {
|
|
2486
|
+
const waiter = waiters.shift();
|
|
2487
|
+
if (waiter !== void 0) {
|
|
2488
|
+
waiter(line);
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
pending.push(line);
|
|
2492
|
+
},
|
|
2493
|
+
close() {
|
|
2494
|
+
closed = true;
|
|
2495
|
+
for (const waiter of waiters.splice(0)) {
|
|
2496
|
+
waiter(void 0);
|
|
2497
|
+
}
|
|
2498
|
+
},
|
|
2499
|
+
read() {
|
|
2500
|
+
const next = pending.shift();
|
|
2501
|
+
if (next !== void 0) {
|
|
2502
|
+
return Promise.resolve(next);
|
|
2503
|
+
}
|
|
2504
|
+
if (closed === true) {
|
|
2505
|
+
return Promise.resolve(void 0);
|
|
2506
|
+
}
|
|
2507
|
+
return new Promise((resolve) => {
|
|
2508
|
+
waiters.push(resolve);
|
|
2509
|
+
});
|
|
2510
|
+
}
|
|
2511
|
+
};
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
// src/mcp/mcp_child.ts
|
|
2515
|
+
function spawn_failure(code) {
|
|
2516
|
+
if (code === "ENOENT") {
|
|
2517
|
+
return "mcp command not found";
|
|
2518
|
+
}
|
|
2519
|
+
return "mcp spawn failed";
|
|
2520
|
+
}
|
|
2521
|
+
function failed_child(message) {
|
|
2522
|
+
return {
|
|
2523
|
+
write_line() {
|
|
2524
|
+
return void 0;
|
|
2525
|
+
},
|
|
2526
|
+
read_line() {
|
|
2527
|
+
return Promise.resolve(void 0);
|
|
2528
|
+
},
|
|
2529
|
+
stop() {
|
|
2530
|
+
return void 0;
|
|
2531
|
+
},
|
|
2532
|
+
failed() {
|
|
2533
|
+
return message;
|
|
2534
|
+
}
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
// src/mcp/mcp_stdio_bun.ts
|
|
2539
|
+
async function pump_stdout(stream, queue) {
|
|
2540
|
+
const reader = stream.getReader();
|
|
2541
|
+
const decoder = new TextDecoder();
|
|
2542
|
+
let buffer = "";
|
|
2543
|
+
for (; ; ) {
|
|
2544
|
+
const next = await reader.read();
|
|
2545
|
+
if (next.done === true) {
|
|
2546
|
+
if (buffer.length > 0) {
|
|
2547
|
+
queue.push(buffer);
|
|
2548
|
+
}
|
|
2549
|
+
queue.close();
|
|
2550
|
+
return;
|
|
2551
|
+
}
|
|
2552
|
+
buffer += decoder.decode(next.value, { stream: true });
|
|
2553
|
+
const parts = buffer.split("\n");
|
|
2554
|
+
buffer = parts.pop() ?? "";
|
|
2555
|
+
for (const part of parts) {
|
|
2556
|
+
queue.push(part);
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
async function drain_stderr(stream) {
|
|
2561
|
+
const reader = stream.getReader();
|
|
2562
|
+
while ((await reader.read()).done === false) {
|
|
2563
|
+
continue;
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
function bun_line_child(command, args, env) {
|
|
2567
|
+
const queue = create_line_queue();
|
|
2568
|
+
try {
|
|
2569
|
+
const options = { stdin: "pipe", stdout: "pipe", stderr: "pipe" };
|
|
2570
|
+
const child = env === void 0 ? Bun.spawn([command, ...args], options) : Bun.spawn([command, ...args], { ...options, env });
|
|
2571
|
+
void pump_stdout(child.stdout, queue);
|
|
2572
|
+
void drain_stderr(child.stderr);
|
|
2573
|
+
return {
|
|
2574
|
+
write_line(line) {
|
|
2575
|
+
child.stdin.write(`${line}
|
|
2576
|
+
`);
|
|
2577
|
+
void child.stdin.flush();
|
|
2578
|
+
},
|
|
2579
|
+
read_line() {
|
|
2580
|
+
return queue.read();
|
|
2581
|
+
},
|
|
2582
|
+
stop() {
|
|
2583
|
+
child.kill();
|
|
2584
|
+
},
|
|
2585
|
+
failed() {
|
|
2586
|
+
return void 0;
|
|
2587
|
+
}
|
|
2588
|
+
};
|
|
2589
|
+
} catch (error) {
|
|
2590
|
+
const coded = error;
|
|
2591
|
+
return failed_child(spawn_failure(coded.code));
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
// src/mcp/mcp_stdio_node.ts
|
|
2003
2596
|
import { spawn as spawn3 } from "child_process";
|
|
2597
|
+
import { createInterface } from "readline";
|
|
2598
|
+
function child_env(extra) {
|
|
2599
|
+
if (extra === void 0) {
|
|
2600
|
+
return void 0;
|
|
2601
|
+
}
|
|
2602
|
+
const merged = {};
|
|
2603
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
2604
|
+
if (typeof value === "string") {
|
|
2605
|
+
merged[key] = value;
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
for (const [key, value] of Object.entries(extra)) {
|
|
2609
|
+
merged[key] = value;
|
|
2610
|
+
}
|
|
2611
|
+
return merged;
|
|
2612
|
+
}
|
|
2613
|
+
function node_line_child(command, args, env) {
|
|
2614
|
+
const queue = create_line_queue();
|
|
2615
|
+
let failure;
|
|
2616
|
+
const child = spawn3(command, [...args], { stdio: ["pipe", "pipe", "pipe"], env: child_env(env) });
|
|
2617
|
+
const reader = createInterface({ input: child.stdout });
|
|
2618
|
+
reader.on("line", (line) => {
|
|
2619
|
+
queue.push(line);
|
|
2620
|
+
});
|
|
2621
|
+
reader.on("close", () => {
|
|
2622
|
+
queue.close();
|
|
2623
|
+
});
|
|
2624
|
+
child.stderr?.resume();
|
|
2625
|
+
child.on("error", (error) => {
|
|
2626
|
+
failure = spawn_failure(error.code);
|
|
2627
|
+
queue.close();
|
|
2628
|
+
});
|
|
2629
|
+
return {
|
|
2630
|
+
write_line(line) {
|
|
2631
|
+
child.stdin?.write(`${line}
|
|
2632
|
+
`);
|
|
2633
|
+
},
|
|
2634
|
+
read_line() {
|
|
2635
|
+
return queue.read();
|
|
2636
|
+
},
|
|
2637
|
+
stop() {
|
|
2638
|
+
child.kill();
|
|
2639
|
+
},
|
|
2640
|
+
failed() {
|
|
2641
|
+
return failure;
|
|
2642
|
+
}
|
|
2643
|
+
};
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
// src/mcp/mcp_stdio.ts
|
|
2647
|
+
function running_under_bun() {
|
|
2648
|
+
return process.versions.bun !== void 0;
|
|
2649
|
+
}
|
|
2650
|
+
function default_line_spawner(command, args, env) {
|
|
2651
|
+
if (running_under_bun() === true) {
|
|
2652
|
+
return bun_line_child(command, args, env);
|
|
2653
|
+
}
|
|
2654
|
+
return node_line_child(command, args, env);
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
// src/mcp/mcp_attach.ts
|
|
2658
|
+
async function open_and_register(registry, name, enabled, session) {
|
|
2659
|
+
try {
|
|
2660
|
+
register_listed(registry, name, await session.list_tools(), enabled, session);
|
|
2661
|
+
} catch (error) {
|
|
2662
|
+
session.close();
|
|
2663
|
+
const message = error instanceof Error ? error.message : "mcp skipped";
|
|
2664
|
+
logger.warn(`mcp ${name} skipped: ${message}`);
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
async function attach_stdio(registry, name, entry, config, runtime) {
|
|
2668
|
+
const planned = plan_stdio(name, entry.command, entry.args, runtime?.env_path ?? process.env.PATH);
|
|
2669
|
+
if (typeof planned === "string") {
|
|
2670
|
+
logger.warn(`mcp ${name} skipped: ${planned}`);
|
|
2671
|
+
return;
|
|
2672
|
+
}
|
|
2673
|
+
const spawn5 = runtime?.spawn ?? default_line_spawner;
|
|
2674
|
+
const session = new McpSession(stdio_pipe(spawn5(planned.command, planned.args, entry.env)));
|
|
2675
|
+
await open_and_register(registry, name, config.tools_enabled, session);
|
|
2676
|
+
}
|
|
2677
|
+
async function attach_http(registry, name, url, config, runtime) {
|
|
2678
|
+
const session = new McpSession(http_pipe(url, runtime?.fetch_fn ?? fetch));
|
|
2679
|
+
await open_and_register(registry, name, config.tools_enabled, session);
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
// src/mcp/mcp_tools.ts
|
|
2683
|
+
function allowlist_wants_mcp(enabled) {
|
|
2684
|
+
if (enabled === "all") {
|
|
2685
|
+
return true;
|
|
2686
|
+
}
|
|
2687
|
+
return enabled.some((name) => name.startsWith("mcp_") === true);
|
|
2688
|
+
}
|
|
2689
|
+
async function attach_enabled_mcp_tools(registry, config, runtime) {
|
|
2690
|
+
const servers = config.mcp_servers;
|
|
2691
|
+
if (servers === void 0 || allowlist_wants_mcp(config.tools_enabled) === false) {
|
|
2692
|
+
return;
|
|
2693
|
+
}
|
|
2694
|
+
for (const [name, entry] of Object.entries(servers)) {
|
|
2695
|
+
if (entry.enabled !== true) {
|
|
2696
|
+
continue;
|
|
2697
|
+
}
|
|
2698
|
+
try {
|
|
2699
|
+
if ("url" in entry) {
|
|
2700
|
+
await attach_http(registry, name, entry.url, config, runtime);
|
|
2701
|
+
continue;
|
|
2702
|
+
}
|
|
2703
|
+
await attach_stdio(registry, name, entry, config, runtime);
|
|
2704
|
+
} catch (error) {
|
|
2705
|
+
const message = error instanceof Error ? error.message : "mcp skipped";
|
|
2706
|
+
logger.warn(`mcp ${name} skipped: ${message}`);
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
// src/plugins/builtin/gatekeeper.plugin.ts
|
|
2712
|
+
import { spawn as spawn4 } from "child_process";
|
|
2004
2713
|
import { statSync as statSync2 } from "fs";
|
|
2005
|
-
import
|
|
2714
|
+
import path13 from "path";
|
|
2006
2715
|
var HOOKS_OFF = ["-c", "core.hooksPath=/dev/null"];
|
|
2007
2716
|
var PATHSPEC_MAGIC = /[:*?[]/;
|
|
2008
2717
|
var SECRET_BASENAMES = [".env", ".env.local", "id_rsa"];
|
|
2009
2718
|
function is_secret_path(file_path) {
|
|
2010
|
-
const base =
|
|
2719
|
+
const base = path13.basename(file_path);
|
|
2011
2720
|
if (SECRET_BASENAMES.includes(base) === true) {
|
|
2012
2721
|
return true;
|
|
2013
2722
|
}
|
|
@@ -2034,7 +2743,7 @@ function matches_git_denylist(command) {
|
|
|
2034
2743
|
}
|
|
2035
2744
|
function run_git(args, work_dir, signal) {
|
|
2036
2745
|
return new Promise((resolve) => {
|
|
2037
|
-
const child =
|
|
2746
|
+
const child = spawn4("git", args, { cwd: work_dir, env: process.env });
|
|
2038
2747
|
let settled = false;
|
|
2039
2748
|
let out = "";
|
|
2040
2749
|
const on_abort = () => {
|
|
@@ -2085,7 +2794,7 @@ function invalid_commit_path(raw, work_dir) {
|
|
|
2085
2794
|
return `invalid_path: ${raw}`;
|
|
2086
2795
|
}
|
|
2087
2796
|
const resolved = resolve_safe_path(work_dir, raw);
|
|
2088
|
-
if (resolved ===
|
|
2797
|
+
if (resolved === path13.resolve(work_dir)) {
|
|
2089
2798
|
return `invalid_path: ${raw} resolves to work_dir`;
|
|
2090
2799
|
}
|
|
2091
2800
|
if (is_secret_path(raw) === true) {
|
|
@@ -3387,7 +4096,7 @@ function to_provider_error(error, fallback_name) {
|
|
|
3387
4096
|
|
|
3388
4097
|
// src/session/store.ts
|
|
3389
4098
|
import { appendFile, mkdir as mkdir2, readFile as readFile4 } from "fs/promises";
|
|
3390
|
-
import
|
|
4099
|
+
import path14 from "path";
|
|
3391
4100
|
var counter_state = { value: 0 };
|
|
3392
4101
|
function slugify_label(label) {
|
|
3393
4102
|
const slug = label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
@@ -3398,7 +4107,7 @@ async function open_session(dir, label) {
|
|
|
3398
4107
|
counter_state.value += 1;
|
|
3399
4108
|
const label_part = label === void 0 ? "" : slugify_label(label);
|
|
3400
4109
|
const id = `${Date.now().toString(36)}-${counter_state.value}${label_part}`;
|
|
3401
|
-
const file_path =
|
|
4110
|
+
const file_path = path14.join(dir, `${id}.jsonl`);
|
|
3402
4111
|
return {
|
|
3403
4112
|
id,
|
|
3404
4113
|
path: file_path,
|
|
@@ -3640,6 +4349,17 @@ function collect_usage(total) {
|
|
|
3640
4349
|
function append_meta(handle, meta) {
|
|
3641
4350
|
return handle.append({ ts: (/* @__PURE__ */ new Date()).toISOString(), kind: "meta", meta });
|
|
3642
4351
|
}
|
|
4352
|
+
function append_run_end(handle, stopped_reason, usage_total) {
|
|
4353
|
+
return append_meta(handle, {
|
|
4354
|
+
event: "run_end",
|
|
4355
|
+
stopped_reason,
|
|
4356
|
+
usage: {
|
|
4357
|
+
prompt_tokens: usage_total.prompt_tokens,
|
|
4358
|
+
completion_tokens: usage_total.completion_tokens,
|
|
4359
|
+
total_tokens: usage_total.total_tokens
|
|
4360
|
+
}
|
|
4361
|
+
});
|
|
4362
|
+
}
|
|
3643
4363
|
function register_plugin_tools(registry, plugins) {
|
|
3644
4364
|
for (const loaded of plugins) {
|
|
3645
4365
|
for (const tool of loaded.plugin.tools ?? []) {
|
|
@@ -3668,8 +4388,11 @@ var Agent = class {
|
|
|
3668
4388
|
registry;
|
|
3669
4389
|
executor;
|
|
3670
4390
|
hook_runner;
|
|
3671
|
-
|
|
4391
|
+
mcp_runtime;
|
|
4392
|
+
mcp_attached = false;
|
|
4393
|
+
constructor(config, plugins = [], runtime) {
|
|
3672
4394
|
this.config = config;
|
|
4395
|
+
this.mcp_runtime = runtime?.mcp;
|
|
3673
4396
|
this.events = new AgentEmitter();
|
|
3674
4397
|
this.router = new ProviderRouter(config.providers);
|
|
3675
4398
|
const base_registry = new ToolRegistry();
|
|
@@ -3693,6 +4416,7 @@ var Agent = class {
|
|
|
3693
4416
|
}
|
|
3694
4417
|
}
|
|
3695
4418
|
async run(options) {
|
|
4419
|
+
await this.attach_mcp_once();
|
|
3696
4420
|
const usage_total = { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 };
|
|
3697
4421
|
const stop_collecting = this.events.on(collect_usage(usage_total));
|
|
3698
4422
|
await this.call_plugin_run_start(options.input);
|
|
@@ -3716,10 +4440,18 @@ var Agent = class {
|
|
|
3716
4440
|
await this.call_plugin_run_end(outcome);
|
|
3717
4441
|
}
|
|
3718
4442
|
}
|
|
3719
|
-
const session_path = await this.persist_session(outcome, options);
|
|
4443
|
+
const session_path = await this.persist_session(outcome, options, usage_total);
|
|
3720
4444
|
const full_messages = [...options.history ?? [], ...outcome.messages];
|
|
3721
4445
|
return { outcome, messages: full_messages, usage_total, session_path };
|
|
3722
4446
|
}
|
|
4447
|
+
/** tools/list once, before the model sees definitions. Empty allowlists never connect. */
|
|
4448
|
+
async attach_mcp_once() {
|
|
4449
|
+
if (this.mcp_attached === true) {
|
|
4450
|
+
return;
|
|
4451
|
+
}
|
|
4452
|
+
this.mcp_attached = true;
|
|
4453
|
+
await attach_enabled_mcp_tools(this.registry, this.config, this.mcp_runtime);
|
|
4454
|
+
}
|
|
3723
4455
|
/** Per-run deps: the built-once ToolContext threads through every tool execution. */
|
|
3724
4456
|
loop_deps(tool_context) {
|
|
3725
4457
|
return {
|
|
@@ -3750,7 +4482,7 @@ var Agent = class {
|
|
|
3750
4482
|
);
|
|
3751
4483
|
}
|
|
3752
4484
|
/** Best-effort JSONL transcript: never fails the run, returns undefined path on error. */
|
|
3753
|
-
async persist_session(outcome, options) {
|
|
4485
|
+
async persist_session(outcome, options, usage_total) {
|
|
3754
4486
|
try {
|
|
3755
4487
|
const handle = await open_session(this.config.session_dir, options.label);
|
|
3756
4488
|
await append_meta(handle, { event: "run_start", input_chars: options.input.length, history_size: outcome.messages.length });
|
|
@@ -3760,6 +4492,7 @@ var Agent = class {
|
|
|
3760
4492
|
if (outcome.stopped_reason === "budget") {
|
|
3761
4493
|
await append_meta(handle, { event: "budget_exhausted" });
|
|
3762
4494
|
}
|
|
4495
|
+
await append_run_end(handle, outcome.stopped_reason, usage_total);
|
|
3763
4496
|
return handle.path;
|
|
3764
4497
|
} catch (error) {
|
|
3765
4498
|
logger.warn("session persistence failed; continuing without transcript", error);
|
|
@@ -3788,6 +4521,8 @@ export {
|
|
|
3788
4521
|
truncate_text,
|
|
3789
4522
|
logger,
|
|
3790
4523
|
register_builtin_tools,
|
|
4524
|
+
catalog_by_name,
|
|
4525
|
+
refuse_mcp_entry,
|
|
3791
4526
|
ToolRegistry,
|
|
3792
4527
|
ToolExecutor,
|
|
3793
4528
|
HookedToolRunner,
|
|
@@ -3806,4 +4541,4 @@ export {
|
|
|
3806
4541
|
create_agent_with_plugins,
|
|
3807
4542
|
run_agent
|
|
3808
4543
|
};
|
|
3809
|
-
//# sourceMappingURL=chunk-
|
|
4544
|
+
//# sourceMappingURL=chunk-QVJCIZIF.js.map
|