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/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(readFile10) {
6022
+ async function computeLockfileHash(readFile11) {
6023
6023
  for (const name of LOCKFILES) {
6024
- const content = await readFile10(name);
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 isPlainObject(value) {
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 (!isPlainObject(parsed)) return fail("top-level JSON must be an object");
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 (!isPlainObject(parsed.categories)) return fail("categories must be an object");
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 (!isPlainObject(parsed.models)) return fail("models must be an object");
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: readFile10, homedir: homedir3 } = deps;
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 readFile10(file);
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) => this.startOne(def))).then(() => void 0);
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 s of this.ordered()) {
8992
- if (s.status !== "ready") continue;
8993
- for (const t of s.tools) {
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 s of this.ordered()) {
9002
- if (s.status !== "ready") continue;
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 s of this.ordered()) {
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 join14(regexps, separator = "|") {
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(join14(terminators), true);
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/permission-rules-loader.ts
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 readFile8(path12, "utf8");
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 = join11(deps.configDir, "imports.json");
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: join11(deps.homeDir, ".claude", "settings.json") }];
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: join11(deps.cwd, ".claude", "settings.json") }, { scope: "local", path: join11(deps.cwd, ".claude", "settings.local.json") });
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 join12 } from "node:path";
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 = join12(deps.configDir, "imports.json");
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 readFile9, writeFile as writeFile6, mkdir as mkdir6 } from "node:fs/promises";
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 join13 } from "node:path";
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) => readFile9(p, "utf-8"), writeFile: (p, d) => writeFile6(p, d, "utf-8"), mkdir: (p) => mkdir6(p, { recursive: true }).then(() => void 0), homedir: homedir2, fetch }),
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() || join13(homedir2(), ".spacecode"),
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) => readFile9(p, "utf-8"), homedir: homedir2 }).catch(() => null),
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() || join13(homedir2(), ".spacecode"),
105451
+ configDir: process.env.SPACECODE_CONFIG_DIR?.trim() || join14(homedir2(), ".spacecode"),
105267
105452
  cwd: process.cwd(),
105268
105453
  env: process.env,
105269
- readFile: (p) => readFile9(p, "utf-8"),
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() || join13(homedir2(), ".spacecode");
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) => readFile9(p, "utf-8"), homedir: homedir2, fetch, builtins: SLASH_COMMANDS.map((c) => c.name) });
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) => readFile9(p, "utf-8"), homedir: homedir2 });
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) => readFile9(p, "utf-8"), homedir: homedir2 });
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) => readFile9(p, "utf-8"), homedir: homedir2 }).catch(() => null);
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) => readFile9(p, "utf-8"),
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() || join13(homedir2(), ".spacecode");
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) => readFile9(p, "utf-8"),
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() || join13(homedir2(), ".spacecode");
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() || join13(homedir2(), ".spacecode");
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(join13(homedir2(), ".spacecode", "audit.jsonl")),
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: readFile10, readdir: readdir6 } = await import("node:fs/promises");
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) => readFile10(p, "utf-8"), readdir: (p) => readdir6(p) },
106483
- readFile: (p) => readFile10(p, "utf-8"),
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: readFile10 } = await import("node:fs/promises");
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) => readFile10(p, "utf-8"), homedir: os5.homedir }).catch(() => null);
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) => readFile10(p, "utf-8"),
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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spacecode",
3
- "version": "2.10.0",
3
+ "version": "2.10.1",
4
4
  "description": "SpaceCode - launch cloud dev environments with AI coding agents",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",