spacecode 2.10.0 → 2.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/repl.mjs +251 -63
- package/package.json +1 -1
package/dist/repl.mjs
CHANGED
|
@@ -6019,9 +6019,9 @@ async function sha256(content) {
|
|
|
6019
6019
|
const hashArray = new Uint8Array(hashBuffer);
|
|
6020
6020
|
return Array.from(hashArray).map((b2) => b2.toString(16).padStart(2, "0")).join("");
|
|
6021
6021
|
}
|
|
6022
|
-
async function computeLockfileHash(
|
|
6022
|
+
async function computeLockfileHash(readFile11) {
|
|
6023
6023
|
for (const name of LOCKFILES) {
|
|
6024
|
-
const content = await
|
|
6024
|
+
const content = await readFile11(name);
|
|
6025
6025
|
if (content) {
|
|
6026
6026
|
const hash = await sha256(content);
|
|
6027
6027
|
return hash.slice(0, 12);
|
|
@@ -7274,6 +7274,70 @@ var init_source_changes = __esm({
|
|
|
7274
7274
|
}
|
|
7275
7275
|
});
|
|
7276
7276
|
|
|
7277
|
+
// ../agent/src/import/mcp-tool-cache.ts
|
|
7278
|
+
function serverCacheKey(def, hash) {
|
|
7279
|
+
return hash(JSON.stringify([def.name, def.command, def.args, [...def.envNames].sort()]));
|
|
7280
|
+
}
|
|
7281
|
+
function cleanTool(raw) {
|
|
7282
|
+
if (!isPlainObject(raw) || typeof raw.name !== "string" || raw.name === "" || typeof raw.description !== "string" || !isPlainObject(raw.inputSchema)) return null;
|
|
7283
|
+
return { name: raw.name, description: raw.description.slice(0, MAX_DESCRIPTION), inputSchema: raw.inputSchema };
|
|
7284
|
+
}
|
|
7285
|
+
function parseToolCache(text) {
|
|
7286
|
+
if (text === null || text === "") return { cache: {} };
|
|
7287
|
+
let raw;
|
|
7288
|
+
try {
|
|
7289
|
+
raw = JSON.parse(text);
|
|
7290
|
+
} catch (err) {
|
|
7291
|
+
if (!(err instanceof SyntaxError)) throw err;
|
|
7292
|
+
return { cache: {}, error: "tool cache ignored: not valid JSON" };
|
|
7293
|
+
}
|
|
7294
|
+
if (!isPlainObject(raw) || raw.version !== 1 || !isPlainObject(raw.servers)) return { cache: {}, error: "tool cache ignored: unsupported format" };
|
|
7295
|
+
const cache4 = {};
|
|
7296
|
+
for (const key of Object.keys(raw.servers)) {
|
|
7297
|
+
if (Object.keys(cache4).length >= MAX_SERVERS) break;
|
|
7298
|
+
const entry = raw.servers[key];
|
|
7299
|
+
if (!isPlainObject(entry) || !Array.isArray(entry.tools)) continue;
|
|
7300
|
+
const tools = [];
|
|
7301
|
+
for (const t of entry.tools) {
|
|
7302
|
+
const clean = cleanTool(t);
|
|
7303
|
+
if (clean) tools.push(clean);
|
|
7304
|
+
if (tools.length >= MAX_TOOLS_PER_SERVER) break;
|
|
7305
|
+
}
|
|
7306
|
+
Object.defineProperty(cache4, key, { value: { tools }, enumerable: true, writable: true, configurable: true });
|
|
7307
|
+
}
|
|
7308
|
+
return { cache: cache4 };
|
|
7309
|
+
}
|
|
7310
|
+
function serializeToolCache(cache4) {
|
|
7311
|
+
return JSON.stringify({ version: 1, servers: cache4 });
|
|
7312
|
+
}
|
|
7313
|
+
function getCachedTools(cache4, key) {
|
|
7314
|
+
return own(cache4, key) ? cache4[key].tools : void 0;
|
|
7315
|
+
}
|
|
7316
|
+
function sameTools(a, b2) {
|
|
7317
|
+
return JSON.stringify(a) === JSON.stringify(b2);
|
|
7318
|
+
}
|
|
7319
|
+
function putCachedTools(cache4, key, tools) {
|
|
7320
|
+
const existing = getCachedTools(cache4, key);
|
|
7321
|
+
if (existing && sameTools(existing, tools)) return cache4;
|
|
7322
|
+
const next = {};
|
|
7323
|
+
for (const k2 of Object.keys(cache4)) if (k2 !== key) Object.defineProperty(next, k2, { value: cache4[k2], enumerable: true, writable: true, configurable: true });
|
|
7324
|
+
Object.defineProperty(next, key, { value: { tools }, enumerable: true, writable: true, configurable: true });
|
|
7325
|
+
const keys = Object.keys(next);
|
|
7326
|
+
for (const k2 of keys.slice(0, Math.max(0, keys.length - MAX_SERVERS))) delete next[k2];
|
|
7327
|
+
return next;
|
|
7328
|
+
}
|
|
7329
|
+
var MAX_TOOLS_PER_SERVER, MAX_SERVERS, MAX_DESCRIPTION, own, isPlainObject;
|
|
7330
|
+
var init_mcp_tool_cache = __esm({
|
|
7331
|
+
"../agent/src/import/mcp-tool-cache.ts"() {
|
|
7332
|
+
"use strict";
|
|
7333
|
+
MAX_TOOLS_PER_SERVER = 200;
|
|
7334
|
+
MAX_SERVERS = 500;
|
|
7335
|
+
MAX_DESCRIPTION = 1024;
|
|
7336
|
+
own = (o, k2) => Object.hasOwn(o, k2);
|
|
7337
|
+
isPlainObject = (v2) => typeof v2 === "object" && v2 !== null && !Array.isArray(v2);
|
|
7338
|
+
}
|
|
7339
|
+
});
|
|
7340
|
+
|
|
7277
7341
|
// ../agent/src/import/imports-config.ts
|
|
7278
7342
|
function allOffConfig() {
|
|
7279
7343
|
return {
|
|
@@ -7289,7 +7353,7 @@ function allOffConfig() {
|
|
|
7289
7353
|
models: {}
|
|
7290
7354
|
};
|
|
7291
7355
|
}
|
|
7292
|
-
function
|
|
7356
|
+
function isPlainObject2(value) {
|
|
7293
7357
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7294
7358
|
}
|
|
7295
7359
|
function parseImportsConfig(text, path12) {
|
|
@@ -7301,11 +7365,11 @@ function parseImportsConfig(text, path12) {
|
|
|
7301
7365
|
} catch {
|
|
7302
7366
|
return fail("invalid JSON");
|
|
7303
7367
|
}
|
|
7304
|
-
if (!
|
|
7368
|
+
if (!isPlainObject2(parsed)) return fail("top-level JSON must be an object");
|
|
7305
7369
|
if (parsed.version !== 1) return fail(`unsupported version ${String(parsed.version)}`);
|
|
7306
7370
|
const config = allOffConfig();
|
|
7307
7371
|
if (parsed.categories !== void 0) {
|
|
7308
|
-
if (!
|
|
7372
|
+
if (!isPlainObject2(parsed.categories)) return fail("categories must be an object");
|
|
7309
7373
|
for (const [key, value] of Object.entries(parsed.categories)) {
|
|
7310
7374
|
if (!IMPORT_CATEGORIES.includes(key)) return fail(`unknown category key "${key}"`);
|
|
7311
7375
|
if (typeof value !== "boolean") return fail(`category "${key}" must be a boolean`);
|
|
@@ -7313,7 +7377,7 @@ function parseImportsConfig(text, path12) {
|
|
|
7313
7377
|
}
|
|
7314
7378
|
}
|
|
7315
7379
|
if (parsed.models !== void 0) {
|
|
7316
|
-
if (!
|
|
7380
|
+
if (!isPlainObject2(parsed.models)) return fail("models must be an object");
|
|
7317
7381
|
for (const key of MODEL_KEYS) {
|
|
7318
7382
|
const value = parsed.models[key];
|
|
7319
7383
|
if (value === void 0) continue;
|
|
@@ -8021,6 +8085,7 @@ __export(src_exports, {
|
|
|
8021
8085
|
formatSkillBody: () => formatSkillBody,
|
|
8022
8086
|
formatSourceChanges: () => formatSourceChanges,
|
|
8023
8087
|
formatToolResult: () => formatToolResult,
|
|
8088
|
+
getCachedTools: () => getCachedTools,
|
|
8024
8089
|
guardHost: () => guardHost,
|
|
8025
8090
|
isReadOnlyCommand: () => isReadOnlyCommand,
|
|
8026
8091
|
joinSections: () => joinSections,
|
|
@@ -8032,13 +8097,18 @@ __export(src_exports, {
|
|
|
8032
8097
|
parseImportsConfig: () => parseImportsConfig,
|
|
8033
8098
|
parseSkillFile: () => parseSkillFile,
|
|
8034
8099
|
parseTodos: () => parseTodos,
|
|
8100
|
+
parseToolCache: () => parseToolCache,
|
|
8035
8101
|
prepareAdaptation: () => prepareAdaptation,
|
|
8036
8102
|
projectKey: () => projectKey,
|
|
8103
|
+
putCachedTools: () => putCachedTools,
|
|
8037
8104
|
readClaudeAssets: () => readClaudeAssets,
|
|
8038
8105
|
renderProjectCard: () => renderProjectCard,
|
|
8039
8106
|
runAgentLoop: () => runAgentLoop,
|
|
8040
8107
|
safeSkillPath: () => safeSkillPath,
|
|
8108
|
+
sameTools: () => sameTools,
|
|
8041
8109
|
serializeImportsConfig: () => serializeImportsConfig,
|
|
8110
|
+
serializeToolCache: () => serializeToolCache,
|
|
8111
|
+
serverCacheKey: () => serverCacheKey,
|
|
8042
8112
|
siblingAssetName: () => siblingAssetName,
|
|
8043
8113
|
splitCommand: () => splitCommand,
|
|
8044
8114
|
splitSections: () => splitSections,
|
|
@@ -8086,6 +8156,7 @@ var init_src = __esm({
|
|
|
8086
8156
|
init_instructions_tool();
|
|
8087
8157
|
init_trust();
|
|
8088
8158
|
init_source_changes();
|
|
8159
|
+
init_mcp_tool_cache();
|
|
8089
8160
|
init_imports_config();
|
|
8090
8161
|
init_permission_rules();
|
|
8091
8162
|
init_command_args();
|
|
@@ -8115,7 +8186,7 @@ __export(api_key_exports, {
|
|
|
8115
8186
|
});
|
|
8116
8187
|
import { join } from "node:path";
|
|
8117
8188
|
async function resolveApiKey(deps) {
|
|
8118
|
-
const { env: env3, readFile:
|
|
8189
|
+
const { env: env3, readFile: readFile11, homedir: homedir3 } = deps;
|
|
8119
8190
|
for (const name of ["SPACECODE_API_KEY", "ANTHROPIC_API_KEY"]) {
|
|
8120
8191
|
const value = env3[name]?.trim();
|
|
8121
8192
|
if (value) return value;
|
|
@@ -8124,7 +8195,7 @@ async function resolveApiKey(deps) {
|
|
|
8124
8195
|
const file = join(dir, "auth.json");
|
|
8125
8196
|
let raw;
|
|
8126
8197
|
try {
|
|
8127
|
-
raw = await
|
|
8198
|
+
raw = await readFile11(file);
|
|
8128
8199
|
} catch (err) {
|
|
8129
8200
|
if (err.code === "ENOENT") return null;
|
|
8130
8201
|
throw new Error(`Cannot read ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -8904,6 +8975,14 @@ var init_mcp_manager = __esm({
|
|
|
8904
8975
|
servers = /* @__PURE__ */ new Map();
|
|
8905
8976
|
turnSignal;
|
|
8906
8977
|
startPromise = null;
|
|
8978
|
+
primePromise = null;
|
|
8979
|
+
cache = {};
|
|
8980
|
+
/** Cached tools of approved, startable servers, usable until the live list replaces them. */
|
|
8981
|
+
cachedTools = /* @__PURE__ */ new Map();
|
|
8982
|
+
/** Settles when a server has connected or failed, so a call on a cached tool can wait for it. */
|
|
8983
|
+
connected = /* @__PURE__ */ new Map();
|
|
8984
|
+
cacheNotes = [];
|
|
8985
|
+
cacheWrite = Promise.resolve();
|
|
8907
8986
|
/** Server states in definition order, so output does not depend on which server connected first. */
|
|
8908
8987
|
ordered() {
|
|
8909
8988
|
return this.deps.defs.map((d) => this.servers.get(d.name)).filter((s) => s !== void 0);
|
|
@@ -8912,11 +8991,86 @@ var init_mcp_manager = __esm({
|
|
|
8912
8991
|
beginTurn(signal) {
|
|
8913
8992
|
this.turnSignal = signal;
|
|
8914
8993
|
}
|
|
8994
|
+
/**
|
|
8995
|
+
* Loads cached tool lists for servers that could start right now (stdio, approved on this machine, environment set),
|
|
8996
|
+
* without spawning anything. Await it before building the first toolset. Never throws; a bad cache is noted in /status.
|
|
8997
|
+
*/
|
|
8998
|
+
prime() {
|
|
8999
|
+
this.primePromise ??= this.primeOnce();
|
|
9000
|
+
return this.primePromise;
|
|
9001
|
+
}
|
|
9002
|
+
async primeOnce() {
|
|
9003
|
+
const tc = this.deps.toolCache;
|
|
9004
|
+
if (!tc) return;
|
|
9005
|
+
try {
|
|
9006
|
+
const parsed = parseToolCache(await tc.read());
|
|
9007
|
+
this.cache = parsed.cache;
|
|
9008
|
+
if (parsed.error) this.cacheNotes.push(parsed.error);
|
|
9009
|
+
} catch (err) {
|
|
9010
|
+
this.cacheNotes.push(`tool cache not read (${err instanceof Error ? err.message : String(err)})`);
|
|
9011
|
+
return;
|
|
9012
|
+
}
|
|
9013
|
+
for (const def of this.deps.defs) {
|
|
9014
|
+
try {
|
|
9015
|
+
if (def.transport !== "stdio") continue;
|
|
9016
|
+
const envNames = approvalEnv(def.env);
|
|
9017
|
+
if (!await this.deps.isStdioApproved({ command: def.command, args: def.args, envNames })) continue;
|
|
9018
|
+
if (!expandServerEnv({ command: def.command, args: def.args, env: def.env }, this.deps.env).ok) continue;
|
|
9019
|
+
const hit = getCachedTools(this.cache, serverCacheKey({ name: def.name, command: def.command, args: def.args, envNames }, tc.hash));
|
|
9020
|
+
if (hit) this.cachedTools.set(def.name, this.usable(def.name, hit).tools);
|
|
9021
|
+
} catch (err) {
|
|
9022
|
+
this.cacheNotes.push(`${def.name}: cached tools skipped (${err instanceof Error ? err.message : String(err)})`);
|
|
9023
|
+
}
|
|
9024
|
+
}
|
|
9025
|
+
}
|
|
8915
9026
|
/** Starts every eligible server in the background. Resolves when all have connected or failed; callers need not await it. */
|
|
8916
9027
|
start() {
|
|
8917
|
-
this.startPromise ??= Promise.all(this.deps.defs.map((def) =>
|
|
9028
|
+
this.startPromise ??= Promise.all(this.deps.defs.map((def) => {
|
|
9029
|
+
const p = this.startOne(def);
|
|
9030
|
+
this.connected.set(def.name, p);
|
|
9031
|
+
return p;
|
|
9032
|
+
})).then(() => void 0);
|
|
8918
9033
|
return this.startPromise;
|
|
8919
9034
|
}
|
|
9035
|
+
/** Tools whose full names are valid and unique, plus the ones that are not. */
|
|
9036
|
+
usable(server, listed) {
|
|
9037
|
+
const tools = [];
|
|
9038
|
+
const skipped = [];
|
|
9039
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9040
|
+
for (const tool of listed) {
|
|
9041
|
+
const full = mcpToolName(server, tool.name);
|
|
9042
|
+
if (!TOOL_NAME_RE.test(full) || seen.has(full)) skipped.push(tool.name);
|
|
9043
|
+
else {
|
|
9044
|
+
seen.add(full);
|
|
9045
|
+
tools.push(tool);
|
|
9046
|
+
}
|
|
9047
|
+
}
|
|
9048
|
+
return { tools, skipped };
|
|
9049
|
+
}
|
|
9050
|
+
/** Live tools once connected; cached ones while a server is still connecting (or not started yet). */
|
|
9051
|
+
toolsOf(def) {
|
|
9052
|
+
const s = this.servers.get(def.name);
|
|
9053
|
+
if (s?.status === "ready") return s.tools;
|
|
9054
|
+
if (s === void 0 || s.status === "connecting") return this.cachedTools.get(def.name) ?? [];
|
|
9055
|
+
return [];
|
|
9056
|
+
}
|
|
9057
|
+
/** Stores the live list for the next start. Writes are serialized; a failure becomes a /status line. */
|
|
9058
|
+
saveToolCache(def, tools) {
|
|
9059
|
+
const tc = this.deps.toolCache;
|
|
9060
|
+
if (!tc) return Promise.resolve();
|
|
9061
|
+
this.cacheWrite = this.cacheWrite.then(async () => {
|
|
9062
|
+
try {
|
|
9063
|
+
const key = serverCacheKey({ name: def.name, command: def.command, args: def.args, envNames: approvalEnv(def.env) }, tc.hash);
|
|
9064
|
+
const next = putCachedTools(this.cache, key, tools.map((t) => ({ name: t.name, description: t.description, inputSchema: t.inputSchema })));
|
|
9065
|
+
if (next === this.cache) return;
|
|
9066
|
+
await tc.write(serializeToolCache(next));
|
|
9067
|
+
this.cache = next;
|
|
9068
|
+
} catch (err) {
|
|
9069
|
+
this.cacheNotes.push(`${def.name}: tool list not cached (${err instanceof Error ? err.message : String(err)})`);
|
|
9070
|
+
}
|
|
9071
|
+
});
|
|
9072
|
+
return this.cacheWrite;
|
|
9073
|
+
}
|
|
8920
9074
|
async startOne(def) {
|
|
8921
9075
|
const name = def.name;
|
|
8922
9076
|
const skip = (reason) => void this.servers.set(name, { name, def, status: "skipped", reason });
|
|
@@ -8967,19 +9121,11 @@ var init_mcp_manager = __esm({
|
|
|
8967
9121
|
});
|
|
8968
9122
|
await client.initialize();
|
|
8969
9123
|
const listed = await client.listTools();
|
|
8970
|
-
const tools =
|
|
8971
|
-
const skippedTools = [];
|
|
8972
|
-
const seen = /* @__PURE__ */ new Set();
|
|
8973
|
-
for (const tool of listed.tools) {
|
|
8974
|
-
const full = mcpToolName(name, tool.name);
|
|
8975
|
-
if (!TOOL_NAME_RE.test(full) || seen.has(full)) skippedTools.push(tool.name);
|
|
8976
|
-
else {
|
|
8977
|
-
seen.add(full);
|
|
8978
|
-
tools.push(tool);
|
|
8979
|
-
}
|
|
8980
|
-
}
|
|
9124
|
+
const { tools, skipped: skippedTools } = this.usable(name, listed.tools);
|
|
8981
9125
|
if (listed.skipped > 0) skippedTools.push(`${listed.skipped} unnamed`);
|
|
8982
9126
|
this.servers.set(name, { name, def, status: "ready", client, transport, session: session2, tools, skippedTools });
|
|
9127
|
+
this.cachedTools.delete(name);
|
|
9128
|
+
await this.saveToolCache(def, tools);
|
|
8983
9129
|
} catch (err) {
|
|
8984
9130
|
await transport?.close().catch(() => void 0);
|
|
8985
9131
|
fail(err instanceof Error ? err.message : String(err));
|
|
@@ -8988,22 +9134,29 @@ var init_mcp_manager = __esm({
|
|
|
8988
9134
|
/** Tools of every connected server, as the model sees them. */
|
|
8989
9135
|
tools() {
|
|
8990
9136
|
const out = [];
|
|
8991
|
-
for (const
|
|
8992
|
-
|
|
8993
|
-
|
|
8994
|
-
out.push({ name: mcpToolName(s.name, t.name), description: `[MCP server ${s.name}] ${t.description}`.slice(0, 1024), input_schema: t.inputSchema });
|
|
9137
|
+
for (const def of this.deps.defs) {
|
|
9138
|
+
for (const t of this.toolsOf(def)) {
|
|
9139
|
+
out.push({ name: mcpToolName(def.name, t.name), description: `[MCP server ${def.name}] ${t.description}`.slice(0, 1024), input_schema: t.inputSchema });
|
|
8995
9140
|
}
|
|
8996
9141
|
}
|
|
8997
9142
|
return out;
|
|
8998
9143
|
}
|
|
8999
9144
|
handlers() {
|
|
9000
9145
|
const out = {};
|
|
9001
|
-
for (const
|
|
9002
|
-
|
|
9003
|
-
for (const t of s.tools) out[mcpToolName(s.name, t.name)] = (input) => this.call(s.name, t.name, input);
|
|
9146
|
+
for (const def of this.deps.defs) {
|
|
9147
|
+
for (const t of this.toolsOf(def)) out[mcpToolName(def.name, t.name)] = (input) => this.callWhenReady(def.name, t.name, input);
|
|
9004
9148
|
}
|
|
9005
9149
|
return out;
|
|
9006
9150
|
}
|
|
9151
|
+
/** A call on a cached tool waits for its server to connect (bounded by the connect timeout) and then goes through the normal checks. */
|
|
9152
|
+
async callWhenReady(server, tool, input) {
|
|
9153
|
+
await this.connected.get(server);
|
|
9154
|
+
const state = this.servers.get(server);
|
|
9155
|
+
if (state?.status === "ready" && !state.tools.some((t) => t.name === tool)) {
|
|
9156
|
+
return { output: `MCP server "${server}" no longer offers ${tool}; its tool list changed since it was cached.`, is_error: true };
|
|
9157
|
+
}
|
|
9158
|
+
return this.call(server, tool, input);
|
|
9159
|
+
}
|
|
9007
9160
|
async call(server, tool, input) {
|
|
9008
9161
|
const full = mcpToolName(server, tool);
|
|
9009
9162
|
const state = this.servers.get(server);
|
|
@@ -9045,11 +9198,18 @@ var init_mcp_manager = __esm({
|
|
|
9045
9198
|
/** One line per server for /status; reasons never include env values or stderr. */
|
|
9046
9199
|
statusLines() {
|
|
9047
9200
|
const lines2 = [];
|
|
9048
|
-
for (const
|
|
9201
|
+
for (const def of this.deps.defs) {
|
|
9202
|
+
const s = this.servers.get(def.name);
|
|
9203
|
+
if (s === void 0) {
|
|
9204
|
+
const cached3 = this.cachedTools.get(def.name);
|
|
9205
|
+
if (cached3) lines2.push(`mcp: ${def.name} connecting... (${cached3.length} tools from cache)`);
|
|
9206
|
+
continue;
|
|
9207
|
+
}
|
|
9049
9208
|
if (s.status === "ready") lines2.push(`mcp: ${s.name} ready, ${s.tools.length} tools${s.skippedTools.length ? ` (not usable: ${s.skippedTools.join(", ")})` : ""}`);
|
|
9050
|
-
else if (s.status === "connecting") lines2.push(`mcp: ${s.name} connecting
|
|
9209
|
+
else if (s.status === "connecting") lines2.push(`mcp: ${s.name} connecting...${this.cachedTools.has(s.name) ? ` (${this.cachedTools.get(s.name).length} tools from cache)` : ""}`);
|
|
9051
9210
|
else lines2.push(`mcp: ${s.name} NOT available (${s.reason})`);
|
|
9052
9211
|
}
|
|
9212
|
+
for (const n of this.cacheNotes) lines2.push(`mcp: ${n}`);
|
|
9053
9213
|
return lines2;
|
|
9054
9214
|
}
|
|
9055
9215
|
/** For /tools: names grouped by server. */
|
|
@@ -44353,7 +44513,7 @@ var require_core = __commonJS({
|
|
|
44353
44513
|
return match && match.index === 0;
|
|
44354
44514
|
}
|
|
44355
44515
|
var BACKREF_RE = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;
|
|
44356
|
-
function
|
|
44516
|
+
function join15(regexps, separator = "|") {
|
|
44357
44517
|
let numCaptures = 0;
|
|
44358
44518
|
return regexps.map((regex2) => {
|
|
44359
44519
|
numCaptures += 1;
|
|
@@ -44657,7 +44817,7 @@ var require_core = __commonJS({
|
|
|
44657
44817
|
this.exec = () => null;
|
|
44658
44818
|
}
|
|
44659
44819
|
const terminators = this.regexes.map((el) => el[1]);
|
|
44660
|
-
this.matcherRe = langRe(
|
|
44820
|
+
this.matcherRe = langRe(join15(terminators), true);
|
|
44661
44821
|
this.lastIndex = 0;
|
|
44662
44822
|
}
|
|
44663
44823
|
/** @param {string} s */
|
|
@@ -105001,12 +105161,37 @@ var init_skills_command = __esm({
|
|
|
105001
105161
|
}
|
|
105002
105162
|
});
|
|
105003
105163
|
|
|
105004
|
-
// src/
|
|
105164
|
+
// src/mcp/tool-cache-file.ts
|
|
105005
105165
|
import { readFile as readFile8 } from "node:fs/promises";
|
|
105006
105166
|
import { join as join11 } from "node:path";
|
|
105167
|
+
function nodeToolCache(configDir) {
|
|
105168
|
+
const path12 = join11(configDir, "mcp-tools-cache.json");
|
|
105169
|
+
return {
|
|
105170
|
+
read: async () => {
|
|
105171
|
+
try {
|
|
105172
|
+
return await readFile8(path12, "utf8");
|
|
105173
|
+
} catch (err) {
|
|
105174
|
+
if (err.code === "ENOENT") return null;
|
|
105175
|
+
throw err;
|
|
105176
|
+
}
|
|
105177
|
+
},
|
|
105178
|
+
write: (text) => writeTextAtomic(path12, text),
|
|
105179
|
+
hash: sha2562
|
|
105180
|
+
};
|
|
105181
|
+
}
|
|
105182
|
+
var init_tool_cache_file = __esm({
|
|
105183
|
+
"src/mcp/tool-cache-file.ts"() {
|
|
105184
|
+
"use strict";
|
|
105185
|
+
init_import_gate();
|
|
105186
|
+
}
|
|
105187
|
+
});
|
|
105188
|
+
|
|
105189
|
+
// src/permission-rules-loader.ts
|
|
105190
|
+
import { readFile as readFile9 } from "node:fs/promises";
|
|
105191
|
+
import { join as join12 } from "node:path";
|
|
105007
105192
|
async function readOrNull2(path12) {
|
|
105008
105193
|
try {
|
|
105009
|
-
return await
|
|
105194
|
+
return await readFile9(path12, "utf8");
|
|
105010
105195
|
} catch (err) {
|
|
105011
105196
|
if (err.code === "ENOENT") return null;
|
|
105012
105197
|
throw err;
|
|
@@ -105014,7 +105199,7 @@ async function readOrNull2(path12) {
|
|
|
105014
105199
|
}
|
|
105015
105200
|
async function loadPermissionRules(deps) {
|
|
105016
105201
|
const none = (line) => ({ rules: void 0, defaultMode: void 0, statusLines: [line] });
|
|
105017
|
-
const configPath =
|
|
105202
|
+
const configPath = join12(deps.configDir, "imports.json");
|
|
105018
105203
|
try {
|
|
105019
105204
|
const loaded = parseImportsConfig(await readOrNull2(configPath), configPath);
|
|
105020
105205
|
if (loaded.error) return none("permission rules: off (imports.json unreadable)");
|
|
@@ -105023,13 +105208,13 @@ async function loadPermissionRules(deps) {
|
|
|
105023
105208
|
return none(`permission rules: off (imports.json not read: ${err instanceof Error ? err.message : String(err)})`);
|
|
105024
105209
|
}
|
|
105025
105210
|
const lines2 = [];
|
|
105026
|
-
const files = [{ scope: "user", path:
|
|
105211
|
+
const files = [{ scope: "user", path: join12(deps.homeDir, ".claude", "settings.json") }];
|
|
105027
105212
|
try {
|
|
105028
105213
|
const sources2 = await readRepoSources(deps.cwd);
|
|
105029
105214
|
if (sources2[".claude/settings.json"] !== void 0 || sources2[".claude/settings.local.json"] !== void 0) {
|
|
105030
105215
|
const status = await createNodeTrustStore(deps.configDir).repoStatus(deps.cwd, sources2);
|
|
105031
105216
|
if (status === "trusted" || status === "session") {
|
|
105032
|
-
files.push({ scope: "repo", path:
|
|
105217
|
+
files.push({ scope: "repo", path: join12(deps.cwd, ".claude", "settings.json") }, { scope: "local", path: join12(deps.cwd, ".claude", "settings.local.json") });
|
|
105033
105218
|
} else lines2.push(`permission rules: repo settings NOT loaded (${status === "never" ? "marked never trust" : "untrusted repo"})`);
|
|
105034
105219
|
}
|
|
105035
105220
|
} catch (err) {
|
|
@@ -105106,7 +105291,7 @@ var init_import_reload = __esm({
|
|
|
105106
105291
|
});
|
|
105107
105292
|
|
|
105108
105293
|
// src/command-loader.ts
|
|
105109
|
-
import { join as
|
|
105294
|
+
import { join as join13 } from "node:path";
|
|
105110
105295
|
async function userCommands(deps, lines2) {
|
|
105111
105296
|
try {
|
|
105112
105297
|
const apiKey = await resolveApiKey({ env: deps.env, readFile: deps.readFile, homedir: deps.homedir });
|
|
@@ -105161,7 +105346,7 @@ async function repoCommands(deps, lines2) {
|
|
|
105161
105346
|
}
|
|
105162
105347
|
}
|
|
105163
105348
|
async function loadCommands(deps) {
|
|
105164
|
-
const configPath =
|
|
105349
|
+
const configPath = join13(deps.configDir, "imports.json");
|
|
105165
105350
|
try {
|
|
105166
105351
|
const loaded = parseImportsConfig(await deps.readFile(configPath).catch((err) => {
|
|
105167
105352
|
if (err.code === "ENOENT") return null;
|
|
@@ -105194,9 +105379,9 @@ var init_command_loader = __esm({
|
|
|
105194
105379
|
});
|
|
105195
105380
|
|
|
105196
105381
|
// src/useReplController.ts
|
|
105197
|
-
import { readFile as
|
|
105382
|
+
import { readFile as readFile10, writeFile as writeFile6, mkdir as mkdir6 } from "node:fs/promises";
|
|
105198
105383
|
import { homedir as homedir2 } from "node:os";
|
|
105199
|
-
import { join as
|
|
105384
|
+
import { join as join14 } from "node:path";
|
|
105200
105385
|
import { execFile as execFile4 } from "node:child_process";
|
|
105201
105386
|
function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mentionsOpt, sessions, resumeLatest: resumeLatest2, onViewTurns, onViewResume, isEscOwned }) {
|
|
105202
105387
|
const { exit } = use_app_default();
|
|
@@ -105219,7 +105404,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
|
|
|
105219
105404
|
}, [turnStartedAt]);
|
|
105220
105405
|
const projectRoot = (0, import_react30.useMemo)(() => process.cwd(), []);
|
|
105221
105406
|
const promptTemplates = (0, import_react30.useCallback)(
|
|
105222
|
-
() => loadPromptTemplates({ env: process.env, readFile: (p) =>
|
|
105407
|
+
() => loadPromptTemplates({ env: process.env, readFile: (p) => readFile10(p, "utf-8"), writeFile: (p, d) => writeFile6(p, d, "utf-8"), mkdir: (p) => mkdir6(p, { recursive: true }).then(() => void 0), homedir: homedir2, fetch }),
|
|
105223
105408
|
[]
|
|
105224
105409
|
);
|
|
105225
105410
|
(0, import_react30.useEffect)(() => {
|
|
@@ -105235,11 +105420,11 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
|
|
|
105235
105420
|
const recordingRef = (0, import_react30.useRef)(null);
|
|
105236
105421
|
const recording = () => recordingRef.current ??= createRecordingRuntime({
|
|
105237
105422
|
env: process.env,
|
|
105238
|
-
configDir: process.env.SPACECODE_CONFIG_DIR?.trim() ||
|
|
105423
|
+
configDir: process.env.SPACECODE_CONFIG_DIR?.trim() || join14(homedir2(), ".spacecode"),
|
|
105239
105424
|
projectRoot,
|
|
105240
105425
|
fetch,
|
|
105241
105426
|
baseUrl: backendUrl(process.env),
|
|
105242
|
-
resolveApiKey: () => resolveApiKey({ env: process.env, readFile: (p) =>
|
|
105427
|
+
resolveApiKey: () => resolveApiKey({ env: process.env, readFile: (p) => readFile10(p, "utf-8"), homedir: homedir2 }).catch(() => null),
|
|
105243
105428
|
log: (m2) => console.error(`[agent-ui] ${m2}`)
|
|
105244
105429
|
});
|
|
105245
105430
|
const resumeListRef = (0, import_react30.useRef)([]);
|
|
@@ -105263,10 +105448,10 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
|
|
|
105263
105448
|
(0, import_react30.useEffect)(() => {
|
|
105264
105449
|
let live2 = true;
|
|
105265
105450
|
loadCommands({
|
|
105266
|
-
configDir: process.env.SPACECODE_CONFIG_DIR?.trim() ||
|
|
105451
|
+
configDir: process.env.SPACECODE_CONFIG_DIR?.trim() || join14(homedir2(), ".spacecode"),
|
|
105267
105452
|
cwd: process.cwd(),
|
|
105268
105453
|
env: process.env,
|
|
105269
|
-
readFile: (p) =>
|
|
105454
|
+
readFile: (p) => readFile10(p, "utf-8"),
|
|
105270
105455
|
homedir: homedir2,
|
|
105271
105456
|
fetch,
|
|
105272
105457
|
builtins: SLASH_COMMANDS.map((c) => c.name)
|
|
@@ -105484,7 +105669,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
|
|
|
105484
105669
|
notice(formatContextReport({ contextTokens: estimateTotalTokens("", history), maxContextTokens: DEFAULT_MAX_CONTEXT_TOKENS }));
|
|
105485
105670
|
return;
|
|
105486
105671
|
case "import": {
|
|
105487
|
-
const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() ||
|
|
105672
|
+
const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() || join14(homedir2(), ".spacecode");
|
|
105488
105673
|
const deps = { configDir, cwd: process.cwd() };
|
|
105489
105674
|
const [sub, ...rest] = slash.args.split(/\s+/).filter(Boolean);
|
|
105490
105675
|
if (sub !== "reload" || rest.length > 1 || rest[0] !== void 0 && rest[0] !== "trust") {
|
|
@@ -105497,7 +105682,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
|
|
|
105497
105682
|
loadedSourcesRef.current = await trustCurrentSources(deps);
|
|
105498
105683
|
const loaded = await loadPermissionRules({ configDir, homeDir: homedir2(), cwd: process.cwd() });
|
|
105499
105684
|
rulesRef.current = loaded.rules;
|
|
105500
|
-
const cmds = await loadCommands({ configDir, cwd: process.cwd(), env: process.env, readFile: (p) =>
|
|
105685
|
+
const cmds = await loadCommands({ configDir, cwd: process.cwd(), env: process.env, readFile: (p) => readFile10(p, "utf-8"), homedir: homedir2, fetch, builtins: SLASH_COMMANDS.map((c) => c.name) });
|
|
105501
105686
|
setCommandsLoaded(cmds);
|
|
105502
105687
|
notice(["Trusted the repo import sources as they are now.", ...loaded.statusLines, ...cmds.statusLines, "MCP servers and skills apply on the next start."].join("\n"));
|
|
105503
105688
|
} else notice((await describeReload(deps, baseline)).join("\n"));
|
|
@@ -105577,7 +105762,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
|
|
|
105577
105762
|
}
|
|
105578
105763
|
setIsRunning(true);
|
|
105579
105764
|
try {
|
|
105580
|
-
const apiKey2 = await resolveApiKey({ env: process.env, readFile: (p) =>
|
|
105765
|
+
const apiKey2 = await resolveApiKey({ env: process.env, readFile: (p) => readFile10(p, "utf-8"), homedir: homedir2 });
|
|
105581
105766
|
if (!apiKey2) {
|
|
105582
105767
|
notice(MISSING_KEY_MESSAGE);
|
|
105583
105768
|
return;
|
|
@@ -105599,7 +105784,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
|
|
|
105599
105784
|
}
|
|
105600
105785
|
case "model": {
|
|
105601
105786
|
try {
|
|
105602
|
-
const apiKey2 = await resolveApiKey({ env: process.env, readFile: (p) =>
|
|
105787
|
+
const apiKey2 = await resolveApiKey({ env: process.env, readFile: (p) => readFile10(p, "utf-8"), homedir: homedir2 });
|
|
105603
105788
|
if (!apiKey2) {
|
|
105604
105789
|
notice(MISSING_KEY_MESSAGE);
|
|
105605
105790
|
return;
|
|
@@ -105634,7 +105819,7 @@ ${list2.map((c) => ` /${c.name} (${c.scope})${c.description ? ` ${c.descripti
|
|
|
105634
105819
|
}
|
|
105635
105820
|
case "skills": {
|
|
105636
105821
|
const state = profileRef.current;
|
|
105637
|
-
const apiKey2 = await resolveApiKey({ env: process.env, readFile: (p) =>
|
|
105822
|
+
const apiKey2 = await resolveApiKey({ env: process.env, readFile: (p) => readFile10(p, "utf-8"), homedir: homedir2 }).catch(() => null);
|
|
105638
105823
|
const client = apiKey2 ? createProfileClient({ baseUrl: backendUrl(process.env), apiKey: apiKey2, fetch }) : null;
|
|
105639
105824
|
notice(await runSkillsCommand(slash.args, { source: state?.skills ?? null, client, statusLines: state?.statusLines.filter((l) => l.startsWith("skills")) ?? ["skills: not loaded yet (loads on your first message)"] }));
|
|
105640
105825
|
return;
|
|
@@ -105656,7 +105841,7 @@ ${list2.map((c) => ` /${c.name} (${c.scope})${c.description ? ` ${c.descripti
|
|
|
105656
105841
|
try {
|
|
105657
105842
|
apiKey = await resolveApiKey({
|
|
105658
105843
|
env: process.env,
|
|
105659
|
-
readFile: (p) =>
|
|
105844
|
+
readFile: (p) => readFile10(p, "utf-8"),
|
|
105660
105845
|
homedir: homedir2
|
|
105661
105846
|
});
|
|
105662
105847
|
} catch (err) {
|
|
@@ -105692,7 +105877,7 @@ ${list2.map((c) => ` /${c.name} (${c.scope})${c.description ? ` ${c.descripti
|
|
|
105692
105877
|
let keepForRetry = false;
|
|
105693
105878
|
try {
|
|
105694
105879
|
if (loadedSourcesRef.current) {
|
|
105695
|
-
const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() ||
|
|
105880
|
+
const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() || join14(homedir2(), ".spacecode");
|
|
105696
105881
|
try {
|
|
105697
105882
|
const check = await checkRepoSources({ configDir, cwd: process.cwd() }, loadedSourcesRef.current);
|
|
105698
105883
|
if (check.changes.changed) {
|
|
@@ -105709,7 +105894,7 @@ ${list2.map((c) => ` /${c.name} (${c.scope})${c.description ? ` ${c.descripti
|
|
|
105709
105894
|
}
|
|
105710
105895
|
profilePromiseRef.current ??= prepareProfile({
|
|
105711
105896
|
env: process.env,
|
|
105712
|
-
readFile: (p) =>
|
|
105897
|
+
readFile: (p) => readFile10(p, "utf-8"),
|
|
105713
105898
|
writeFile: (p, d) => writeFile6(p, d, { encoding: "utf-8", mode: 384 }),
|
|
105714
105899
|
mkdir: async (p) => {
|
|
105715
105900
|
await mkdir6(p, { recursive: true });
|
|
@@ -105719,7 +105904,7 @@ ${list2.map((c) => ` /${c.name} (${c.scope})${c.description ? ` ${c.descripti
|
|
|
105719
105904
|
cwd: process.cwd(),
|
|
105720
105905
|
gitRoot: gitTopLevel,
|
|
105721
105906
|
importGate: async () => {
|
|
105722
|
-
const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() ||
|
|
105907
|
+
const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() || join14(homedir2(), ".spacecode");
|
|
105723
105908
|
const fingerprint = fingerprintSources(await readRepoSources(process.cwd()), sha2562);
|
|
105724
105909
|
const gate = await importGateStatus({ configDir, cwd: process.cwd(), interactive: Boolean(process.stdin.isTTY) });
|
|
105725
105910
|
const loaded = await loadPermissionRules({ configDir, homeDir: homedir2(), cwd: process.cwd() });
|
|
@@ -105731,7 +105916,7 @@ ${list2.map((c) => ` /${c.name} (${c.scope})${c.description ? ` ${c.descripti
|
|
|
105731
105916
|
});
|
|
105732
105917
|
profileRef.current = await profilePromiseRef.current;
|
|
105733
105918
|
if (mcpRef.current === null && (profileRef.current?.mcpDefs?.length ?? 0) > 0) {
|
|
105734
|
-
const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() ||
|
|
105919
|
+
const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() || join14(homedir2(), ".spacecode");
|
|
105735
105920
|
const trust = createNodeTrustStore(configDir);
|
|
105736
105921
|
const manager = new McpManager({
|
|
105737
105922
|
defs: profileRef.current.mcpDefs,
|
|
@@ -105741,10 +105926,12 @@ ${list2.map((c) => ` /${c.name} (${c.scope})${c.description ? ` ${c.descripti
|
|
|
105741
105926
|
...rulesRef.current ? { rules: rulesRef.current } : {},
|
|
105742
105927
|
permissions,
|
|
105743
105928
|
confirm: askUser,
|
|
105744
|
-
audit: new FileAuditLog(
|
|
105745
|
-
clientVersion: process.env.SPACECODE_VERSION ?? "0"
|
|
105929
|
+
audit: new FileAuditLog(join14(homedir2(), ".spacecode", "audit.jsonl")),
|
|
105930
|
+
clientVersion: process.env.SPACECODE_VERSION ?? "0",
|
|
105931
|
+
toolCache: nodeToolCache(configDir)
|
|
105746
105932
|
});
|
|
105747
105933
|
mcpRef.current = manager;
|
|
105934
|
+
await manager.prime();
|
|
105748
105935
|
void manager.start();
|
|
105749
105936
|
}
|
|
105750
105937
|
const toolset = createToolset();
|
|
@@ -105882,6 +106069,7 @@ var init_useReplController = __esm({
|
|
|
105882
106069
|
init_skills_command();
|
|
105883
106070
|
init_src();
|
|
105884
106071
|
init_mcp_manager();
|
|
106072
|
+
init_tool_cache_file();
|
|
105885
106073
|
init_import_gate();
|
|
105886
106074
|
init_permission_rules_loader();
|
|
105887
106075
|
init_import_reload();
|
|
@@ -106471,7 +106659,7 @@ function clearOnResize(stdout) {
|
|
|
106471
106659
|
var import_jsx_runtime14 = __toESM(require_jsx_runtime(), 1);
|
|
106472
106660
|
if (process.argv[2] === "import-claude") {
|
|
106473
106661
|
const { runImportClaude: runImportClaude2 } = await Promise.resolve().then(() => (init_import_claude(), import_claude_exports));
|
|
106474
|
-
const { readFile:
|
|
106662
|
+
const { readFile: readFile11, readdir: readdir6 } = await import("node:fs/promises");
|
|
106475
106663
|
const { execFile: execFile5 } = await import("node:child_process");
|
|
106476
106664
|
const gitRoot = () => new Promise((resolve2) => {
|
|
106477
106665
|
execFile5("git", ["rev-parse", "--show-toplevel"], { cwd: process.cwd() }, (err, stdout) => resolve2(err ? null : stdout.trim() || null));
|
|
@@ -106479,8 +106667,8 @@ if (process.argv[2] === "import-claude") {
|
|
|
106479
106667
|
const code = await runImportClaude2({
|
|
106480
106668
|
argv: process.argv.slice(2),
|
|
106481
106669
|
env: process.env,
|
|
106482
|
-
fs: { readFile: (p) =>
|
|
106483
|
-
readFile: (p) =>
|
|
106670
|
+
fs: { readFile: (p) => readFile11(p, "utf-8"), readdir: (p) => readdir6(p) },
|
|
106671
|
+
readFile: (p) => readFile11(p, "utf-8"),
|
|
106484
106672
|
homedir: os5.homedir,
|
|
106485
106673
|
cwd: process.cwd(),
|
|
106486
106674
|
gitRoot,
|
|
@@ -106505,9 +106693,9 @@ if (process.argv[2] === "approve-mcp") {
|
|
|
106505
106693
|
const { resolveApiKey: resolveApiKey2 } = await Promise.resolve().then(() => (init_api_key(), api_key_exports));
|
|
106506
106694
|
const { backendUrl: backendUrl2 } = await Promise.resolve().then(() => (init_profile_runtime(), profile_runtime_exports));
|
|
106507
106695
|
const { createProfileClient: createProfileClient2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
106508
|
-
const { readFile:
|
|
106696
|
+
const { readFile: readFile11 } = await import("node:fs/promises");
|
|
106509
106697
|
const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() || path11.join(os5.homedir(), ".spacecode");
|
|
106510
|
-
const apiKey = await resolveApiKey2({ env: process.env, readFile: (p) =>
|
|
106698
|
+
const apiKey = await resolveApiKey2({ env: process.env, readFile: (p) => readFile11(p, "utf-8"), homedir: os5.homedir }).catch(() => null);
|
|
106511
106699
|
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
106512
106700
|
const { createInterface } = interactive ? await import("node:readline/promises") : { createInterface: void 0 };
|
|
106513
106701
|
const rl = createInterface ? createInterface({ input: process.stdin, output: process.stdout }) : void 0;
|
|
@@ -106515,7 +106703,7 @@ if (process.argv[2] === "approve-mcp") {
|
|
|
106515
106703
|
const code = await runApproveMcp2({
|
|
106516
106704
|
configDir,
|
|
106517
106705
|
cwd: process.cwd(),
|
|
106518
|
-
readFile: (p) =>
|
|
106706
|
+
readFile: (p) => readFile11(p, "utf-8"),
|
|
106519
106707
|
client: apiKey ? createProfileClient2({ baseUrl: backendUrl2(process.env), apiKey, fetch }) : null,
|
|
106520
106708
|
trust: createNodeTrustStore2(configDir),
|
|
106521
106709
|
...rl ? { ask: async (q2) => {
|