spacecode 2.10.0 → 2.10.2

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
@@ -3490,12 +3490,6 @@ function truncateOutput(text, maxChars) {
3490
3490
 
3491
3491
  ` + text.slice(text.length - effectiveTail);
3492
3492
  }
3493
- function createSimpleRouter(opts) {
3494
- const threshold = opts.complexAfterTurn ?? 5;
3495
- return (turn, _lastToolCount) => {
3496
- return turn > threshold ? opts.complex : opts.default;
3497
- };
3498
- }
3499
3493
  async function runAgentLoop(prompt, config, executor, emit, signal, existingMessages) {
3500
3494
  const defaultModel = config.model ?? DEFAULT_MODEL;
3501
3495
  const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
@@ -4211,98 +4205,9 @@ ${s.lines.join("\n")}`)].join("\n");
4211
4205
  });
4212
4206
 
4213
4207
  // ../agent/src/storage/file-store.ts
4214
- var FileStore;
4215
4208
  var init_file_store = __esm({
4216
4209
  "../agent/src/storage/file-store.ts"() {
4217
4210
  "use strict";
4218
- FileStore = class {
4219
- constructor(db, sessionId) {
4220
- this.db = db;
4221
- this.sessionId = sessionId;
4222
- }
4223
- async readFile(path12) {
4224
- const stmt = this.db.prepare(
4225
- "SELECT content, status FROM session_files WHERE session_id = ? AND path = ?"
4226
- ).bind(this.sessionId, path12);
4227
- const row = await stmt.first();
4228
- if (row) {
4229
- if (row.status === "deleted") return null;
4230
- if (row.content !== null) return row.content;
4231
- }
4232
- return null;
4233
- }
4234
- async writeFile(path12, content) {
4235
- await this.db.prepare(
4236
- `INSERT OR REPLACE INTO session_files (session_id, path, status, content, updated_at)
4237
- VALUES (?, ?, 'created', ?, ?)`
4238
- ).bind(this.sessionId, path12, content, Date.now()).run();
4239
- }
4240
- async editFile(path12, oldStr, newStr) {
4241
- const content = await this.readFile(path12);
4242
- if (content === null) {
4243
- return { success: false, error: `File not found: ${path12}` };
4244
- }
4245
- if (!content.includes(oldStr)) {
4246
- return { success: false, error: `String not found in ${path12}` };
4247
- }
4248
- const occurrences = content.split(oldStr).length - 1;
4249
- if (occurrences > 1) {
4250
- return { success: false, error: `String appears ${occurrences} times in ${path12}. Provide more context to make it unique.` };
4251
- }
4252
- const newContent = content.replace(oldStr, newStr);
4253
- await this.db.prepare(
4254
- `INSERT OR REPLACE INTO session_files (session_id, path, status, content, updated_at)
4255
- VALUES (?, ?, 'modified', ?, ?)`
4256
- ).bind(this.sessionId, path12, newContent, Date.now()).run();
4257
- return { success: true };
4258
- }
4259
- async listDirectory(dirPath) {
4260
- const prefix = dirPath.endsWith("/") ? dirPath : `${dirPath}/`;
4261
- const stmt = this.db.prepare(
4262
- `SELECT DISTINCT path FROM session_files
4263
- WHERE session_id = ? AND path LIKE ? AND status != 'deleted'`
4264
- ).bind(this.sessionId, `${prefix}%`);
4265
- const { results } = await stmt.all();
4266
- const children = /* @__PURE__ */ new Set();
4267
- for (const row of results) {
4268
- const relative2 = row.path.slice(prefix.length);
4269
- const firstSlash = relative2.indexOf("/");
4270
- if (firstSlash === -1) {
4271
- children.add(relative2);
4272
- } else {
4273
- children.add(relative2.slice(0, firstSlash + 1));
4274
- }
4275
- }
4276
- return Array.from(children).sort();
4277
- }
4278
- async getDirtyFiles() {
4279
- const stmt = this.db.prepare(
4280
- `SELECT path, content, status FROM session_files
4281
- WHERE session_id = ? AND status IN ('modified', 'created')`
4282
- ).bind(this.sessionId);
4283
- const { results } = await stmt.all();
4284
- return results;
4285
- }
4286
- async markClean(paths) {
4287
- if (paths.length === 0) return;
4288
- const stmts = paths.map(
4289
- (p) => this.db.prepare(
4290
- `UPDATE session_files SET status = 'clean', updated_at = ? WHERE session_id = ? AND path = ?`
4291
- ).bind(Date.now(), this.sessionId, p)
4292
- );
4293
- await this.db.batch(stmts);
4294
- }
4295
- async upsertFiles(files) {
4296
- if (files.length === 0) return;
4297
- const stmts = files.map(
4298
- (f) => this.db.prepare(
4299
- `INSERT OR REPLACE INTO session_files (session_id, path, status, content, updated_at)
4300
- VALUES (?, ?, ?, ?, ?)`
4301
- ).bind(this.sessionId, f.path, f.status, f.content, Date.now())
4302
- );
4303
- await this.db.batch(stmts);
4304
- }
4305
- };
4306
4211
  }
4307
4212
  });
4308
4213
 
@@ -4329,9 +4234,9 @@ function createFileTools(store) {
4329
4234
  readCache.clear();
4330
4235
  listCache.clear();
4331
4236
  }
4332
- function invalidatePath(path12) {
4333
- readCache.delete(path12);
4334
- const parent = path12.includes("/") ? path12.slice(0, path12.lastIndexOf("/") + 1) : "";
4237
+ function invalidatePath(path13) {
4238
+ readCache.delete(path13);
4239
+ const parent = path13.includes("/") ? path13.slice(0, path13.lastIndexOf("/") + 1) : "";
4335
4240
  listCache.delete(parent);
4336
4241
  listCache.delete("");
4337
4242
  listCache.delete(".");
@@ -4339,36 +4244,36 @@ function createFileTools(store) {
4339
4244
  }
4340
4245
  const handlers = {
4341
4246
  read_file: async (input) => {
4342
- const path12 = input.path;
4343
- const cached3 = readCache.get(path12);
4247
+ const path13 = input.path;
4248
+ const cached3 = readCache.get(path13);
4344
4249
  if (cached3 !== void 0) {
4345
4250
  return { output: cached3 };
4346
4251
  }
4347
- const content = await store.readFile(path12);
4252
+ const content = await store.readFile(path13);
4348
4253
  if (content === null) {
4349
- return { output: `File not found: ${path12}`, is_error: true };
4254
+ return { output: `File not found: ${path13}`, is_error: true };
4350
4255
  }
4351
- readCache.set(path12, content);
4256
+ readCache.set(path13, content);
4352
4257
  return { output: content };
4353
4258
  },
4354
4259
  write_file: async (input) => {
4355
- const path12 = input.path;
4260
+ const path13 = input.path;
4356
4261
  const content = input.content;
4357
- await store.writeFile(path12, content);
4358
- invalidatePath(path12);
4359
- readCache.set(path12, content);
4360
- return { output: `Written: ${path12}` };
4262
+ await store.writeFile(path13, content);
4263
+ invalidatePath(path13);
4264
+ readCache.set(path13, content);
4265
+ return { output: `Written: ${path13}` };
4361
4266
  },
4362
4267
  edit_file: async (input) => {
4363
- const path12 = input.path;
4268
+ const path13 = input.path;
4364
4269
  const oldStr = input.old_string;
4365
4270
  const newStr = input.new_string;
4366
- let content = readCache.get(path12);
4271
+ let content = readCache.get(path13);
4367
4272
  if (content === void 0) {
4368
- const stored = await store.readFile(path12);
4273
+ const stored = await store.readFile(path13);
4369
4274
  if (stored === null) {
4370
4275
  return {
4371
- output: `File not found: ${path12}. Did you read the file first?`,
4276
+ output: `File not found: ${path13}. Did you read the file first?`,
4372
4277
  is_error: true
4373
4278
  };
4374
4279
  }
@@ -4378,41 +4283,41 @@ function createFileTools(store) {
4378
4283
  if (lineNumbers.length === 0) {
4379
4284
  const lineCount = content.split("\n").length;
4380
4285
  return {
4381
- output: `old_string not found in ${path12}. The file has ${lineCount} lines. Did you read the file first?`,
4286
+ output: `old_string not found in ${path13}. The file has ${lineCount} lines. Did you read the file first?`,
4382
4287
  is_error: true
4383
4288
  };
4384
4289
  }
4385
4290
  if (lineNumbers.length > 1) {
4386
4291
  const locations = lineNumbers.map((ln) => ` - line ${ln}`).join("\n");
4387
4292
  return {
4388
- output: `old_string matches ${lineNumbers.length} locations in ${path12}:
4293
+ output: `old_string matches ${lineNumbers.length} locations in ${path13}:
4389
4294
  ${locations}
4390
4295
  Include more surrounding context to make old_string unique.`,
4391
4296
  is_error: true
4392
4297
  };
4393
4298
  }
4394
- const result = await store.editFile(path12, oldStr, newStr);
4299
+ const result = await store.editFile(path13, oldStr, newStr);
4395
4300
  if (!result.success) {
4396
4301
  return { output: result.error, is_error: true };
4397
4302
  }
4398
- invalidatePath(path12);
4399
- const updated = await store.readFile(path12);
4400
- if (updated !== null) readCache.set(path12, updated);
4401
- return { output: `Edited: ${path12}` };
4303
+ invalidatePath(path13);
4304
+ const updated = await store.readFile(path13);
4305
+ if (updated !== null) readCache.set(path13, updated);
4306
+ return { output: `Edited: ${path13}` };
4402
4307
  },
4403
4308
  list_directory: async (input) => {
4404
- const path12 = input.path;
4405
- const cached3 = listCache.get(path12);
4309
+ const path13 = input.path;
4310
+ const cached3 = listCache.get(path13);
4406
4311
  if (cached3 !== void 0) {
4407
4312
  if (cached3.length === 0) {
4408
- return { output: `Empty or not found: ${path12}` };
4313
+ return { output: `Empty or not found: ${path13}` };
4409
4314
  }
4410
4315
  return { output: cached3.join("\n") };
4411
4316
  }
4412
- const entries = await store.listDirectory(path12);
4413
- listCache.set(path12, entries);
4317
+ const entries = await store.listDirectory(path13);
4318
+ listCache.set(path13, entries);
4414
4319
  if (entries.length === 0) {
4415
- return { output: `Empty or not found: ${path12}` };
4320
+ return { output: `Empty or not found: ${path13}` };
4416
4321
  }
4417
4322
  return { output: entries.join("\n") };
4418
4323
  }
@@ -4480,234 +4385,15 @@ var init_files = __esm({
4480
4385
  });
4481
4386
 
4482
4387
  // ../agent/src/storage/indexer.ts
4483
- var CHUNK_SIZE, CHUNK_OVERLAP, BATCH_SIZE, MAX_FILE_SIZE, INDEXABLE_EXTENSIONS, RepoIndexer;
4388
+ var MAX_FILE_SIZE;
4484
4389
  var init_indexer = __esm({
4485
4390
  "../agent/src/storage/indexer.ts"() {
4486
4391
  "use strict";
4487
- CHUNK_SIZE = 500;
4488
- CHUNK_OVERLAP = 100;
4489
- BATCH_SIZE = 50;
4490
4392
  MAX_FILE_SIZE = 100 * 1024;
4491
- INDEXABLE_EXTENSIONS = /* @__PURE__ */ new Set([
4492
- ".ts",
4493
- ".js",
4494
- ".py",
4495
- ".go",
4496
- ".rs",
4497
- ".java",
4498
- ".tsx",
4499
- ".jsx",
4500
- ".md",
4501
- ".json",
4502
- ".yaml",
4503
- ".yml",
4504
- ".toml",
4505
- ".sql",
4506
- ".sh",
4507
- ".css",
4508
- ".html"
4509
- ]);
4510
- RepoIndexer = class {
4511
- config;
4512
- model;
4513
- constructor(config) {
4514
- this.config = config;
4515
- this.model = config.embeddingModel || "@cf/bge-base-en-v1.5";
4516
- }
4517
- /**
4518
- * Index files into Vectorize. Skips if repo+commit is already indexed.
4519
- * Returns number of chunks indexed.
4520
- */
4521
- async indexFiles(files) {
4522
- const existing = await this.config.db.prepare(
4523
- "SELECT COUNT(*) as count FROM repo_files WHERE repo_id = ? AND commit_sha = ?"
4524
- ).bind(this.config.repoId, this.config.commitSha).first();
4525
- if (existing && existing.count > 0) {
4526
- return 0;
4527
- }
4528
- const chunks = files.flatMap((f) => this.chunkFile(f.path, f.content));
4529
- if (chunks.length === 0) return 0;
4530
- let totalIndexed = 0;
4531
- for (let i = 0; i < chunks.length; i += BATCH_SIZE) {
4532
- const batch = chunks.slice(i, i + BATCH_SIZE);
4533
- const texts = batch.map((c) => c.content);
4534
- const embedResult = await this.config.ai.run(this.model, { text: texts });
4535
- const vectors = batch.map((chunk, j2) => ({
4536
- id: chunk.id,
4537
- values: embedResult.data[j2],
4538
- metadata: {
4539
- path: chunk.path,
4540
- repo_id: this.config.repoId,
4541
- start_line: String(chunk.startLine),
4542
- end_line: String(chunk.endLine)
4543
- }
4544
- }));
4545
- await this.config.vectorize.upsert(vectors);
4546
- totalIndexed += vectors.length;
4547
- }
4548
- const stmts = files.map(
4549
- (f) => this.config.db.prepare(
4550
- `INSERT OR REPLACE INTO repo_files (repo_id, commit_sha, path, size, hash)
4551
- VALUES (?, ?, ?, ?, ?)`
4552
- ).bind(
4553
- this.config.repoId,
4554
- this.config.commitSha,
4555
- f.path,
4556
- f.content.length,
4557
- this.simpleHash(f.content)
4558
- )
4559
- );
4560
- for (let i = 0; i < stmts.length; i += 100) {
4561
- await this.config.db.batch(stmts.slice(i, i + 100));
4562
- }
4563
- return totalIndexed;
4564
- }
4565
- /**
4566
- * Delta re-index: index only files that changed.
4567
- */
4568
- async reindexFile(path12, content) {
4569
- const prefix = `${this.config.repoId}:${path12}:`;
4570
- const oldIds = Array.from({ length: 100 }, (_2, i) => `${prefix}${i}`);
4571
- await this.config.vectorize.deleteByIds(oldIds);
4572
- const chunks = this.chunkFile(path12, content);
4573
- if (chunks.length === 0) return 0;
4574
- const texts = chunks.map((c) => c.content);
4575
- const embedResult = await this.config.ai.run(this.model, { text: texts });
4576
- const vectors = chunks.map((chunk, j2) => ({
4577
- id: chunk.id,
4578
- values: embedResult.data[j2],
4579
- metadata: {
4580
- path: chunk.path,
4581
- repo_id: this.config.repoId,
4582
- start_line: String(chunk.startLine),
4583
- end_line: String(chunk.endLine)
4584
- }
4585
- }));
4586
- await this.config.vectorize.upsert(vectors);
4587
- return vectors.length;
4588
- }
4589
- /**
4590
- * Check if a repo+commit is already indexed.
4591
- */
4592
- async isIndexed() {
4593
- const result = await this.config.db.prepare(
4594
- "SELECT COUNT(*) as count FROM repo_files WHERE repo_id = ? AND commit_sha = ?"
4595
- ).bind(this.config.repoId, this.config.commitSha).first();
4596
- return (result?.count ?? 0) > 0;
4597
- }
4598
- /**
4599
- * Read all files from a FileStore and index those with indexable extensions.
4600
- * Skips files larger than 100KB.
4601
- * Returns the number of chunks indexed.
4602
- */
4603
- async indexFromStore(store, basePath = "/") {
4604
- const storeDb = store.db;
4605
- const sessionId = store.sessionId;
4606
- const prefix = basePath === "/" ? "" : basePath;
4607
- const allFiles = await storeDb.prepare(
4608
- `SELECT path, content FROM session_files
4609
- WHERE session_id = ? AND status != 'deleted' AND path LIKE ?`
4610
- ).bind(sessionId, `${prefix}%`).all();
4611
- const files = [];
4612
- for (const row of allFiles.results) {
4613
- const dotIndex = row.path.lastIndexOf(".");
4614
- if (dotIndex === -1) continue;
4615
- const ext = row.path.slice(dotIndex);
4616
- if (!INDEXABLE_EXTENSIONS.has(ext)) continue;
4617
- if (row.content && row.content.length > MAX_FILE_SIZE) continue;
4618
- if (!row.content) continue;
4619
- files.push({ path: row.path, content: row.content });
4620
- }
4621
- if (files.length === 0) return 0;
4622
- return this.indexFiles(files);
4623
- }
4624
- chunkFile(path12, content) {
4625
- if (content.length === 0) return [];
4626
- const chunks = [];
4627
- let charPos = 0;
4628
- let lineNum = 1;
4629
- let chunkIndex = 0;
4630
- while (charPos < content.length) {
4631
- const chunkContent = content.slice(charPos, charPos + CHUNK_SIZE);
4632
- const startLine = lineNum;
4633
- const linesInChunk = chunkContent.split("\n").length - 1;
4634
- const endLine = startLine + linesInChunk;
4635
- chunks.push({
4636
- id: `${this.config.repoId}:${path12}:${chunkIndex}`,
4637
- path: path12,
4638
- content: chunkContent,
4639
- startLine,
4640
- endLine
4641
- });
4642
- chunkIndex++;
4643
- const advance = CHUNK_SIZE - CHUNK_OVERLAP;
4644
- charPos += advance;
4645
- const advancedContent = content.slice(charPos - advance, charPos);
4646
- lineNum += (advancedContent.match(/\n/g) || []).length;
4647
- }
4648
- return chunks;
4649
- }
4650
- simpleHash(content) {
4651
- let hash = 0;
4652
- for (let i = 0; i < content.length; i++) {
4653
- const char = content.charCodeAt(i);
4654
- hash = (hash << 5) - hash + char;
4655
- hash |= 0;
4656
- }
4657
- return hash.toString(16);
4658
- }
4659
- };
4660
4393
  }
4661
4394
  });
4662
4395
 
4663
4396
  // ../agent/src/tools/search.ts
4664
- function createSearchTool(config) {
4665
- const model = config.embeddingModel || "@cf/bge-base-en-v1.5";
4666
- return async (input) => {
4667
- const query = input.query;
4668
- const limit = input.limit || 10;
4669
- if (!query || query.trim().length === 0) {
4670
- return { output: "Search query cannot be empty", is_error: true };
4671
- }
4672
- try {
4673
- let embedResult;
4674
- try {
4675
- embedResult = await config.ai.run(model, { text: [query] });
4676
- } catch {
4677
- return { output: `Semantic search is not available. Use the bash tool with grep to search the codebase instead.
4678
- Example: bash -c "grep -rn 'pattern' ."` };
4679
- }
4680
- const queryVector = embedResult.data[0];
4681
- const results = await config.vectorize.query(queryVector, {
4682
- topK: limit,
4683
- filter: { repo_id: config.repoId },
4684
- returnMetadata: "all"
4685
- });
4686
- if (results.matches.length === 0) {
4687
- return { output: "No results found." };
4688
- }
4689
- const byFile = /* @__PURE__ */ new Map();
4690
- for (const match of results.matches) {
4691
- const path12 = match.metadata?.path ?? "unknown";
4692
- if (!byFile.has(path12)) byFile.set(path12, []);
4693
- byFile.get(path12).push({
4694
- score: match.score,
4695
- startLine: match.metadata?.start_line ?? "?",
4696
- endLine: match.metadata?.end_line ?? "?"
4697
- });
4698
- }
4699
- const lines2 = [];
4700
- for (const [path12, matches] of byFile) {
4701
- const bestScore = Math.max(...matches.map((m2) => m2.score));
4702
- const lineRanges = matches.sort((a, b2) => Number(a.startLine) - Number(b2.startLine)).map((m2) => `L${m2.startLine}-${m2.endLine}`).join(", ");
4703
- lines2.push(`${path12} (score: ${bestScore.toFixed(3)}) \u2014 ${lineRanges}`);
4704
- }
4705
- return { output: lines2.join("\n") };
4706
- } catch (err) {
4707
- return { output: `Search failed: ${err instanceof Error ? err.message : String(err)}`, is_error: true };
4708
- }
4709
- };
4710
- }
4711
4397
  function createHostSearchTool(exec2) {
4712
4398
  return async (input) => {
4713
4399
  const query = input.query;
@@ -4748,126 +4434,9 @@ var init_search = __esm({
4748
4434
  });
4749
4435
 
4750
4436
  // ../agent/src/tools/sentry.ts
4751
- function formatIssue(issue) {
4752
- return `[${issue.shortId}] ${issue.title}
4753
- Level: ${issue.level} | Count: ${issue.count} | Last seen: ${issue.lastSeen}`;
4754
- }
4755
- function formatIssueDetail(issue) {
4756
- const lines2 = [];
4757
- lines2.push(`Issue: ${issue.shortId} (${issue.id})`);
4758
- lines2.push(`Title: ${issue.title}`);
4759
- lines2.push(`Culprit: ${issue.culprit}`);
4760
- lines2.push(`Level: ${issue.level} | Count: ${issue.count}`);
4761
- lines2.push(`First seen: ${issue.firstSeen}`);
4762
- lines2.push(`Last seen: ${issue.lastSeen}`);
4763
- if (issue.metadata.type || issue.metadata.value) {
4764
- lines2.push(`
4765
- Error: ${issue.metadata.type ?? ""}: ${issue.metadata.value ?? ""}`);
4766
- }
4767
- if (issue.tags.length > 0) {
4768
- lines2.push(`
4769
- Tags:`);
4770
- for (const tag of issue.tags) {
4771
- lines2.push(` ${tag.key}: ${tag.value}`);
4772
- }
4773
- }
4774
- if (issue.stackTrace && issue.stackTrace.length > 0) {
4775
- lines2.push(`
4776
- Stack trace:`);
4777
- for (const frame of issue.stackTrace) {
4778
- lines2.push(` ${frame.filename}:${frame.lineNo} in ${frame.function}`);
4779
- if (frame.context.length > 0) {
4780
- for (const [lineNo, code] of frame.context) {
4781
- const marker = lineNo === frame.lineNo ? ">" : " ";
4782
- lines2.push(` ${marker} ${lineNo}: ${code}`);
4783
- }
4784
- }
4785
- }
4786
- }
4787
- return lines2.join("\n");
4788
- }
4789
- function createSentryTools(fetchFn) {
4790
- const handlers = {
4791
- sentry_list_issues: async (input) => {
4792
- const project = input.project;
4793
- const query = input.query;
4794
- const params = new URLSearchParams();
4795
- if (project) params.set("project", project);
4796
- if (query) params.set("query", query);
4797
- const qs = params.toString();
4798
- const path12 = `/api/sentry/issues${qs ? `?${qs}` : ""}`;
4799
- try {
4800
- const res = await fetchFn(path12);
4801
- if (!res.ok) {
4802
- const text = await res.text();
4803
- return { output: `Sentry API error (${res.status}): ${text}`, is_error: true };
4804
- }
4805
- const issues = await res.json();
4806
- if (issues.length === 0) {
4807
- return { output: "No unresolved issues found." };
4808
- }
4809
- return { output: issues.map(formatIssue).join("\n\n") };
4810
- } catch (err) {
4811
- return { output: `Failed to list Sentry issues: ${err.message}`, is_error: true };
4812
- }
4813
- },
4814
- sentry_get_issue: async (input) => {
4815
- const issueId = input.issue_id;
4816
- if (!issueId) {
4817
- return { output: "Missing required parameter: issue_id", is_error: true };
4818
- }
4819
- try {
4820
- const res = await fetchFn(`/api/sentry/issues/${encodeURIComponent(issueId)}`);
4821
- if (!res.ok) {
4822
- const text = await res.text();
4823
- return { output: `Sentry API error (${res.status}): ${text}`, is_error: true };
4824
- }
4825
- const issue = await res.json();
4826
- return { output: formatIssueDetail(issue) };
4827
- } catch (err) {
4828
- return { output: `Failed to get Sentry issue: ${err.message}`, is_error: true };
4829
- }
4830
- }
4831
- };
4832
- return { handlers };
4833
- }
4834
- var SENTRY_LIST_ISSUES_TOOL, SENTRY_GET_ISSUE_TOOL, SENTRY_TOOL_DEFINITIONS;
4835
4437
  var init_sentry = __esm({
4836
4438
  "../agent/src/tools/sentry.ts"() {
4837
4439
  "use strict";
4838
- SENTRY_LIST_ISSUES_TOOL = {
4839
- name: "sentry_list_issues",
4840
- description: "List unresolved Sentry issues. Optionally filter by project slug or search query.",
4841
- input_schema: {
4842
- type: "object",
4843
- properties: {
4844
- project: {
4845
- type: "string",
4846
- description: "Sentry project slug to filter by (optional)"
4847
- },
4848
- query: {
4849
- type: "string",
4850
- description: "Search query to filter issues (optional)"
4851
- }
4852
- },
4853
- required: []
4854
- }
4855
- };
4856
- SENTRY_GET_ISSUE_TOOL = {
4857
- name: "sentry_get_issue",
4858
- description: "Get detailed information about a Sentry issue including its stack trace.",
4859
- input_schema: {
4860
- type: "object",
4861
- properties: {
4862
- issue_id: {
4863
- type: "string",
4864
- description: "The Sentry issue ID"
4865
- }
4866
- },
4867
- required: ["issue_id"]
4868
- }
4869
- };
4870
- SENTRY_TOOL_DEFINITIONS = [SENTRY_LIST_ISSUES_TOOL, SENTRY_GET_ISSUE_TOOL];
4871
4440
  }
4872
4441
  });
4873
4442
 
@@ -4966,83 +4535,9 @@ var init_github = __esm({
4966
4535
  });
4967
4536
 
4968
4537
  // ../agent/src/tools/preview.ts
4969
- function formatResult(body) {
4970
- switch (body.status) {
4971
- case "ready":
4972
- return { output: `Preview ready: ${body.url}
4973
- Framework: ${body.framework ?? "unknown"}` };
4974
- case "starting":
4975
- return {
4976
- output: `Preview server is starting (still compiling). URL will be: ${body.url ?? "unknown"}
4977
- Check again in a few seconds by calling this tool again.`
4978
- };
4979
- case "crashed":
4980
- return {
4981
- output: `Preview server crashed.${body.url ? ` URL was: ${body.url}` : ""}${body.exitCode !== void 0 ? ` Exit code: ${body.exitCode}` : ""}${body.logs ? `
4982
- Logs:
4983
- ${body.logs}` : ""}${body.error ? `
4984
- ${body.error}` : ""}`,
4985
- is_error: true
4986
- };
4987
- default:
4988
- return { output: body.error ?? "Unknown preview status", is_error: true };
4989
- }
4990
- }
4991
- function createPreviewTool(fetchFn) {
4992
- return async (_input) => {
4993
- let startBody;
4994
- try {
4995
- const startRes = await fetchFn("/preview", { method: "POST" });
4996
- startBody = await startRes.json();
4997
- if (!startRes.ok) {
4998
- return { output: startBody.error ?? `Failed to start preview (HTTP ${startRes.status})`, is_error: true };
4999
- }
5000
- } catch (err) {
5001
- return { output: `Failed to start preview: ${err.message}`, is_error: true };
5002
- }
5003
- if (startBody.status !== "starting") {
5004
- return formatResult(startBody);
5005
- }
5006
- let lastPollError;
5007
- for (let attempt = 0; attempt < POLL_ATTEMPTS; attempt++) {
5008
- await new Promise((resolve2) => setTimeout(resolve2, POLL_DELAY_MS));
5009
- try {
5010
- const statusRes = await fetchFn("/preview", { method: "GET" });
5011
- const statusBody = await statusRes.json();
5012
- lastPollError = void 0;
5013
- if (statusBody.status === "ready" || statusBody.status === "crashed") {
5014
- return formatResult(statusBody);
5015
- }
5016
- } catch (err) {
5017
- lastPollError = err instanceof Error ? err.message : String(err);
5018
- console.error("Preview status poll failed, retrying", { error: lastPollError });
5019
- }
5020
- }
5021
- if (lastPollError) {
5022
- return {
5023
- output: `Could not confirm preview status after ${POLL_ATTEMPTS} attempts (last error: ${lastPollError}). The server may still be starting, or status checks may be failing \u2014 try calling this tool again.`,
5024
- is_error: true
5025
- };
5026
- }
5027
- return formatResult(startBody);
5028
- };
5029
- }
5030
- var PREVIEW_TOOL, PREVIEW_TOOL_DEFINITIONS, POLL_ATTEMPTS, POLL_DELAY_MS;
5031
4538
  var init_preview = __esm({
5032
4539
  "../agent/src/tools/preview.ts"() {
5033
4540
  "use strict";
5034
- PREVIEW_TOOL = {
5035
- name: "preview",
5036
- description: 'Start or check the status of the front-end dev server preview for this workspace and get a live URL to view the current state of the app. Call this after making a front-end change to verify it visually. The result distinguishes "starting" (server launching, still compiling) from "ready" (server is up, safe to view) from "crashed" (server process died \u2014 check logs in the result).',
5037
- input_schema: {
5038
- type: "object",
5039
- properties: {},
5040
- required: []
5041
- }
5042
- };
5043
- PREVIEW_TOOL_DEFINITIONS = [PREVIEW_TOOL];
5044
- POLL_ATTEMPTS = 3;
5045
- POLL_DELAY_MS = 1500;
5046
4541
  }
5047
4542
  });
5048
4543
 
@@ -5109,414 +4604,23 @@ var init_todo = __esm({
5109
4604
  });
5110
4605
 
5111
4606
  // ../agent/src/tools/codegen.ts
5112
- async function detectStackType(fileStore) {
5113
- if (await fileStore.readFile("wrangler.toml") !== null || await fileStore.readFile("wrangler.jsonc") !== null) return "worker";
5114
- if (await fileStore.readFile("app.json") !== null || await fileStore.readFile("expo.json") !== null) return "mobile";
5115
- if (await fileStore.readFile("next.config.js") !== null || await fileStore.readFile("next.config.ts") !== null || await fileStore.readFile("next.config.mjs") !== null) return "pages";
5116
- return "worker";
5117
- }
5118
- async function cancelBuild(baseUrl, buildId, authHeader) {
5119
- try {
5120
- const res = await fetch(`${baseUrl}/builds/${buildId}/cancel`, {
5121
- method: "POST",
5122
- headers: { "Authorization": authHeader }
5123
- });
5124
- if (!res.ok) console.error(`[codegen] cancel of build ${buildId} failed: HTTP ${res.status}`);
5125
- } catch (err) {
5126
- console.error(`[codegen] cancel of build ${buildId} failed:`, err);
5127
- }
5128
- }
5129
- function createCodegenTools(fileStore, config) {
5130
- const handlers = {
5131
- build_feature: async (input, onOutput) => {
5132
- const title = input.title;
5133
- const requirements = input.requirements;
5134
- const context = input.context;
5135
- const stackTypeOverride = input.stackType;
5136
- if (!title || !requirements) {
5137
- return { output: "Missing required parameters: title and requirements", is_error: true };
5138
- }
5139
- const stackType = stackTypeOverride ?? await detectStackType(fileStore);
5140
- const toolId = crypto.randomUUID();
5141
- if (config.jwtToken) {
5142
- const baseUrl = "https://api.codemindhq.dev";
5143
- const authHeader = `Bearer ${config.jwtToken}`;
5144
- onOutput?.(toolId, "stdout", `[codegen] Submitting build...
5145
- `);
5146
- const submitRes = await fetch(`${baseUrl}/builds`, {
5147
- method: "POST",
5148
- headers: { "Content-Type": "application/json", "Authorization": authHeader },
5149
- body: JSON.stringify({
5150
- product: config.product ?? "spacecode",
5151
- repoUrl: config.repoUrl,
5152
- storyTitle: title,
5153
- acceptanceCriteria: requirements,
5154
- architectureContext: context ?? "",
5155
- stackType,
5156
- skipCommit: true
5157
- })
5158
- });
5159
- if (!submitRes.ok) {
5160
- const err = await submitRes.text().catch(() => "unknown");
5161
- throw new Error(`Codegen submit error: ${submitRes.status} ${err}`);
5162
- }
5163
- const { id: buildId2 } = await submitRes.json();
5164
- onOutput?.(toolId, "stdout", `[codegen] Build ${buildId2} queued, waiting...
5165
- `);
5166
- const baseDelay = CODEGEN_POLL_INTERVAL_MS;
5167
- let status = "queued";
5168
- let consecutiveFailures = 0;
5169
- for (let i = 0; i < CODEGEN_POLL_MAX_ATTEMPTS && status !== "completed"; i++) {
5170
- await new Promise((r) => setTimeout(r, baseDelay));
5171
- const poll = await fetch(`${baseUrl}/builds/${buildId2}`, {
5172
- headers: { "Authorization": authHeader }
5173
- });
5174
- if (!poll.ok) {
5175
- consecutiveFailures++;
5176
- if (consecutiveFailures >= MAX_CONSECUTIVE_POLL_FAILURES) {
5177
- await cancelBuild(baseUrl, buildId2, authHeader);
5178
- throw new Error(`Codegen build ${buildId2}: polling failed ${consecutiveFailures} times in a row (last HTTP ${poll.status}); build cancelled`);
5179
- }
5180
- continue;
5181
- }
5182
- consecutiveFailures = 0;
5183
- const b2 = await poll.json();
5184
- status = b2.status;
5185
- if (b2.error) throw new Error(`Codegen failed: ${b2.error}`);
5186
- if (TERMINAL_FAILURE_STATUSES.has(status)) {
5187
- throw new Error(`Build ${buildId2} ended with status: ${status}`);
5188
- }
5189
- }
5190
- if (status !== "completed") {
5191
- await cancelBuild(baseUrl, buildId2, authHeader);
5192
- throw new Error(`Build ${buildId2} did not finish within ${CODEGEN_POLL_MAX_ATTEMPTS * baseDelay / 1e3}s (last status: ${status}); cancelled`);
5193
- }
5194
- const filesRes = await fetch(`${baseUrl}/builds/${buildId2}/files`, {
5195
- headers: { "Authorization": authHeader }
5196
- });
5197
- if (!filesRes.ok) throw new Error(`Failed to fetch files: ${filesRes.status}`);
5198
- const { files: files2 } = await filesRes.json();
5199
- if (!files2?.length) throw new Error("Build completed but no files generated");
5200
- const written2 = [];
5201
- for (const file of files2) {
5202
- await fileStore.writeFile(file.path, file.content);
5203
- written2.push(file.path);
5204
- }
5205
- onOutput?.(toolId, "stdout", `[codegen] ${files2.length} files written
5206
- `);
5207
- return {
5208
- output: [
5209
- `Generated ${files2.length} files for: ${title}`,
5210
- "",
5211
- "Files written to workspace:",
5212
- ...written2.map((p) => ` ${p}`),
5213
- "",
5214
- "Run tests to verify, then commit when ready."
5215
- ].join("\n")
5216
- };
5217
- }
5218
- if (!config.codegenUrl) {
5219
- throw new Error("No codegen backend configured (set jwtToken or codegenUrl)");
5220
- }
5221
- const res = await fetch(`${config.codegenUrl}/builds?mode=interactive`, {
5222
- method: "POST",
5223
- headers: {
5224
- "Content-Type": "application/json",
5225
- "Authorization": `Bearer ${config.apiKey}`,
5226
- "x-tenant-id": config.tenantId ?? ""
5227
- },
5228
- body: JSON.stringify({
5229
- product: config.product,
5230
- repoUrl: config.repoUrl,
5231
- storyTitle: title,
5232
- acceptanceCriteria: requirements,
5233
- architectureContext: context ?? "",
5234
- stackType,
5235
- skipCommit: true
5236
- })
5237
- });
5238
- if (!res.ok) {
5239
- const err = await res.text().catch(() => "unknown");
5240
- throw new Error(`Codegen service error: ${res.status} ${err}`);
5241
- }
5242
- const reader = res.body?.getReader();
5243
- if (!reader) throw new Error("No response body");
5244
- const decoder = new TextDecoder();
5245
- let buildId = "";
5246
- let summary = "";
5247
- let buffer = "";
5248
- let currentEvent = "";
5249
- try {
5250
- while (true) {
5251
- const { done, value } = await reader.read();
5252
- if (done) break;
5253
- buffer += decoder.decode(value, { stream: true });
5254
- const lines2 = buffer.split("\n");
5255
- buffer = lines2.pop() ?? "";
5256
- for (const line of lines2) {
5257
- if (line.startsWith("event: ")) {
5258
- currentEvent = line.slice(7).trim();
5259
- continue;
5260
- }
5261
- if (!line.startsWith("data: ")) continue;
5262
- let data;
5263
- try {
5264
- data = JSON.parse(line.slice(6));
5265
- } catch (parseErr) {
5266
- throw new Error(`Codegen stream sent malformed event data: ${parseErr instanceof Error ? parseErr.message : String(parseErr)}`);
5267
- }
5268
- if (currentEvent === "phase" && onOutput) {
5269
- const count = data.componentCount ? ` (${data.componentCount} components)` : "";
5270
- onOutput(toolId, "stdout", `[codegen] ${data.phase}: ${data.status}${count}
5271
- `);
5272
- }
5273
- if (currentEvent === "component" && onOutput) {
5274
- const status = data.fromLibrary ? "reused from library" : data.status;
5275
- onOutput(toolId, "stdout", `[codegen] ${data.name}: ${status}
5276
- `);
5277
- }
5278
- if (currentEvent === "error") {
5279
- throw new Error(`Codegen error: ${data.message}`);
5280
- }
5281
- if (currentEvent === "complete") {
5282
- buildId = data.buildId;
5283
- summary = `Generated ${data.filesGenerated ?? 0} files (${data.componentsReused ?? 0} reused)`;
5284
- }
5285
- currentEvent = "";
5286
- }
5287
- }
5288
- } finally {
5289
- reader.releaseLock();
5290
- }
5291
- if (!buildId) {
5292
- throw new Error("Codegen stream ended without completion");
5293
- }
5294
- const buildRes = await fetch(`${config.codegenUrl}/builds/${buildId}`, {
5295
- headers: {
5296
- "Authorization": `Bearer ${config.apiKey}`,
5297
- "x-tenant-id": config.tenantId ?? ""
5298
- }
5299
- });
5300
- if (!buildRes.ok) {
5301
- throw new Error(`Failed to fetch build result: ${buildRes.status}`);
5302
- }
5303
- const build2 = await buildRes.json();
5304
- if (!build2.result) {
5305
- throw new Error("Build completed but no files in result");
5306
- }
5307
- let files;
5308
- try {
5309
- const parsed = JSON.parse(build2.result);
5310
- files = parsed.files ?? [];
5311
- } catch {
5312
- throw new Error("Failed to parse build result");
5313
- }
5314
- if (files.length === 0) {
5315
- throw new Error("Codegen produced no files");
5316
- }
5317
- const written = [];
5318
- for (const file of files) {
5319
- await fileStore.writeFile(file.path, file.content);
5320
- written.push(file.path);
5321
- }
5322
- return {
5323
- output: [
5324
- summary,
5325
- "",
5326
- "Files written to workspace:",
5327
- ...written.map((p) => ` ${p}`),
5328
- "",
5329
- "Run tests to verify, then commit when ready."
5330
- ].join("\n")
5331
- };
5332
- }
5333
- };
5334
- return { handlers };
5335
- }
5336
- var BUILD_FEATURE_TOOL, CODEGEN_TOOL_DEFINITIONS, CODEGEN_POLL_INTERVAL_MS, CODEGEN_POLL_MAX_ATTEMPTS, MAX_CONSECUTIVE_POLL_FAILURES, TERMINAL_FAILURE_STATUSES;
5337
4607
  var init_codegen = __esm({
5338
4608
  "../agent/src/tools/codegen.ts"() {
5339
4609
  "use strict";
5340
- BUILD_FEATURE_TOOL = {
5341
- name: "build_feature",
5342
- description: "Generate a complete feature using the platform codegen service. Produces multiple files with tests, leveraging a library of reusable components. Use this for multi-file features, new modules, or significant refactors. Files are written to the workspace \u2014 run tests afterwards to verify. Prefer this over manually writing multiple files.",
5343
- input_schema: {
5344
- type: "object",
5345
- properties: {
5346
- title: {
5347
- type: "string",
5348
- description: 'Feature title (e.g., "Add user authentication with sessions and API keys")'
5349
- },
5350
- requirements: {
5351
- type: "string",
5352
- description: "Acceptance criteria or detailed requirements for the feature"
5353
- },
5354
- context: {
5355
- type: "string",
5356
- description: "Architecture context \u2014 existing code patterns, tech stack, conventions."
5357
- },
5358
- stackType: {
5359
- type: "string",
5360
- enum: ["worker", "mobile", "pages"],
5361
- description: "Override auto-detected stack type. Usually not needed."
5362
- }
5363
- },
5364
- required: ["title", "requirements"]
5365
- }
5366
- };
5367
- CODEGEN_TOOL_DEFINITIONS = [BUILD_FEATURE_TOOL];
5368
- CODEGEN_POLL_INTERVAL_MS = 5e3;
5369
- CODEGEN_POLL_MAX_ATTEMPTS = 120;
5370
- MAX_CONSECUTIVE_POLL_FAILURES = 5;
5371
- TERMINAL_FAILURE_STATUSES = /* @__PURE__ */ new Set(["failed", "cancelled", "canceled", "error"]);
5372
4610
  }
5373
4611
  });
5374
4612
 
5375
4613
  // ../agent/src/sandbox/snapshot-manager.ts
5376
- var SnapshotManager;
5377
4614
  var init_snapshot_manager = __esm({
5378
4615
  "../agent/src/sandbox/snapshot-manager.ts"() {
5379
4616
  "use strict";
5380
- SnapshotManager = class {
5381
- constructor(config) {
5382
- this.config = config;
5383
- }
5384
- /**
5385
- * Get or create a container for the given repo+lockfile.
5386
- * If a snapshot exists, restore from it (~1-3s).
5387
- * If not, create fresh container (~30-60s), then snapshot it for next time.
5388
- */
5389
- async getOrCreateContainer(repoId, lockfileHash, setupFn) {
5390
- const existing = await this.config.db.prepare(
5391
- "SELECT backup_handle, image_version FROM repo_snapshots WHERE repo_id = ? AND lockfile_hash = ?"
5392
- ).bind(repoId, lockfileHash).first();
5393
- if (existing && existing.image_version === this.config.imageVersion) {
5394
- const sandbox2 = await this.config.sandboxNs.restore(existing.backup_handle);
5395
- await this.config.db.prepare(
5396
- "UPDATE repo_snapshots SET last_used_at = ? WHERE repo_id = ? AND lockfile_hash = ?"
5397
- ).bind(Date.now(), repoId, lockfileHash).run();
5398
- return sandbox2;
5399
- }
5400
- const sandbox = await this.config.sandboxNs.create();
5401
- if (setupFn) {
5402
- await setupFn(sandbox);
5403
- }
5404
- const handle = await sandbox.backup();
5405
- await this.config.db.prepare(
5406
- `INSERT OR REPLACE INTO repo_snapshots (repo_id, lockfile_hash, backup_handle, image_version, created_at, last_used_at, ttl_hours)
5407
- VALUES (?, ?, ?, ?, ?, ?, 168)`
5408
- ).bind(repoId, lockfileHash, handle, this.config.imageVersion, Date.now(), Date.now()).run();
5409
- return sandbox;
5410
- }
5411
- /**
5412
- * Clean up expired snapshots (older than TTL).
5413
- */
5414
- async cleanupExpired() {
5415
- const now = Date.now();
5416
- const result = await this.config.db.prepare(
5417
- "SELECT repo_id, lockfile_hash FROM repo_snapshots WHERE (last_used_at + ttl_hours * 3600000) < ?"
5418
- ).bind(now).all();
5419
- if (result.results.length === 0) return 0;
5420
- const stmts = result.results.map(
5421
- (r) => this.config.db.prepare(
5422
- "DELETE FROM repo_snapshots WHERE repo_id = ? AND lockfile_hash = ?"
5423
- ).bind(r.repo_id, r.lockfile_hash)
5424
- );
5425
- await this.config.db.batch(stmts);
5426
- return result.results.length;
5427
- }
5428
- };
5429
4617
  }
5430
4618
  });
5431
4619
 
5432
4620
  // ../agent/src/sandbox/sync.ts
5433
- async function syncBeforeBash(store, sandbox, workDir) {
5434
- const dirtyFiles = await store.getDirtyFiles();
5435
- const errors = [];
5436
- let pushed = 0;
5437
- for (const file of dirtyFiles) {
5438
- try {
5439
- const fullPath = `${workDir}/${file.path}`;
5440
- const dir = fullPath.substring(0, fullPath.lastIndexOf("/"));
5441
- await sandbox.exec(`mkdir -p '${dir}'`);
5442
- await sandbox.writeFile(fullPath, file.content);
5443
- pushed++;
5444
- } catch (err) {
5445
- errors.push(`Failed to push ${file.path}: ${err instanceof Error ? err.message : String(err)}`);
5446
- }
5447
- }
5448
- return { filesPushed: pushed, errors };
5449
- }
5450
- async function syncAfterBash(store, sandbox, workDir) {
5451
- const errors = [];
5452
- let pulled = 0;
5453
- const gitResult = await sandbox.exec(`cd '${workDir}' && git status --short`);
5454
- if (gitResult.exitCode !== 0) {
5455
- return { filesPulled: 0, errors: [`git status failed: ${gitResult.stderr}`] };
5456
- }
5457
- if (!gitResult.stdout.trim()) {
5458
- return { filesPulled: 0, errors: [] };
5459
- }
5460
- const changedFiles = parseGitStatus(gitResult.stdout);
5461
- const fileEntries = [];
5462
- for (const file of changedFiles) {
5463
- if (file.status === "deleted") {
5464
- fileEntries.push({ path: file.path, content: "", status: "deleted" });
5465
- pulled++;
5466
- continue;
5467
- }
5468
- if (isGeneratedPath(file.path)) continue;
5469
- try {
5470
- const content = await sandbox.readFile(`${workDir}/${file.path}`);
5471
- fileEntries.push({
5472
- path: file.path,
5473
- content,
5474
- status: file.status === "new" ? "created" : "modified"
5475
- });
5476
- pulled++;
5477
- } catch (err) {
5478
- errors.push(`Failed to pull ${file.path}: ${err instanceof Error ? err.message : String(err)}`);
5479
- }
5480
- }
5481
- if (fileEntries.length > 0) {
5482
- await store.upsertFiles(fileEntries);
5483
- }
5484
- return { filesPulled: pulled, errors };
5485
- }
5486
- function parseGitStatus(output) {
5487
- const entries = [];
5488
- for (const line of output.split("\n")) {
5489
- if (!line.trim()) continue;
5490
- const statusCode = line.substring(0, 2);
5491
- const path12 = line.substring(3).trim();
5492
- if (!path12) continue;
5493
- if (statusCode.includes("D")) {
5494
- entries.push({ path: path12, status: "deleted" });
5495
- } else if (statusCode === "??" || statusCode.includes("A")) {
5496
- entries.push({ path: path12, status: "new" });
5497
- } else {
5498
- entries.push({ path: path12, status: "modified" });
5499
- }
5500
- }
5501
- return entries;
5502
- }
5503
- function isGeneratedPath(path12) {
5504
- return GENERATED_PATTERNS.some((p) => path12.startsWith(p) || path12.includes(`/${p}`));
5505
- }
5506
- var GENERATED_PATTERNS;
5507
4621
  var init_sync = __esm({
5508
4622
  "../agent/src/sandbox/sync.ts"() {
5509
4623
  "use strict";
5510
- GENERATED_PATTERNS = [
5511
- "node_modules/",
5512
- "dist/",
5513
- ".cache/",
5514
- ".next/",
5515
- "__pycache__/",
5516
- ".pytest_cache/",
5517
- "coverage/",
5518
- ".git/"
5519
- ];
5520
4624
  }
5521
4625
  });
5522
4626
 
@@ -5927,17 +5031,11 @@ import * as path2 from "node:path";
5927
5031
  function truncate(e) {
5928
5032
  return e.subject.length > MAX_SUBJECT ? { ...e, subject: e.subject.slice(0, MAX_SUBJECT) + "...[truncated]" } : e;
5929
5033
  }
5930
- var MAX_SUBJECT, MemoryAuditLog, FileAuditLog;
5034
+ var MAX_SUBJECT, FileAuditLog;
5931
5035
  var init_audit_log = __esm({
5932
5036
  "../agent/src/host/audit-log.ts"() {
5933
5037
  "use strict";
5934
5038
  MAX_SUBJECT = 4096;
5935
- MemoryAuditLog = class {
5936
- entries = [];
5937
- async record(entry) {
5938
- this.entries.push(truncate(entry));
5939
- }
5940
- };
5941
5039
  FileAuditLog = class {
5942
5040
  constructor(file) {
5943
5041
  this.file = file;
@@ -5951,89 +5049,23 @@ var init_audit_log = __esm({
5951
5049
  });
5952
5050
 
5953
5051
  // ../agent/src/host/cloud-host.ts
5954
- var CloudHost;
5955
5052
  var init_cloud_host = __esm({
5956
5053
  "../agent/src/host/cloud-host.ts"() {
5957
5054
  "use strict";
5958
- CloudHost = class {
5959
- kind = "cloud";
5960
- files;
5961
- execFn;
5962
- constructor(config) {
5963
- this.files = config.files;
5964
- this.execFn = config.exec;
5965
- }
5966
- exec(command, onOutput) {
5967
- return this.execFn(command, onOutput);
5968
- }
5969
- };
5970
5055
  }
5971
5056
  });
5972
5057
 
5973
5058
  // ../agent/src/replay-buffer.ts
5974
- var ReplayBuffer;
5975
5059
  var init_replay_buffer = __esm({
5976
5060
  "../agent/src/replay-buffer.ts"() {
5977
5061
  "use strict";
5978
- ReplayBuffer = class {
5979
- buffer = [];
5980
- maxSize;
5981
- constructor(maxSize = 1e3) {
5982
- this.maxSize = maxSize;
5983
- }
5984
- /** Add an event, evicting the oldest if at capacity. */
5985
- push(event) {
5986
- if (this.buffer.length >= this.maxSize) {
5987
- this.buffer.shift();
5988
- }
5989
- this.buffer.push(event);
5990
- }
5991
- /** Return all events with seq > lastSeq, in order. */
5992
- getAfter(lastSeq) {
5993
- return this.buffer.filter((e) => e.seq > lastSeq);
5994
- }
5995
- /** Current number of buffered events. */
5996
- get size() {
5997
- return this.buffer.length;
5998
- }
5999
- /** Lowest seq still in the buffer, or 0 if empty. */
6000
- get oldestSeq() {
6001
- return this.buffer.length > 0 ? this.buffer[0].seq : 0;
6002
- }
6003
- /** Highest seq in the buffer, or 0 if empty. */
6004
- get newestSeq() {
6005
- return this.buffer.length > 0 ? this.buffer[this.buffer.length - 1].seq : 0;
6006
- }
6007
- /** Clear the buffer. */
6008
- clear() {
6009
- this.buffer = [];
6010
- }
6011
- };
6012
5062
  }
6013
5063
  });
6014
5064
 
6015
5065
  // ../agent/src/sandbox/lockfile-hash.ts
6016
- async function sha256(content) {
6017
- const data = new TextEncoder().encode(content);
6018
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
6019
- const hashArray = new Uint8Array(hashBuffer);
6020
- return Array.from(hashArray).map((b2) => b2.toString(16).padStart(2, "0")).join("");
6021
- }
6022
- async function computeLockfileHash(readFile10) {
6023
- for (const name of LOCKFILES) {
6024
- const content = await readFile10(name);
6025
- if (content) {
6026
- const hash = await sha256(content);
6027
- return hash.slice(0, 12);
6028
- }
6029
- }
6030
- return "none";
6031
- }
6032
- var LOCKFILES;
6033
5066
  var init_lockfile_hash = __esm({
6034
5067
  "../agent/src/sandbox/lockfile-hash.ts"() {
6035
5068
  "use strict";
6036
- LOCKFILES = ["package-lock.json", "yarn.lock", "pnpm-lock.yaml", "bun.lockb"];
6037
5069
  }
6038
5070
  });
6039
5071
 
@@ -6518,12 +5550,12 @@ function frontmatterDescription(text) {
6518
5550
  const line = m2[1].split("\n").find((l) => l.startsWith("description:"));
6519
5551
  return line ? line.slice("description:".length).trim().slice(0, 300) : "";
6520
5552
  }
6521
- async function readOptional(fs6, path12, report) {
5553
+ async function readOptional(fs6, path13, report) {
6522
5554
  try {
6523
- return await fs6.readFile(path12);
5555
+ return await fs6.readFile(path13);
6524
5556
  } catch (err) {
6525
5557
  if (isEnoent(err)) return null;
6526
- report.errors.push({ path: path12, message: err instanceof Error ? err.message : String(err) });
5558
+ report.errors.push({ path: path13, message: err instanceof Error ? err.message : String(err) });
6527
5559
  return null;
6528
5560
  }
6529
5561
  }
@@ -6554,17 +5586,17 @@ async function readClaudeAssets({ fs: fs6, home, projectDirs }) {
6554
5586
  report.notes.push(`Memory read from ${memDir} (namespaced as "${key}.*")`);
6555
5587
  const seen = /* @__PURE__ */ new Set();
6556
5588
  for (const entry of entries.filter((e) => e.endsWith(".md")).sort()) {
6557
- const path12 = `${memDir}/${entry}`;
6558
- const text = await readOptional(fs6, path12, report);
5589
+ const path13 = `${memDir}/${entry}`;
5590
+ const text = await readOptional(fs6, path13, report);
6559
5591
  if (text === null) continue;
6560
5592
  const base = entry === "MEMORY.md" ? "index" : entry.slice(0, -3).replace(/[^A-Za-z0-9._-]/g, "_");
6561
5593
  const name = `${key}.${base}`;
6562
5594
  if (!NAME_RE3.test(name)) {
6563
- report.errors.push({ path: path12, message: `cannot derive a valid asset name from "${entry}"` });
5595
+ report.errors.push({ path: path13, message: `cannot derive a valid asset name from "${entry}"` });
6564
5596
  continue;
6565
5597
  }
6566
5598
  if (seen.has(name)) {
6567
- report.errors.push({ path: path12, message: `name collision on "${name}"` });
5599
+ report.errors.push({ path: path13, message: `name collision on "${name}"` });
6568
5600
  continue;
6569
5601
  }
6570
5602
  seen.add(name);
@@ -6573,7 +5605,7 @@ async function readClaudeAssets({ fs: fs6, home, projectDirs }) {
6573
5605
  name,
6574
5606
  description: entry === "MEMORY.md" ? "Memory index" : frontmatterDescription(text),
6575
5607
  content: text,
6576
- source: path12
5608
+ source: path13
6577
5609
  });
6578
5610
  }
6579
5611
  return report;
@@ -6592,28 +5624,28 @@ async function readCommands(fs6, home, report) {
6592
5624
  }
6593
5625
  const seen = /* @__PURE__ */ new Set();
6594
5626
  for (const entry of [...entries].sort()) {
6595
- const path12 = `${dir}/${entry}`;
5627
+ const path13 = `${dir}/${entry}`;
6596
5628
  if (!entry.endsWith(".md")) {
6597
- if (!entry.startsWith(".")) report.notes.push(`Skipped ${path12}: only top-level .md command files are imported (subdirectories are not supported)`);
5629
+ if (!entry.startsWith(".")) report.notes.push(`Skipped ${path13}: only top-level .md command files are imported (subdirectories are not supported)`);
6598
5630
  continue;
6599
5631
  }
6600
5632
  const name = entry.slice(0, -3);
6601
5633
  if (!COMMAND_NAME_RE.test(name)) {
6602
- report.errors.push({ path: path12, message: `command name "${name}" must start with a letter and use only letters, digits, - and _` });
5634
+ report.errors.push({ path: path13, message: `command name "${name}" must start with a letter and use only letters, digits, - and _` });
6603
5635
  continue;
6604
5636
  }
6605
5637
  if (seen.has(name.toLowerCase())) {
6606
- report.errors.push({ path: path12, message: `command name collision on "${name}" (names are case-insensitive)` });
5638
+ report.errors.push({ path: path13, message: `command name collision on "${name}" (names are case-insensitive)` });
6607
5639
  continue;
6608
5640
  }
6609
- const text = await readOptional(fs6, path12, report);
5641
+ const text = await readOptional(fs6, path13, report);
6610
5642
  if (text === null) continue;
6611
5643
  if (Buffer.byteLength(text, "utf8") > MAX_ASSET_BYTES) {
6612
- report.errors.push({ path: path12, message: `skipped: ${Buffer.byteLength(text, "utf8")} bytes is over the ${MAX_ASSET_BYTES} byte asset limit` });
5644
+ report.errors.push({ path: path13, message: `skipped: ${Buffer.byteLength(text, "utf8")} bytes is over the ${MAX_ASSET_BYTES} byte asset limit` });
6613
5645
  continue;
6614
5646
  }
6615
5647
  seen.add(name.toLowerCase());
6616
- report.assets.push({ kind: "command", name, description: parseCommandFile(text).description, content: text, source: path12 });
5648
+ report.assets.push({ kind: "command", name, description: parseCommandFile(text).description, content: text, source: path13 });
6617
5649
  }
6618
5650
  }
6619
5651
  async function listFiles(fs6, dir, rel, out, report) {
@@ -6666,8 +5698,8 @@ async function readSkills(fs6, home, report) {
6666
5698
  const siblings = [];
6667
5699
  const slugs = /* @__PURE__ */ new Set();
6668
5700
  for (const rel of files.filter((f) => f !== "SKILL.md")) {
6669
- const path12 = `${root}/${dir}/${rel}`;
6670
- const content = await readOptional(fs6, path12, report);
5701
+ const path13 = `${root}/${dir}/${rel}`;
5702
+ const content = await readOptional(fs6, path13, report);
6671
5703
  if (content === null) continue;
6672
5704
  const size = Buffer.byteLength(content, "utf8");
6673
5705
  const name = siblingAssetName(parsed.name, rel);
@@ -6676,33 +5708,33 @@ async function readSkills(fs6, home, report) {
6676
5708
  continue;
6677
5709
  }
6678
5710
  slugs.add(name);
6679
- siblings.push({ kind: "skill", name, description: `${SIBLING_PREFIX}${rel}`, content, source: path12 });
5711
+ siblings.push({ kind: "skill", name, description: `${SIBLING_PREFIX}${rel}`, content, source: path13 });
6680
5712
  }
6681
5713
  if (siblings.some((s) => isScriptPath(s.description.slice(SIBLING_PREFIX.length)))) report.notes.push(`Skill ${parsed.name}: ships scripts, not runnable until permitted (they run through bash and its permission rules)`);
6682
5714
  report.assets.push({ kind: "skill", name: parsed.name, description: parsed.description, content: text, source: skillPath }, ...siblings);
6683
5715
  }
6684
5716
  }
6685
5717
  async function readMcpServers(fs6, home, projectDirs, report) {
6686
- const path12 = `${home}/.claude.json`;
6687
- const text = await readOptional(fs6, path12, report);
5718
+ const path13 = `${home}/.claude.json`;
5719
+ const text = await readOptional(fs6, path13, report);
6688
5720
  if (text === null) return;
6689
5721
  const { servers, errors } = normalizeClaudeJsonMcp(text, projectDirs);
6690
- for (const e of errors) report.errors.push({ path: path12, message: e });
5722
+ for (const e of errors) report.errors.push({ path: path13, message: e });
6691
5723
  const seen = /* @__PURE__ */ new Set();
6692
5724
  for (const s of servers) {
6693
5725
  if (!NAME_RE3.test(s.name)) {
6694
- report.errors.push({ path: path12, message: `MCP server name "${s.name}" must use only letters, digits, . _ - (max 100)` });
5726
+ report.errors.push({ path: path13, message: `MCP server name "${s.name}" must use only letters, digits, . _ - (max 100)` });
6695
5727
  continue;
6696
5728
  }
6697
5729
  if (seen.has(s.name.toLowerCase())) {
6698
- report.errors.push({ path: path12, message: `MCP server name collision on "${s.name}" (names are case-insensitive)` });
5730
+ report.errors.push({ path: path13, message: `MCP server name collision on "${s.name}" (names are case-insensitive)` });
6699
5731
  continue;
6700
5732
  }
6701
5733
  seen.add(s.name.toLowerCase());
6702
5734
  const { literalKeys, ...def } = s;
6703
5735
  const content = JSON.stringify(def, null, 2);
6704
5736
  if (Buffer.byteLength(content, "utf8") > MAX_ASSET_BYTES) {
6705
- report.errors.push({ path: path12, message: `MCP server "${s.name}" skipped: over the ${MAX_ASSET_BYTES} byte asset limit` });
5737
+ report.errors.push({ path: path13, message: `MCP server "${s.name}" skipped: over the ${MAX_ASSET_BYTES} byte asset limit` });
6706
5738
  continue;
6707
5739
  }
6708
5740
  if (literalKeys.length > 0) {
@@ -6716,7 +5748,7 @@ async function readMcpServers(fs6, home, projectDirs, report) {
6716
5748
  name: s.name,
6717
5749
  description: s.transport === "stdio" ? `stdio: ${s.command}` : `http: ${s.url}`,
6718
5750
  content,
6719
- source: `${path12} (${s.scope})`
5751
+ source: `${path13} (${s.scope})`
6720
5752
  });
6721
5753
  }
6722
5754
  }
@@ -6739,10 +5771,10 @@ var init_claude_sources = __esm({
6739
5771
  // ../agent/src/import/profile-client.ts
6740
5772
  function createProfileClient({ baseUrl, apiKey, fetch: doFetch, timeoutMs = 8e3 }) {
6741
5773
  const root = baseUrl.replace(/\/+$/, "");
6742
- async function call(method, path12, init = {}) {
5774
+ async function call(method, path13, init = {}) {
6743
5775
  let res;
6744
5776
  try {
6745
- res = await doFetch(`${root}/api/profile${path12}`, {
5777
+ res = await doFetch(`${root}/api/profile${path13}`, {
6746
5778
  method,
6747
5779
  headers: { Authorization: `Bearer ${apiKey}`, ...init.body !== void 0 ? { "content-type": "application/json" } : {}, ...init.headers },
6748
5780
  ...init.body !== void 0 ? { body: JSON.stringify(init.body) } : {},
@@ -6759,7 +5791,7 @@ function createProfileClient({ baseUrl, apiKey, fetch: doFetch, timeoutMs = 8e3
6759
5791
  body = void 0;
6760
5792
  }
6761
5793
  const detail = typeof body === "object" && body !== null && "error" in body ? String(body.error) : res.statusText;
6762
- throw new ProfileApiError(res.status, `profile ${method} ${path12} -> ${res.status} ${detail}`, body);
5794
+ throw new ProfileApiError(res.status, `profile ${method} ${path13} -> ${res.status} ${detail}`, body);
6763
5795
  }
6764
5796
  const enc = encodeURIComponent;
6765
5797
  return {
@@ -6874,7 +5906,7 @@ function createMemoryReadTool(client, projectKey2) {
6874
5906
  }
6875
5907
  };
6876
5908
  }
6877
- var MEMORY_READ_TOOL, MEMORY_TOOL_DEFINITIONS, SAFE;
5909
+ var MEMORY_READ_TOOL, SAFE;
6878
5910
  var init_memory_tool = __esm({
6879
5911
  "../agent/src/import/memory-tool.ts"() {
6880
5912
  "use strict";
@@ -6887,7 +5919,6 @@ var init_memory_tool = __esm({
6887
5919
  required: ["name"]
6888
5920
  }
6889
5921
  };
6890
- MEMORY_TOOL_DEFINITIONS = [MEMORY_READ_TOOL];
6891
5922
  SAFE = /^[A-Za-z0-9._-]{1,100}$/;
6892
5923
  }
6893
5924
  });
@@ -6969,9 +6000,6 @@ function splitSections(text) {
6969
6000
  const preamble = starts[0] === 0 ? "" : lines2.slice(0, starts[0]).join("\n") + "\n";
6970
6001
  return { preamble, sections: sections2 };
6971
6002
  }
6972
- function joinSections(split) {
6973
- return split.preamble + split.sections.map((s) => s.text).join("");
6974
- }
6975
6003
  var init_instruction_sections = __esm({
6976
6004
  "../agent/src/import/instruction-sections.ts"() {
6977
6005
  "use strict";
@@ -7140,7 +6168,7 @@ function createInstructionsReadTool(client) {
7140
6168
  }
7141
6169
  };
7142
6170
  }
7143
- var INSTRUCTIONS_READ_TOOL, INSTRUCTIONS_TOOL_DEFINITIONS, SAFE2;
6171
+ var INSTRUCTIONS_READ_TOOL, SAFE2;
7144
6172
  var init_instructions_tool = __esm({
7145
6173
  "../agent/src/import/instructions-tool.ts"() {
7146
6174
  "use strict";
@@ -7154,7 +6182,6 @@ var init_instructions_tool = __esm({
7154
6182
  required: ["name"]
7155
6183
  }
7156
6184
  };
7157
- INSTRUCTIONS_TOOL_DEFINITIONS = [INSTRUCTIONS_READ_TOOL];
7158
6185
  SAFE2 = /^[a-z0-9-]{1,40}$/;
7159
6186
  }
7160
6187
  });
@@ -7255,7 +6282,7 @@ var init_trust = __esm({
7255
6282
  // ../agent/src/import/source-changes.ts
7256
6283
  function fingerprintSources(files, hash) {
7257
6284
  const fingerprint = {};
7258
- for (const [path12, content] of Object.entries(files)) fingerprint[path12] = hash(content);
6285
+ for (const [path13, content] of Object.entries(files)) fingerprint[path13] = hash(content);
7259
6286
  return fingerprint;
7260
6287
  }
7261
6288
  function diffSources(before, after) {
@@ -7274,6 +6301,70 @@ var init_source_changes = __esm({
7274
6301
  }
7275
6302
  });
7276
6303
 
6304
+ // ../agent/src/import/mcp-tool-cache.ts
6305
+ function serverCacheKey(def, hash) {
6306
+ return hash(JSON.stringify([def.name, def.command, def.args, [...def.envNames].sort()]));
6307
+ }
6308
+ function cleanTool(raw) {
6309
+ if (!isPlainObject(raw) || typeof raw.name !== "string" || raw.name === "" || typeof raw.description !== "string" || !isPlainObject(raw.inputSchema)) return null;
6310
+ return { name: raw.name, description: raw.description.slice(0, MAX_DESCRIPTION), inputSchema: raw.inputSchema };
6311
+ }
6312
+ function parseToolCache(text) {
6313
+ if (text === null || text === "") return { cache: {} };
6314
+ let raw;
6315
+ try {
6316
+ raw = JSON.parse(text);
6317
+ } catch (err) {
6318
+ if (!(err instanceof SyntaxError)) throw err;
6319
+ return { cache: {}, error: "tool cache ignored: not valid JSON" };
6320
+ }
6321
+ if (!isPlainObject(raw) || raw.version !== 1 || !isPlainObject(raw.servers)) return { cache: {}, error: "tool cache ignored: unsupported format" };
6322
+ const cache4 = {};
6323
+ for (const key of Object.keys(raw.servers)) {
6324
+ if (Object.keys(cache4).length >= MAX_SERVERS) break;
6325
+ const entry = raw.servers[key];
6326
+ if (!isPlainObject(entry) || !Array.isArray(entry.tools)) continue;
6327
+ const tools = [];
6328
+ for (const t of entry.tools) {
6329
+ const clean = cleanTool(t);
6330
+ if (clean) tools.push(clean);
6331
+ if (tools.length >= MAX_TOOLS_PER_SERVER) break;
6332
+ }
6333
+ Object.defineProperty(cache4, key, { value: { tools }, enumerable: true, writable: true, configurable: true });
6334
+ }
6335
+ return { cache: cache4 };
6336
+ }
6337
+ function serializeToolCache(cache4) {
6338
+ return JSON.stringify({ version: 1, servers: cache4 });
6339
+ }
6340
+ function getCachedTools(cache4, key) {
6341
+ return own(cache4, key) ? cache4[key].tools : void 0;
6342
+ }
6343
+ function sameTools(a, b2) {
6344
+ return JSON.stringify(a) === JSON.stringify(b2);
6345
+ }
6346
+ function putCachedTools(cache4, key, tools) {
6347
+ const existing = getCachedTools(cache4, key);
6348
+ if (existing && sameTools(existing, tools)) return cache4;
6349
+ const next = {};
6350
+ for (const k2 of Object.keys(cache4)) if (k2 !== key) Object.defineProperty(next, k2, { value: cache4[k2], enumerable: true, writable: true, configurable: true });
6351
+ Object.defineProperty(next, key, { value: { tools }, enumerable: true, writable: true, configurable: true });
6352
+ const keys = Object.keys(next);
6353
+ for (const k2 of keys.slice(0, Math.max(0, keys.length - MAX_SERVERS))) delete next[k2];
6354
+ return next;
6355
+ }
6356
+ var MAX_TOOLS_PER_SERVER, MAX_SERVERS, MAX_DESCRIPTION, own, isPlainObject;
6357
+ var init_mcp_tool_cache = __esm({
6358
+ "../agent/src/import/mcp-tool-cache.ts"() {
6359
+ "use strict";
6360
+ MAX_TOOLS_PER_SERVER = 200;
6361
+ MAX_SERVERS = 500;
6362
+ MAX_DESCRIPTION = 1024;
6363
+ own = (o, k2) => Object.hasOwn(o, k2);
6364
+ isPlainObject = (v2) => typeof v2 === "object" && v2 !== null && !Array.isArray(v2);
6365
+ }
6366
+ });
6367
+
7277
6368
  // ../agent/src/import/imports-config.ts
7278
6369
  function allOffConfig() {
7279
6370
  return {
@@ -7289,23 +6380,23 @@ function allOffConfig() {
7289
6380
  models: {}
7290
6381
  };
7291
6382
  }
7292
- function isPlainObject(value) {
6383
+ function isPlainObject2(value) {
7293
6384
  return typeof value === "object" && value !== null && !Array.isArray(value);
7294
6385
  }
7295
- function parseImportsConfig(text, path12) {
6386
+ function parseImportsConfig(text, path13) {
7296
6387
  if (text === null) return { config: allOffConfig() };
7297
- const fail = (reason) => ({ config: allOffConfig(), error: `${path12}: ${reason}` });
6388
+ const fail = (reason) => ({ config: allOffConfig(), error: `${path13}: ${reason}` });
7298
6389
  let parsed;
7299
6390
  try {
7300
6391
  parsed = JSON.parse(text);
7301
6392
  } catch {
7302
6393
  return fail("invalid JSON");
7303
6394
  }
7304
- if (!isPlainObject(parsed)) return fail("top-level JSON must be an object");
6395
+ if (!isPlainObject2(parsed)) return fail("top-level JSON must be an object");
7305
6396
  if (parsed.version !== 1) return fail(`unsupported version ${String(parsed.version)}`);
7306
6397
  const config = allOffConfig();
7307
6398
  if (parsed.categories !== void 0) {
7308
- if (!isPlainObject(parsed.categories)) return fail("categories must be an object");
6399
+ if (!isPlainObject2(parsed.categories)) return fail("categories must be an object");
7309
6400
  for (const [key, value] of Object.entries(parsed.categories)) {
7310
6401
  if (!IMPORT_CATEGORIES.includes(key)) return fail(`unknown category key "${key}"`);
7311
6402
  if (typeof value !== "boolean") return fail(`category "${key}" must be a boolean`);
@@ -7313,7 +6404,7 @@ function parseImportsConfig(text, path12) {
7313
6404
  }
7314
6405
  }
7315
6406
  if (parsed.models !== void 0) {
7316
- if (!isPlainObject(parsed.models)) return fail("models must be an object");
6407
+ if (!isPlainObject2(parsed.models)) return fail("models must be an object");
7317
6408
  for (const key of MODEL_KEYS) {
7318
6409
  const value = parsed.models[key];
7319
6410
  if (value === void 0) continue;
@@ -7323,9 +6414,6 @@ function parseImportsConfig(text, path12) {
7323
6414
  }
7324
6415
  return { config };
7325
6416
  }
7326
- function serializeImportsConfig(config) {
7327
- return JSON.stringify({ version: 1, categories: config.categories, models: config.models }, null, 2);
7328
- }
7329
6417
  var IMPORT_CATEGORIES, MODEL_KEYS;
7330
6418
  var init_imports_config = __esm({
7331
6419
  "../agent/src/import/imports-config.ts"() {
@@ -7918,135 +7006,6 @@ var init_mcp_client = __esm({
7918
7006
  });
7919
7007
 
7920
7008
  // ../agent/src/index.ts
7921
- var src_exports = {};
7922
- __export(src_exports, {
7923
- AdaptPlanError: () => AdaptPlanError,
7924
- BASH_TOOL_DEFINITION: () => BASH_TOOL_DEFINITION,
7925
- BUILD_FEATURE_TOOL: () => BUILD_FEATURE_TOOL,
7926
- CALL_TIMEOUT_MS: () => CALL_TIMEOUT_MS,
7927
- CODEGEN_TOOL_DEFINITIONS: () => CODEGEN_TOOL_DEFINITIONS,
7928
- CONNECT_TIMEOUT_MS: () => CONNECT_TIMEOUT_MS,
7929
- CREATE_PR_TOOL: () => CREATE_PR_TOOL,
7930
- CloudHost: () => CloudHost,
7931
- DEFAULT_MAX_CONTEXT_TOKENS: () => DEFAULT_MAX_CONTEXT_TOKENS,
7932
- DEFAULT_PROFILE_BUDGET_CHARS: () => DEFAULT_PROFILE_BUDGET_CHARS,
7933
- EDIT_FILE_TOOL: () => EDIT_FILE_TOOL,
7934
- FILE_TOOL_DEFINITIONS: () => FILE_TOOL_DEFINITIONS,
7935
- FileAuditLog: () => FileAuditLog,
7936
- FileStore: () => FileStore,
7937
- GITHUB_TOOL_DEFINITIONS: () => GITHUB_TOOL_DEFINITIONS,
7938
- IMPORT_CATEGORIES: () => IMPORT_CATEGORIES,
7939
- INSTRUCTIONS_READ_TOOL: () => INSTRUCTIONS_READ_TOOL,
7940
- INSTRUCTIONS_TOOL_DEFINITIONS: () => INSTRUCTIONS_TOOL_DEFINITIONS,
7941
- LIST_DIRECTORY_TOOL: () => LIST_DIRECTORY_TOOL,
7942
- LocalHost: () => LocalHost,
7943
- MAX_ASSET_BYTES: () => MAX_ASSET_BYTES,
7944
- MAX_CORE_CHARS: () => MAX_CORE_CHARS,
7945
- MCP_PROTOCOL_VERSION: () => MCP_PROTOCOL_VERSION,
7946
- MEMORY_READ_TOOL: () => MEMORY_READ_TOOL,
7947
- MEMORY_TOOL_DEFINITIONS: () => MEMORY_TOOL_DEFINITIONS,
7948
- McpProtocolError: () => McpProtocolError,
7949
- MemoryAuditLog: () => MemoryAuditLog,
7950
- PERMISSION_MODES: () => PERMISSION_MODES,
7951
- PREVIEW_TOOL: () => PREVIEW_TOOL,
7952
- PREVIEW_TOOL_DEFINITIONS: () => PREVIEW_TOOL_DEFINITIONS,
7953
- PendingRequests: () => PendingRequests,
7954
- PermissionState: () => PermissionState,
7955
- PolicyViolation: () => PolicyViolation,
7956
- ProfileApiError: () => ProfileApiError,
7957
- READ_FILE_TOOL: () => READ_FILE_TOOL,
7958
- RESULT_CAP_CHARS: () => RESULT_CAP_CHARS,
7959
- ReplayBuffer: () => ReplayBuffer,
7960
- RepoIndexer: () => RepoIndexer,
7961
- RpcAbortError: () => RpcAbortError,
7962
- RpcClosedError: () => RpcClosedError,
7963
- RpcError: () => RpcError,
7964
- RpcTimeoutError: () => RpcTimeoutError,
7965
- SEARCH_TOOL: () => SEARCH_TOOL,
7966
- SENTRY_GET_ISSUE_TOOL: () => SENTRY_GET_ISSUE_TOOL,
7967
- SENTRY_LIST_ISSUES_TOOL: () => SENTRY_LIST_ISSUES_TOOL,
7968
- SENTRY_TOOL_DEFINITIONS: () => SENTRY_TOOL_DEFINITIONS,
7969
- SIBLING_PREFIX: () => SIBLING_PREFIX,
7970
- SKILLS_BUDGET_CHARS: () => SKILLS_BUDGET_CHARS,
7971
- SKILL_TOOL: () => SKILL_TOOL,
7972
- SnapshotManager: () => SnapshotManager,
7973
- TODO_TOOL: () => TODO_TOOL,
7974
- TODO_TOOL_DEFINITIONS: () => TODO_TOOL_DEFINITIONS,
7975
- TOOL_NAME_MAP: () => TOOL_NAME_MAP,
7976
- TrustError: () => TrustError,
7977
- TrustStore: () => TrustStore,
7978
- WRITE_FILE_TOOL: () => WRITE_FILE_TOOL,
7979
- allOffConfig: () => allOffConfig,
7980
- applyPlan: () => applyPlan,
7981
- buildCommandRegistry: () => buildCommandRegistry,
7982
- buildProfileSection: () => buildProfileSection,
7983
- buildRuleSet: () => buildRuleSet,
7984
- buildSkillsSection: () => buildSkillsSection,
7985
- buildSystemPrompt: () => buildSystemPrompt,
7986
- buildTranscriptForSummarization: () => buildTranscriptForSummarization,
7987
- checkCoverage: () => checkCoverage,
7988
- classifyCommand: () => classifyCommand,
7989
- classifyRpcMessage: () => classifyRpcMessage,
7990
- claudeProjectSlug: () => claudeProjectSlug,
7991
- computeLockfileHash: () => computeLockfileHash,
7992
- createBashTool: () => createBashTool,
7993
- createCodegenTools: () => createCodegenTools,
7994
- createFileTools: () => createFileTools,
7995
- createGitHubTools: () => createGitHubTools,
7996
- createHostSearchTool: () => createHostSearchTool,
7997
- createHostToolExecutor: () => createHostToolExecutor,
7998
- createInstructionsReadTool: () => createInstructionsReadTool,
7999
- createMcpClient: () => createMcpClient,
8000
- createMemoryReadTool: () => createMemoryReadTool,
8001
- createPreviewTool: () => createPreviewTool,
8002
- createProfileClient: () => createProfileClient,
8003
- createProfileSkillSource: () => createProfileSkillSource,
8004
- createRpcSession: () => createRpcSession,
8005
- createSearchTool: () => createSearchTool,
8006
- createSentryTools: () => createSentryTools,
8007
- createSimpleRouter: () => createSimpleRouter,
8008
- createSkillTool: () => createSkillTool,
8009
- createTodoTool: () => createTodoTool,
8010
- defaultModeFor: () => defaultModeFor,
8011
- diffSources: () => diffSources,
8012
- estimateTokens: () => estimateTokens,
8013
- estimateTotalTokens: () => estimateTotalTokens,
8014
- expandCommand: () => expandCommand,
8015
- expandRefs: () => expandRefs,
8016
- expandServerEnv: () => expandServerEnv,
8017
- extractRuleLines: () => extractRuleLines,
8018
- findUnsupported: () => findUnsupported,
8019
- fingerprintSources: () => fingerprintSources,
8020
- flattenMessageText: () => flattenMessageText,
8021
- formatSkillBody: () => formatSkillBody,
8022
- formatSourceChanges: () => formatSourceChanges,
8023
- formatToolResult: () => formatToolResult,
8024
- guardHost: () => guardHost,
8025
- isReadOnlyCommand: () => isReadOnlyCommand,
8026
- joinSections: () => joinSections,
8027
- listMcpSpawns: () => listMcpSpawns,
8028
- loadProfile: () => loadProfile,
8029
- normalizeClaudeJsonMcp: () => normalizeClaudeJsonMcp,
8030
- parseCommandFile: () => parseCommandFile,
8031
- parseGitStatus: () => parseGitStatus,
8032
- parseImportsConfig: () => parseImportsConfig,
8033
- parseSkillFile: () => parseSkillFile,
8034
- parseTodos: () => parseTodos,
8035
- prepareAdaptation: () => prepareAdaptation,
8036
- projectKey: () => projectKey,
8037
- readClaudeAssets: () => readClaudeAssets,
8038
- renderProjectCard: () => renderProjectCard,
8039
- runAgentLoop: () => runAgentLoop,
8040
- safeSkillPath: () => safeSkillPath,
8041
- serializeImportsConfig: () => serializeImportsConfig,
8042
- siblingAssetName: () => siblingAssetName,
8043
- splitCommand: () => splitCommand,
8044
- splitSections: () => splitSections,
8045
- summarizeConversation: () => summarizeConversation,
8046
- syncAfterBash: () => syncAfterBash,
8047
- syncBeforeBash: () => syncBeforeBash,
8048
- translateToolNames: () => translateToolNames
8049
- });
8050
7009
  var init_src = __esm({
8051
7010
  "../agent/src/index.ts"() {
8052
7011
  init_loop();
@@ -8086,6 +7045,7 @@ var init_src = __esm({
8086
7045
  init_instructions_tool();
8087
7046
  init_trust();
8088
7047
  init_source_changes();
7048
+ init_mcp_tool_cache();
8089
7049
  init_imports_config();
8090
7050
  init_permission_rules();
8091
7051
  init_command_args();
@@ -8108,14 +7068,9 @@ var init_src = __esm({
8108
7068
  });
8109
7069
 
8110
7070
  // src/api-key.ts
8111
- var api_key_exports = {};
8112
- __export(api_key_exports, {
8113
- MISSING_KEY_MESSAGE: () => MISSING_KEY_MESSAGE,
8114
- resolveApiKey: () => resolveApiKey
8115
- });
8116
7071
  import { join } from "node:path";
8117
7072
  async function resolveApiKey(deps) {
8118
- const { env: env3, readFile: readFile10, homedir: homedir3 } = deps;
7073
+ const { env: env3, readFile: readFile12, homedir: homedir3 } = deps;
8119
7074
  for (const name of ["SPACECODE_API_KEY", "ANTHROPIC_API_KEY"]) {
8120
7075
  const value = env3[name]?.trim();
8121
7076
  if (value) return value;
@@ -8124,7 +7079,7 @@ async function resolveApiKey(deps) {
8124
7079
  const file = join(dir, "auth.json");
8125
7080
  let raw;
8126
7081
  try {
8127
- raw = await readFile10(file);
7082
+ raw = await readFile12(file);
8128
7083
  } catch (err) {
8129
7084
  if (err.code === "ENOENT") return null;
8130
7085
  throw new Error(`Cannot read ${file}: ${err instanceof Error ? err.message : String(err)}`);
@@ -8157,26 +7112,26 @@ __export(import_gate_exports, {
8157
7112
  importGateStatus: () => importGateStatus,
8158
7113
  nodeTrustFs: () => nodeTrustFs,
8159
7114
  readRepoSources: () => readRepoSources,
8160
- sha256: () => sha2562,
7115
+ sha256: () => sha256,
8161
7116
  writeTextAtomic: () => writeTextAtomic
8162
7117
  });
8163
7118
  import { createHash, randomUUID } from "node:crypto";
8164
7119
  import { chmod, mkdir, readFile, readdir, realpath, rename, writeFile } from "node:fs/promises";
8165
7120
  import { dirname as dirname3, join as join2 } from "node:path";
8166
- async function readOrNull(path12) {
7121
+ async function readOrNull(path13) {
8167
7122
  try {
8168
- return await readFile(path12, "utf8");
7123
+ return await readFile(path13, "utf8");
8169
7124
  } catch (err) {
8170
7125
  if (err.code === "ENOENT") return null;
8171
7126
  throw err;
8172
7127
  }
8173
7128
  }
8174
- async function writeTextAtomic(path12, text) {
8175
- await mkdir(dirname3(path12), { recursive: true });
8176
- const tmp = `${path12}.${process.pid}.${randomUUID()}.tmp`;
7129
+ async function writeTextAtomic(path13, text) {
7130
+ await mkdir(dirname3(path13), { recursive: true });
7131
+ const tmp = `${path13}.${process.pid}.${randomUUID()}.tmp`;
8177
7132
  await writeFile(tmp, text, { mode: 384 });
8178
7133
  await chmod(tmp, 384);
8179
- await rename(tmp, path12);
7134
+ await rename(tmp, path13);
8180
7135
  }
8181
7136
  async function readRepoSources(cwd2) {
8182
7137
  const files = {};
@@ -8230,7 +7185,7 @@ function nodeTrustFs() {
8230
7185
  function createNodeTrustStore(configDir) {
8231
7186
  let store = trustStores.get(configDir);
8232
7187
  if (!store) {
8233
- store = new TrustStore({ fs: nodeTrustFs(), path: join2(configDir, "trust.json"), hash: sha2562 });
7188
+ store = new TrustStore({ fs: nodeTrustFs(), path: join2(configDir, "trust.json"), hash: sha256 });
8234
7189
  trustStores.set(configDir, store);
8235
7190
  }
8236
7191
  return store;
@@ -8262,7 +7217,7 @@ async function importGateStatus(deps) {
8262
7217
  }
8263
7218
  return lines2;
8264
7219
  }
8265
- var REPO_SOURCE_FILES, MAX_REPO_SKILL_FILES, MAX_REPO_SKILL_BYTES, sha2562, trustStores;
7220
+ var REPO_SOURCE_FILES, MAX_REPO_SKILL_FILES, MAX_REPO_SKILL_BYTES, sha256, trustStores;
8266
7221
  var init_import_gate = __esm({
8267
7222
  "src/import-gate.ts"() {
8268
7223
  "use strict";
@@ -8270,7 +7225,7 @@ var init_import_gate = __esm({
8270
7225
  REPO_SOURCE_FILES = [".mcp.json", ".claude/settings.json", ".claude/settings.local.json", "CLAUDE.local.md"];
8271
7226
  MAX_REPO_SKILL_FILES = 200;
8272
7227
  MAX_REPO_SKILL_BYTES = 256 * 1024;
8273
- sha2562 = (text) => createHash("sha256").update(text).digest("hex");
7228
+ sha256 = (text) => createHash("sha256").update(text).digest("hex");
8274
7229
  trustStores = /* @__PURE__ */ new Map();
8275
7230
  }
8276
7231
  });
@@ -8456,12 +7411,6 @@ var init_mcp_loader = __esm({
8456
7411
  });
8457
7412
 
8458
7413
  // src/profile-runtime.ts
8459
- var profile_runtime_exports = {};
8460
- __export(profile_runtime_exports, {
8461
- DEFAULT_BACKEND_URL: () => DEFAULT_BACKEND_URL,
8462
- backendUrl: () => backendUrl,
8463
- prepareProfile: () => prepareProfile
8464
- });
8465
7414
  import { join as join5 } from "node:path";
8466
7415
  function backendUrl(env3) {
8467
7416
  return env3.SPACECODE_BACKEND_URL?.trim() || env3.SPACECODE_API_URL?.trim() || DEFAULT_BACKEND_URL;
@@ -8904,6 +7853,14 @@ var init_mcp_manager = __esm({
8904
7853
  servers = /* @__PURE__ */ new Map();
8905
7854
  turnSignal;
8906
7855
  startPromise = null;
7856
+ primePromise = null;
7857
+ cache = {};
7858
+ /** Cached tools of approved, startable servers, usable until the live list replaces them. */
7859
+ cachedTools = /* @__PURE__ */ new Map();
7860
+ /** Settles when a server has connected or failed, so a call on a cached tool can wait for it. */
7861
+ connected = /* @__PURE__ */ new Map();
7862
+ cacheNotes = [];
7863
+ cacheWrite = Promise.resolve();
8907
7864
  /** Server states in definition order, so output does not depend on which server connected first. */
8908
7865
  ordered() {
8909
7866
  return this.deps.defs.map((d) => this.servers.get(d.name)).filter((s) => s !== void 0);
@@ -8912,11 +7869,95 @@ var init_mcp_manager = __esm({
8912
7869
  beginTurn(signal) {
8913
7870
  this.turnSignal = signal;
8914
7871
  }
8915
- /** Starts every eligible server in the background. Resolves when all have connected or failed; callers need not await it. */
7872
+ /**
7873
+ * Loads cached tool lists for servers that could start right now (stdio, approved on this machine, environment set),
7874
+ * without spawning anything. Await it before building the first toolset. Never throws; a bad cache is noted in /status.
7875
+ */
7876
+ prime() {
7877
+ this.primePromise ??= this.primeOnce();
7878
+ return this.primePromise;
7879
+ }
7880
+ async primeOnce() {
7881
+ const tc = this.deps.toolCache;
7882
+ if (!tc) return;
7883
+ try {
7884
+ const parsed = parseToolCache(await tc.read());
7885
+ this.cache = parsed.cache;
7886
+ if (parsed.error) this.cacheNotes.push(parsed.error);
7887
+ } catch (err) {
7888
+ this.cacheNotes.push(`tool cache not read (${err instanceof Error ? err.message : String(err)})`);
7889
+ return;
7890
+ }
7891
+ for (const def of this.deps.defs) {
7892
+ try {
7893
+ if (def.transport !== "stdio") continue;
7894
+ const envNames = approvalEnv(def.env);
7895
+ if (!await this.deps.isStdioApproved({ command: def.command, args: def.args, envNames })) continue;
7896
+ if (!expandServerEnv({ command: def.command, args: def.args, env: def.env }, this.deps.env).ok) continue;
7897
+ const hit = getCachedTools(this.cache, serverCacheKey({ name: def.name, command: def.command, args: def.args, envNames }, tc.hash));
7898
+ if (hit) this.cachedTools.set(def.name, this.usable(def.name, hit).tools);
7899
+ } catch (err) {
7900
+ this.cacheNotes.push(`${def.name}: cached tools skipped (${err instanceof Error ? err.message : String(err)})`);
7901
+ }
7902
+ }
7903
+ }
7904
+ /**
7905
+ * Starts servers in the background. A server whose tools came from the cache is NOT started here: it starts on its first call
7906
+ * (`begin`), so an unused server never spawns. Every other server starts now, because its tools (or why it is skipped) are unknown.
7907
+ * Resolves when those have connected or failed; callers need not await it.
7908
+ */
8916
7909
  start() {
8917
- this.startPromise ??= Promise.all(this.deps.defs.map((def) => this.startOne(def))).then(() => void 0);
7910
+ this.startPromise ??= Promise.all(this.deps.defs.filter((def) => !this.cachedTools.has(def.name)).map((def) => this.begin(def))).then(() => void 0);
8918
7911
  return this.startPromise;
8919
7912
  }
7913
+ /** Starts one server once; later callers share the same attempt. */
7914
+ begin(def) {
7915
+ let p = this.connected.get(def.name);
7916
+ if (!p) {
7917
+ p = this.startOne(def);
7918
+ this.connected.set(def.name, p);
7919
+ }
7920
+ return p;
7921
+ }
7922
+ /** Tools whose full names are valid and unique, plus the ones that are not. */
7923
+ usable(server, listed) {
7924
+ const tools = [];
7925
+ const skipped = [];
7926
+ const seen = /* @__PURE__ */ new Set();
7927
+ for (const tool of listed) {
7928
+ const full = mcpToolName(server, tool.name);
7929
+ if (!TOOL_NAME_RE.test(full) || seen.has(full)) skipped.push(tool.name);
7930
+ else {
7931
+ seen.add(full);
7932
+ tools.push(tool);
7933
+ }
7934
+ }
7935
+ return { tools, skipped };
7936
+ }
7937
+ /** Live tools once connected; cached ones while a server is still connecting (or not started yet). */
7938
+ toolsOf(def) {
7939
+ const s = this.servers.get(def.name);
7940
+ if (s?.status === "ready") return s.tools;
7941
+ if (s === void 0 || s.status === "connecting") return this.cachedTools.get(def.name) ?? [];
7942
+ return [];
7943
+ }
7944
+ /** Stores the live list for the next start. Writes are serialized; a failure becomes a /status line. */
7945
+ saveToolCache(def, tools) {
7946
+ const tc = this.deps.toolCache;
7947
+ if (!tc) return Promise.resolve();
7948
+ this.cacheWrite = this.cacheWrite.then(async () => {
7949
+ try {
7950
+ const key = serverCacheKey({ name: def.name, command: def.command, args: def.args, envNames: approvalEnv(def.env) }, tc.hash);
7951
+ const next = putCachedTools(this.cache, key, tools.map((t) => ({ name: t.name, description: t.description, inputSchema: t.inputSchema })));
7952
+ if (next === this.cache) return;
7953
+ await tc.write(serializeToolCache(next));
7954
+ this.cache = next;
7955
+ } catch (err) {
7956
+ this.cacheNotes.push(`${def.name}: tool list not cached (${err instanceof Error ? err.message : String(err)})`);
7957
+ }
7958
+ });
7959
+ return this.cacheWrite;
7960
+ }
8920
7961
  async startOne(def) {
8921
7962
  const name = def.name;
8922
7963
  const skip = (reason) => void this.servers.set(name, { name, def, status: "skipped", reason });
@@ -8967,19 +8008,11 @@ var init_mcp_manager = __esm({
8967
8008
  });
8968
8009
  await client.initialize();
8969
8010
  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
- }
8011
+ const { tools, skipped: skippedTools } = this.usable(name, listed.tools);
8981
8012
  if (listed.skipped > 0) skippedTools.push(`${listed.skipped} unnamed`);
8982
8013
  this.servers.set(name, { name, def, status: "ready", client, transport, session: session2, tools, skippedTools });
8014
+ this.cachedTools.delete(name);
8015
+ await this.saveToolCache(def, tools);
8983
8016
  } catch (err) {
8984
8017
  await transport?.close().catch(() => void 0);
8985
8018
  fail(err instanceof Error ? err.message : String(err));
@@ -8988,22 +8021,30 @@ var init_mcp_manager = __esm({
8988
8021
  /** Tools of every connected server, as the model sees them. */
8989
8022
  tools() {
8990
8023
  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 });
8024
+ for (const def of this.deps.defs) {
8025
+ for (const t of this.toolsOf(def)) {
8026
+ out.push({ name: mcpToolName(def.name, t.name), description: `[MCP server ${def.name}] ${t.description}`.slice(0, 1024), input_schema: t.inputSchema });
8995
8027
  }
8996
8028
  }
8997
8029
  return out;
8998
8030
  }
8999
8031
  handlers() {
9000
8032
  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);
8033
+ for (const def of this.deps.defs) {
8034
+ for (const t of this.toolsOf(def)) out[mcpToolName(def.name, t.name)] = (input) => this.callWhenReady(def.name, t.name, input);
9004
8035
  }
9005
8036
  return out;
9006
8037
  }
8038
+ /** A call on a cached tool starts its server if it is not running yet, waits for it to connect (bounded by the connect timeout) and then goes through the normal checks. */
8039
+ async callWhenReady(server, tool, input) {
8040
+ const def = this.deps.defs.find((d) => d.name === server);
8041
+ if (def) await this.begin(def);
8042
+ const state = this.servers.get(server);
8043
+ if (state?.status === "ready" && !state.tools.some((t) => t.name === tool)) {
8044
+ return { output: `MCP server "${server}" no longer offers ${tool}; its tool list changed since it was cached.`, is_error: true };
8045
+ }
8046
+ return this.call(server, tool, input);
8047
+ }
9007
8048
  async call(server, tool, input) {
9008
8049
  const full = mcpToolName(server, tool);
9009
8050
  const state = this.servers.get(server);
@@ -9045,11 +8086,18 @@ var init_mcp_manager = __esm({
9045
8086
  /** One line per server for /status; reasons never include env values or stderr. */
9046
8087
  statusLines() {
9047
8088
  const lines2 = [];
9048
- for (const s of this.ordered()) {
8089
+ for (const def of this.deps.defs) {
8090
+ const s = this.servers.get(def.name);
8091
+ if (s === void 0) {
8092
+ const cached3 = this.cachedTools.get(def.name);
8093
+ if (cached3) lines2.push(`mcp: ${def.name} not started, ${cached3.length} tools from cache (starts on first use)`);
8094
+ continue;
8095
+ }
9049
8096
  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...`);
8097
+ 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
8098
  else lines2.push(`mcp: ${s.name} NOT available (${s.reason})`);
9052
8099
  }
8100
+ for (const n of this.cacheNotes) lines2.push(`mcp: ${n}`);
9053
8101
  return lines2;
9054
8102
  }
9055
8103
  /** For /tools: names grouped by server. */
@@ -9066,13 +8114,9 @@ var init_mcp_manager = __esm({
9066
8114
  });
9067
8115
 
9068
8116
  // src/mcp/approve-mcp.ts
9069
- var approve_mcp_exports = {};
9070
- __export(approve_mcp_exports, {
9071
- runApproveMcp: () => runApproveMcp
9072
- });
9073
8117
  async function runApproveMcp(deps) {
9074
8118
  const { defs, statusLines } = await loadMcpDefs(deps);
9075
- for (const l of statusLines) deps.log(l);
8119
+ if (!deps.quiet) for (const l of statusLines) deps.log(l);
9076
8120
  const stdio = defs.filter((d) => d.transport === "stdio");
9077
8121
  const todo = [];
9078
8122
  for (const d of stdio) {
@@ -9080,13 +8124,15 @@ async function runApproveMcp(deps) {
9080
8124
  if (await deps.trust.stdioStatus(key) === "unapproved") todo.push(d);
9081
8125
  }
9082
8126
  if (todo.length === 0) {
9083
- deps.log(stdio.length === 0 ? "No stdio MCP servers to approve." : "Every stdio MCP server is already approved on this machine.");
8127
+ if (!deps.quiet) deps.log(stdio.length === 0 ? "No stdio MCP servers to approve." : "Every stdio MCP server is already approved on this machine.");
9084
8128
  return 0;
9085
8129
  }
9086
8130
  if (!deps.ask) {
9087
8131
  deps.log(`${todo.length} MCP server(s) need approval, but this is not an interactive terminal: nothing was approved.`);
9088
8132
  return 1;
9089
8133
  }
8134
+ if (deps.quiet) deps.log(`
8135
+ ${todo.length} imported MCP server(s) want to run a command on this machine. Nothing spawns until you approve it (you can also run \`spacecode approve-mcp\` later).`);
9090
8136
  let approved = 0;
9091
8137
  for (const d of todo) {
9092
8138
  deps.log(`
@@ -9114,19 +8160,65 @@ var init_approve_mcp = __esm({
9114
8160
  }
9115
8161
  });
9116
8162
 
8163
+ // src/mcp/approve-mcp-node.ts
8164
+ var approve_mcp_node_exports = {};
8165
+ __export(approve_mcp_node_exports, {
8166
+ runApproveMcpOnNode: () => runApproveMcpOnNode
8167
+ });
8168
+ import os from "node:os";
8169
+ import path3 from "node:path";
8170
+ import { readFile as readFile2 } from "node:fs/promises";
8171
+ async function runApproveMcpOnNode(opts) {
8172
+ const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() || path3.join(os.homedir(), ".spacecode");
8173
+ const apiKey = await resolveApiKey({ env: process.env, readFile: (p) => readFile2(p, "utf-8"), homedir: os.homedir }).catch(() => null);
8174
+ const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
8175
+ const { createInterface } = interactive ? await import("node:readline/promises") : { createInterface: void 0 };
8176
+ const rl = createInterface ? createInterface({ input: process.stdin, output: process.stdout }) : void 0;
8177
+ try {
8178
+ return await runApproveMcp({
8179
+ configDir,
8180
+ cwd: process.cwd(),
8181
+ readFile: (p) => readFile2(p, "utf-8"),
8182
+ client: apiKey ? createProfileClient({ baseUrl: backendUrl(process.env), apiKey, fetch }) : null,
8183
+ trust: createNodeTrustStore(configDir),
8184
+ ...rl ? { ask: async (q2) => {
8185
+ try {
8186
+ return await rl.question(q2);
8187
+ } catch {
8188
+ return null;
8189
+ }
8190
+ } } : {},
8191
+ log: (line) => console.log(line),
8192
+ quiet: opts.quiet
8193
+ });
8194
+ } finally {
8195
+ rl?.close();
8196
+ }
8197
+ }
8198
+ var init_approve_mcp_node = __esm({
8199
+ "src/mcp/approve-mcp-node.ts"() {
8200
+ "use strict";
8201
+ init_src();
8202
+ init_approve_mcp();
8203
+ init_import_gate();
8204
+ init_api_key();
8205
+ init_profile_runtime();
8206
+ }
8207
+ });
8208
+
9117
8209
  // src/repo-trust-prompt.ts
9118
8210
  var repo_trust_prompt_exports = {};
9119
8211
  __export(repo_trust_prompt_exports, {
9120
8212
  describeRepoSources: () => describeRepoSources,
9121
8213
  promptRepoTrust: () => promptRepoTrust
9122
8214
  });
9123
- function ruleLists(path12, text, lines2, warnings) {
8215
+ function ruleLists(path13, text, lines2, warnings) {
9124
8216
  let data;
9125
8217
  try {
9126
8218
  data = JSON.parse(text);
9127
8219
  } catch (err) {
9128
8220
  if (!(err instanceof SyntaxError)) throw err;
9129
- warnings.push(`${path12}: not valid JSON, nothing from it loads`);
8221
+ warnings.push(`${path13}: not valid JSON, nothing from it loads`);
9130
8222
  return;
9131
8223
  }
9132
8224
  const perms = typeof data === "object" && data !== null ? data.permissions : void 0;
@@ -9134,7 +8226,7 @@ function ruleLists(path12, text, lines2, warnings) {
9134
8226
  for (const effect of ["allow", "deny", "ask"]) {
9135
8227
  const list2 = perms[effect];
9136
8228
  if (!Array.isArray(list2)) continue;
9137
- for (const rule of list2) if (typeof rule === "string") lines2.push(` ${effect}: ${show2(rule)} (${path12})`);
8229
+ for (const rule of list2) if (typeof rule === "string") lines2.push(` ${effect}: ${show2(rule)} (${path13})`);
9138
8230
  }
9139
8231
  }
9140
8232
  function describeRepoSources(files) {
@@ -9380,7 +8472,7 @@ __export(base_exports, {
9380
8472
  synchronizedOutput: () => synchronizedOutput
9381
8473
  });
9382
8474
  import process2 from "node:process";
9383
- import os from "node:os";
8475
+ import os2 from "node:os";
9384
8476
  var ESC, OSC, BEL, SEP, isTerminalApp, isWindows2, isTmux, cwdFunction, wrapOsc, cursorTo, cursorMove, cursorUp, cursorDown, cursorForward, cursorBackward, cursorLeft, cursorSavePosition, cursorRestorePosition, cursorGetPosition, cursorNextLine, cursorPrevLine, cursorHide, cursorShow, eraseLines, eraseEndLine, eraseStartLine, eraseLine, eraseDown, eraseUp, eraseScreen, scrollUp, scrollDown, clearScreen, clearViewport, isOldWindows, clearTerminal, enterAlternativeScreen, exitAlternativeScreen, beginSynchronizedOutput, endSynchronizedOutput, synchronizedOutput, beep, link, image, iTerm, ConEmu, setCwd;
9385
8477
  var init_base = __esm({
9386
8478
  "../../node_modules/ansi-escapes/base.js"() {
@@ -9463,7 +8555,7 @@ var init_base = __esm({
9463
8555
  if (isBrowser || !isWindows2) {
9464
8556
  return false;
9465
8557
  }
9466
- const parts = os.release().split(".");
8558
+ const parts = os2.release().split(".");
9467
8559
  const major = Number(parts[0]);
9468
8560
  const build2 = Number(parts[2] ?? 0);
9469
8561
  if (major < 10) {
@@ -31743,10 +30835,10 @@ var require_react_reconciler_development = __commonJS({
31743
30835
  var setErrorHandler = null;
31744
30836
  var setSuspenseHandler = null;
31745
30837
  {
31746
- var copyWithDeleteImpl = function(obj, path12, index2) {
31747
- var key = path12[index2];
30838
+ var copyWithDeleteImpl = function(obj, path13, index2) {
30839
+ var key = path13[index2];
31748
30840
  var updated = isArray(obj) ? obj.slice() : assign({}, obj);
31749
- if (index2 + 1 === path12.length) {
30841
+ if (index2 + 1 === path13.length) {
31750
30842
  if (isArray(updated)) {
31751
30843
  updated.splice(key, 1);
31752
30844
  } else {
@@ -31754,11 +30846,11 @@ var require_react_reconciler_development = __commonJS({
31754
30846
  }
31755
30847
  return updated;
31756
30848
  }
31757
- updated[key] = copyWithDeleteImpl(obj[key], path12, index2 + 1);
30849
+ updated[key] = copyWithDeleteImpl(obj[key], path13, index2 + 1);
31758
30850
  return updated;
31759
30851
  };
31760
- var copyWithDelete = function(obj, path12) {
31761
- return copyWithDeleteImpl(obj, path12, 0);
30852
+ var copyWithDelete = function(obj, path13) {
30853
+ return copyWithDeleteImpl(obj, path13, 0);
31762
30854
  };
31763
30855
  var copyWithRenameImpl = function(obj, oldPath, newPath, index2) {
31764
30856
  var oldKey = oldPath[index2];
@@ -31796,17 +30888,17 @@ var require_react_reconciler_development = __commonJS({
31796
30888
  }
31797
30889
  return copyWithRenameImpl(obj, oldPath, newPath, 0);
31798
30890
  };
31799
- var copyWithSetImpl = function(obj, path12, index2, value) {
31800
- if (index2 >= path12.length) {
30891
+ var copyWithSetImpl = function(obj, path13, index2, value) {
30892
+ if (index2 >= path13.length) {
31801
30893
  return value;
31802
30894
  }
31803
- var key = path12[index2];
30895
+ var key = path13[index2];
31804
30896
  var updated = isArray(obj) ? obj.slice() : assign({}, obj);
31805
- updated[key] = copyWithSetImpl(obj[key], path12, index2 + 1, value);
30897
+ updated[key] = copyWithSetImpl(obj[key], path13, index2 + 1, value);
31806
30898
  return updated;
31807
30899
  };
31808
- var copyWithSet = function(obj, path12, value) {
31809
- return copyWithSetImpl(obj, path12, 0, value);
30900
+ var copyWithSet = function(obj, path13, value) {
30901
+ return copyWithSetImpl(obj, path13, 0, value);
31810
30902
  };
31811
30903
  var findHook = function(fiber, id) {
31812
30904
  var currentHook2 = fiber.memoizedState;
@@ -31816,10 +30908,10 @@ var require_react_reconciler_development = __commonJS({
31816
30908
  }
31817
30909
  return currentHook2;
31818
30910
  };
31819
- overrideHookState = function(fiber, id, path12, value) {
30911
+ overrideHookState = function(fiber, id, path13, value) {
31820
30912
  var hook = findHook(fiber, id);
31821
30913
  if (hook !== null) {
31822
- var newState = copyWithSet(hook.memoizedState, path12, value);
30914
+ var newState = copyWithSet(hook.memoizedState, path13, value);
31823
30915
  hook.memoizedState = newState;
31824
30916
  hook.baseState = newState;
31825
30917
  fiber.memoizedProps = assign({}, fiber.memoizedProps);
@@ -31829,10 +30921,10 @@ var require_react_reconciler_development = __commonJS({
31829
30921
  }
31830
30922
  }
31831
30923
  };
31832
- overrideHookStateDeletePath = function(fiber, id, path12) {
30924
+ overrideHookStateDeletePath = function(fiber, id, path13) {
31833
30925
  var hook = findHook(fiber, id);
31834
30926
  if (hook !== null) {
31835
- var newState = copyWithDelete(hook.memoizedState, path12);
30927
+ var newState = copyWithDelete(hook.memoizedState, path13);
31836
30928
  hook.memoizedState = newState;
31837
30929
  hook.baseState = newState;
31838
30930
  fiber.memoizedProps = assign({}, fiber.memoizedProps);
@@ -31855,8 +30947,8 @@ var require_react_reconciler_development = __commonJS({
31855
30947
  }
31856
30948
  }
31857
30949
  };
31858
- overrideProps = function(fiber, path12, value) {
31859
- fiber.pendingProps = copyWithSet(fiber.memoizedProps, path12, value);
30950
+ overrideProps = function(fiber, path13, value) {
30951
+ fiber.pendingProps = copyWithSet(fiber.memoizedProps, path13, value);
31860
30952
  if (fiber.alternate) {
31861
30953
  fiber.alternate.pendingProps = fiber.pendingProps;
31862
30954
  }
@@ -31865,8 +30957,8 @@ var require_react_reconciler_development = __commonJS({
31865
30957
  scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
31866
30958
  }
31867
30959
  };
31868
- overridePropsDeletePath = function(fiber, path12) {
31869
- fiber.pendingProps = copyWithDelete(fiber.memoizedProps, path12);
30960
+ overridePropsDeletePath = function(fiber, path13) {
30961
+ fiber.pendingProps = copyWithDelete(fiber.memoizedProps, path13);
31870
30962
  if (fiber.alternate) {
31871
30963
  fiber.alternate.pendingProps = fiber.pendingProps;
31872
30964
  }
@@ -37722,7 +36814,7 @@ var init_ansi_styles2 = __esm({
37722
36814
 
37723
36815
  // ../../node_modules/chalk/source/vendor/supports-color/index.js
37724
36816
  import process4 from "node:process";
37725
- import os2 from "node:os";
36817
+ import os3 from "node:os";
37726
36818
  import tty from "node:tty";
37727
36819
  function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process4.argv) {
37728
36820
  const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
@@ -37780,7 +36872,7 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
37780
36872
  return min;
37781
36873
  }
37782
36874
  if (process4.platform === "win32") {
37783
- const osRelease = os2.release().split(".");
36875
+ const osRelease = os3.release().split(".");
37784
36876
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
37785
36877
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
37786
36878
  }
@@ -39571,8 +38663,8 @@ var init_ErrorOverview = __esm({
39571
38663
  init_dist3();
39572
38664
  init_Box();
39573
38665
  init_Text();
39574
- cleanupPath = (path12) => {
39575
- return path12?.replace(`file://${cwd()}/`, "");
38666
+ cleanupPath = (path13) => {
38667
+ return path13?.replace(`file://${cwd()}/`, "");
39576
38668
  };
39577
38669
  stackUtils = new import_stack_utils.default({
39578
38670
  cwd: cwd(),
@@ -42811,7 +41903,7 @@ var require_has_flag = __commonJS({
42811
41903
  var require_supports_colors = __commonJS({
42812
41904
  "../../node_modules/@colors/colors/lib/system/supports-colors.js"(exports, module) {
42813
41905
  "use strict";
42814
- var os6 = __require("os");
41906
+ var os7 = __require("os");
42815
41907
  var hasFlag2 = require_has_flag();
42816
41908
  var env3 = process.env;
42817
41909
  var forceColor = void 0;
@@ -42849,7 +41941,7 @@ var require_supports_colors = __commonJS({
42849
41941
  }
42850
41942
  var min = forceColor ? 1 : 0;
42851
41943
  if (process.platform === "win32") {
42852
- var osRelease = os6.release().split(".");
41944
+ var osRelease = os7.release().split(".");
42853
41945
  if (Number(process.versions.node.split(".")[0]) >= 8 && Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
42854
41946
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
42855
41947
  }
@@ -44353,7 +43445,7 @@ var require_core = __commonJS({
44353
43445
  return match && match.index === 0;
44354
43446
  }
44355
43447
  var BACKREF_RE = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;
44356
- function join14(regexps, separator = "|") {
43448
+ function join15(regexps, separator = "|") {
44357
43449
  let numCaptures = 0;
44358
43450
  return regexps.map((regex2) => {
44359
43451
  numCaptures += 1;
@@ -44657,7 +43749,7 @@ var require_core = __commonJS({
44657
43749
  this.exec = () => null;
44658
43750
  }
44659
43751
  const terminators = this.regexes.map((el) => el[1]);
44660
- this.matcherRe = langRe(join14(terminators), true);
43752
+ this.matcherRe = langRe(join15(terminators), true);
44661
43753
  this.lastIndex = 0;
44662
43754
  }
44663
43755
  /** @param {string} s */
@@ -86934,15 +86026,15 @@ var require_route = __commonJS({
86934
86026
  };
86935
86027
  }
86936
86028
  function wrapConversion(toModel, graph) {
86937
- const path12 = [graph[toModel].parent, toModel];
86029
+ const path13 = [graph[toModel].parent, toModel];
86938
86030
  let fn = conversions[graph[toModel].parent][toModel];
86939
86031
  let cur = graph[toModel].parent;
86940
86032
  while (graph[cur].parent) {
86941
- path12.unshift(graph[cur].parent);
86033
+ path13.unshift(graph[cur].parent);
86942
86034
  fn = link2(conversions[graph[cur].parent][cur], fn);
86943
86035
  cur = graph[cur].parent;
86944
86036
  }
86945
- fn.conversion = path12;
86037
+ fn.conversion = path13;
86946
86038
  return fn;
86947
86039
  }
86948
86040
  module.exports = function(fromModel) {
@@ -87182,7 +86274,7 @@ var require_has_flag2 = __commonJS({
87182
86274
  var require_supports_color = __commonJS({
87183
86275
  "../../node_modules/supports-color/index.js"(exports, module) {
87184
86276
  "use strict";
87185
- var os6 = __require("os");
86277
+ var os7 = __require("os");
87186
86278
  var tty2 = __require("tty");
87187
86279
  var hasFlag2 = require_has_flag2();
87188
86280
  var { env: env3 } = process;
@@ -87230,7 +86322,7 @@ var require_supports_color = __commonJS({
87230
86322
  return min;
87231
86323
  }
87232
86324
  if (process.platform === "win32") {
87233
- const osRelease = os6.release().split(".");
86325
+ const osRelease = os7.release().split(".");
87234
86326
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
87235
86327
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
87236
86328
  }
@@ -102163,9 +101255,9 @@ var init_credential_filter = __esm({
102163
101255
  });
102164
101256
 
102165
101257
  // src/session/session-store.ts
102166
- import { chmod as chmod2, mkdir as mkdir2, open, readdir as readdir2, readFile as readFile2, rename as rename2, rm, stat, writeFile as writeFile2 } from "node:fs/promises";
101258
+ import { chmod as chmod2, mkdir as mkdir2, open, readdir as readdir2, readFile as readFile3, rename as rename2, rm, stat, writeFile as writeFile2 } from "node:fs/promises";
102167
101259
  import { randomBytes } from "node:crypto";
102168
- import path3 from "node:path";
101260
+ import path4 from "node:path";
102169
101261
  function redactCredentials(value) {
102170
101262
  if (typeof value === "string") return containsCredential(value) ? REDACTED : value;
102171
101263
  if (Array.isArray(value)) return value.map(redactCredentials);
@@ -102240,7 +101332,7 @@ var init_session_store = __esm({
102240
101332
  }
102241
101333
  fileFor(id) {
102242
101334
  if (!ID.test(id)) throw new SessionError(this.dir, `Invalid session id ${JSON.stringify(id)}`);
102243
- return path3.join(this.dir, `${id}.jsonl`);
101335
+ return path4.join(this.dir, `${id}.jsonl`);
102244
101336
  }
102245
101337
  async save(session2) {
102246
101338
  const file = this.fileFor(session2.meta.id);
@@ -102269,7 +101361,7 @@ var init_session_store = __esm({
102269
101361
  if (size > MAX_SESSION_BYTES) {
102270
101362
  throw new SessionError(file, `Session is ${size} bytes, over the ${MAX_SESSION_BYTES}-byte cap; not loaded`);
102271
101363
  }
102272
- text = await readFile2(file, "utf8");
101364
+ text = await readFile3(file, "utf8");
102273
101365
  } catch (err) {
102274
101366
  if (err instanceof SessionError) throw err;
102275
101367
  throw new SessionError(file, `Could not read session: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
@@ -102293,7 +101385,7 @@ var init_session_store = __esm({
102293
101385
  try {
102294
101386
  sessions.push((await this.load(name.slice(0, -".jsonl".length))).meta);
102295
101387
  } catch (err) {
102296
- errors.push(err instanceof SessionError ? err : new SessionError(path3.join(this.dir, name), String(err), { cause: err }));
101388
+ errors.push(err instanceof SessionError ? err : new SessionError(path4.join(this.dir, name), String(err), { cause: err }));
102297
101389
  }
102298
101390
  }
102299
101391
  sessions.sort((a, b2) => b2.updatedAt.localeCompare(a.updatedAt));
@@ -103367,17 +102459,17 @@ function isSubsequence(needle, hay) {
103367
102459
  for (let j2 = 0; j2 < hay.length && i < needle.length; j2++) if (hay[j2] === needle[i]) i++;
103368
102460
  return i === needle.length;
103369
102461
  }
103370
- function tier(path12, q2) {
103371
- const base = path12.slice(path12.lastIndexOf("/") + 1);
102462
+ function tier(path13, q2) {
102463
+ const base = path13.slice(path13.lastIndexOf("/") + 1);
103372
102464
  if (base.startsWith(q2)) return 0;
103373
102465
  if (base.includes(q2)) return 1;
103374
- if (path12.includes(q2)) return 2;
102466
+ if (path13.includes(q2)) return 2;
103375
102467
  return 3;
103376
102468
  }
103377
102469
  function rankPaths(paths, query, limit = 8) {
103378
102470
  if (query === "") return paths.slice(0, limit);
103379
102471
  const q2 = query.toLowerCase();
103380
- return paths.map((path12) => ({ path: path12, lower: path12.toLowerCase() })).filter(({ lower }) => isSubsequence(q2, lower)).map(({ path: path12, lower }) => ({ path: path12, rank: tier(lower, q2) })).sort((a, b2) => a.rank - b2.rank || a.path.length - b2.path.length || (a.path < b2.path ? -1 : a.path > b2.path ? 1 : 0)).slice(0, limit).map((m2) => m2.path);
102472
+ return paths.map((path13) => ({ path: path13, lower: path13.toLowerCase() })).filter(({ lower }) => isSubsequence(q2, lower)).map(({ path: path13, lower }) => ({ path: path13, rank: tier(lower, q2) })).sort((a, b2) => a.rank - b2.rank || a.path.length - b2.path.length || (a.path < b2.path ? -1 : a.path > b2.path ? 1 : 0)).slice(0, limit).map((m2) => m2.path);
103381
102473
  }
103382
102474
  var init_mention_rank = __esm({
103383
102475
  "src/editor/mention-rank.ts"() {
@@ -103394,9 +102486,9 @@ function activeMention(text, cursor) {
103394
102486
  const explicit = token[1] === "!";
103395
102487
  return { start, query: token.slice(explicit ? 2 : 1), explicit };
103396
102488
  }
103397
- function insertMention(text, cursor, mention, path12) {
103398
- if (/\s/.test(path12)) return { text, cursor };
103399
- const inserted = `@${mention.explicit ? "!" : ""}${path12}`;
102489
+ function insertMention(text, cursor, mention, path13) {
102490
+ if (/\s/.test(path13)) return { text, cursor };
102491
+ const inserted = `@${mention.explicit ? "!" : ""}${path13}`;
103400
102492
  const head = text.slice(0, mention.start) + inserted;
103401
102493
  const next = text[cursor];
103402
102494
  if (next !== void 0 && /\s/.test(next)) return { text: head + text.slice(cursor), cursor: head.length + 1 };
@@ -103766,20 +102858,20 @@ var init_history_store = __esm({
103766
102858
  });
103767
102859
 
103768
102860
  // src/editor/history-file-io.ts
103769
- import { chmod as chmod3, mkdir as mkdir3, readFile as readFile3, rename as rename3, writeFile as writeFile3 } from "node:fs/promises";
103770
- import path4 from "node:path";
102861
+ import { chmod as chmod3, mkdir as mkdir3, readFile as readFile4, rename as rename3, writeFile as writeFile3 } from "node:fs/promises";
102862
+ import path5 from "node:path";
103771
102863
  function createFileHistoryIO(file) {
103772
102864
  return {
103773
102865
  async read() {
103774
102866
  try {
103775
- return await readFile3(file, "utf8");
102867
+ return await readFile4(file, "utf8");
103776
102868
  } catch (err) {
103777
102869
  if (err.code === "ENOENT") return null;
103778
102870
  throw err;
103779
102871
  }
103780
102872
  },
103781
102873
  async write(content) {
103782
- await mkdir3(path4.dirname(file), { recursive: true, mode: 448 });
102874
+ await mkdir3(path5.dirname(file), { recursive: true, mode: 448 });
103783
102875
  const tmp = `${file}.${process.pid}.tmp`;
103784
102876
  await writeFile3(tmp, content, { mode: 384 });
103785
102877
  await chmod3(tmp, 384);
@@ -103941,10 +103033,10 @@ var init_session_commands = __esm({
103941
103033
  });
103942
103034
 
103943
103035
  // src/local-toolset.ts
103944
- import * as os3 from "node:os";
103945
- import * as path5 from "node:path";
103036
+ import * as os4 from "node:os";
103037
+ import * as path6 from "node:path";
103946
103038
  function createLocalToolset(opts) {
103947
- const auditFile = opts.auditFile ?? path5.join(os3.homedir(), ".spacecode", "audit.jsonl");
103039
+ const auditFile = opts.auditFile ?? path6.join(os4.homedir(), ".spacecode", "audit.jsonl");
103948
103040
  const host = guardHost(new LocalHost(opts.cwd), {
103949
103041
  audit: new FileAuditLog(auditFile),
103950
103042
  mode: opts.yolo ? "yolo" : "enforce",
@@ -103974,7 +103066,7 @@ var init_local_toolset = __esm({
103974
103066
 
103975
103067
  // src/project-card-scan.ts
103976
103068
  import { execFile as execFile3 } from "node:child_process";
103977
- import { readdir as readdir3, readFile as readFile4 } from "node:fs/promises";
103069
+ import { readdir as readdir3, readFile as readFile5 } from "node:fs/promises";
103978
103070
  import { join as join7 } from "node:path";
103979
103071
  function digestLines(md) {
103980
103072
  return md.split("\n").map((l) => l.trim()).filter((l) => l && !/^#\s/.test(l) && !/^[|\s:-]+$/.test(l) && !/^\|\s*(area|task|priority|date|issue|id)\b/i.test(l)).slice(0, 12);
@@ -104075,13 +103167,13 @@ var init_project_card_scan = __esm({
104075
103167
  MAX_BACKLOG = 6;
104076
103168
  DIGEST_FILES = ["CLAUDE.md", "TODO.md", "BUGS.md", "ISSUES.md", "CHANGELOG.md", "README.md"];
104077
103169
  nodeScanIo = {
104078
- async readdir(path12) {
104079
- const entries = await readdir3(path12, { withFileTypes: true });
103170
+ async readdir(path13) {
103171
+ const entries = await readdir3(path13, { withFileTypes: true });
104080
103172
  return entries.map((e) => ({ name: e.name, isDir: e.isDirectory() }));
104081
103173
  },
104082
- async readFile(path12) {
103174
+ async readFile(path13) {
104083
103175
  try {
104084
- return await readFile4(path12, "utf8");
103176
+ return await readFile5(path13, "utf8");
104085
103177
  } catch (err) {
104086
103178
  if (err.code === "ENOENT") return null;
104087
103179
  throw err;
@@ -104434,9 +103526,9 @@ var init_mention_resolver = __esm({
104434
103526
 
104435
103527
  // src/editor/node-mention-fs.ts
104436
103528
  import { open as open2, realpath as realpath2, stat as stat2 } from "node:fs/promises";
104437
- import path6 from "node:path";
103529
+ import path7 from "node:path";
104438
103530
  function isInside(root, target) {
104439
- return target === root || target.startsWith(root.endsWith(path6.sep) ? root : root + path6.sep);
103531
+ return target === root || target.startsWith(root.endsWith(path7.sep) ? root : root + path7.sep);
104440
103532
  }
104441
103533
  function createNodeMentionFS(cwd2) {
104442
103534
  let rootPromise;
@@ -104481,10 +103573,10 @@ var init_node_mention_fs = __esm({
104481
103573
 
104482
103574
  // src/editor/node-read-dir.ts
104483
103575
  import { readdir as readdir4 } from "node:fs/promises";
104484
- import path7 from "node:path";
103576
+ import path8 from "node:path";
104485
103577
  function createNodeReadDir(cwd2) {
104486
103578
  return async (rel) => {
104487
- const entries = await readdir4(path7.join(cwd2, rel), { withFileTypes: true });
103579
+ const entries = await readdir4(path8.join(cwd2, rel), { withFileTypes: true });
104488
103580
  return entries.map((e) => ({ name: e.name, isFile: e.isFile(), isDirectory: e.isDirectory() }));
104489
103581
  };
104490
103582
  }
@@ -104495,11 +103587,11 @@ var init_node_read_dir = __esm({
104495
103587
  });
104496
103588
 
104497
103589
  // src/editor/node-mentions.ts
104498
- import { readFile as readFile5 } from "node:fs/promises";
104499
- import path8 from "node:path";
103590
+ import { readFile as readFile6 } from "node:fs/promises";
103591
+ import path9 from "node:path";
104500
103592
  async function readGitignore(cwd2) {
104501
103593
  try {
104502
- return createGitignoreMatcher(await readFile5(path8.join(cwd2, ".gitignore"), "utf8"));
103594
+ return createGitignoreMatcher(await readFile6(path9.join(cwd2, ".gitignore"), "utf8"));
104503
103595
  } catch (err) {
104504
103596
  if (err.code === "ENOENT") return () => false;
104505
103597
  throw err;
@@ -104609,7 +103701,7 @@ var init_recording_chunks = __esm({
104609
103701
 
104610
103702
  // src/recording/recorder.ts
104611
103703
  import { createHash as createHash4 } from "node:crypto";
104612
- import { mkdir as mkdir4, readFile as readFile6, rename as rename4, rm as rm2, writeFile as writeFile4, readdir as readdir5, stat as stat3 } from "node:fs/promises";
103704
+ import { mkdir as mkdir4, readFile as readFile7, rename as rename4, rm as rm2, writeFile as writeFile4, readdir as readdir5, stat as stat3 } from "node:fs/promises";
104613
103705
  import { join as join9 } from "node:path";
104614
103706
  function projectHash(root) {
104615
103707
  return createHash4("sha256").update(root).digest("hex");
@@ -104633,7 +103725,7 @@ function createRecorder(deps) {
104633
103725
  if (cached3) return cached3;
104634
103726
  let st;
104635
103727
  try {
104636
- st = JSON.parse(await readFile6(stateFile(sessionId), "utf8"));
103728
+ st = JSON.parse(await readFile7(stateFile(sessionId), "utf8"));
104637
103729
  } catch (e) {
104638
103730
  if (e.code !== "ENOENT") {
104639
103731
  const bad = join9(deps.dir, "bad");
@@ -104828,15 +103920,15 @@ var init_recorder = __esm({
104828
103920
  });
104829
103921
 
104830
103922
  // src/recording/recording-runtime.ts
104831
- import { mkdir as mkdir5, readFile as readFile7, writeFile as writeFile5, stat as stat4 } from "node:fs/promises";
103923
+ import { mkdir as mkdir5, readFile as readFile8, writeFile as writeFile5, stat as stat4 } from "node:fs/promises";
104832
103924
  import { join as join10, sep as sep2 } from "node:path";
104833
103925
  function matchesExclude(pattern, root) {
104834
103926
  const re3 = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "\0").replace(/\*/g, `[^${sep2 === "\\" ? "\\\\" : "/"}]*`).replace(/\u0000/g, ".*");
104835
103927
  return new RegExp(`^${re3}(?:[/\\\\].*)?$`).test(root);
104836
103928
  }
104837
- async function exists(path12) {
103929
+ async function exists(path13) {
104838
103930
  try {
104839
- await stat4(path12);
103931
+ await stat4(path13);
104840
103932
  return true;
104841
103933
  } catch (e) {
104842
103934
  if (e.code === "ENOENT") return false;
@@ -104853,7 +103945,7 @@ function createRecordingRuntime(deps) {
104853
103945
  if (deps.env.SPACECODE_RECORD?.trim().toLowerCase() === "off") return "SPACECODE_RECORD=off";
104854
103946
  let cfg = {};
104855
103947
  try {
104856
- cfg = JSON.parse(await readFile7(join10(deps.configDir, "config.json"), "utf8"));
103948
+ cfg = JSON.parse(await readFile8(join10(deps.configDir, "config.json"), "utf8"));
104857
103949
  } catch (e) {
104858
103950
  if (e.code !== "ENOENT") return `~/.spacecode/config.json is unreadable (${e instanceof Error ? e.message : String(e)}); fix it to record`;
104859
103951
  }
@@ -105001,12 +104093,37 @@ var init_skills_command = __esm({
105001
104093
  }
105002
104094
  });
105003
104095
 
105004
- // src/permission-rules-loader.ts
105005
- import { readFile as readFile8 } from "node:fs/promises";
104096
+ // src/mcp/tool-cache-file.ts
104097
+ import { readFile as readFile9 } from "node:fs/promises";
105006
104098
  import { join as join11 } from "node:path";
105007
- async function readOrNull2(path12) {
104099
+ function nodeToolCache(configDir) {
104100
+ const path13 = join11(configDir, "mcp-tools-cache.json");
104101
+ return {
104102
+ read: async () => {
104103
+ try {
104104
+ return await readFile9(path13, "utf8");
104105
+ } catch (err) {
104106
+ if (err.code === "ENOENT") return null;
104107
+ throw err;
104108
+ }
104109
+ },
104110
+ write: (text) => writeTextAtomic(path13, text),
104111
+ hash: sha256
104112
+ };
104113
+ }
104114
+ var init_tool_cache_file = __esm({
104115
+ "src/mcp/tool-cache-file.ts"() {
104116
+ "use strict";
104117
+ init_import_gate();
104118
+ }
104119
+ });
104120
+
104121
+ // src/permission-rules-loader.ts
104122
+ import { readFile as readFile10 } from "node:fs/promises";
104123
+ import { join as join12 } from "node:path";
104124
+ async function readOrNull2(path13) {
105008
104125
  try {
105009
- return await readFile8(path12, "utf8");
104126
+ return await readFile10(path13, "utf8");
105010
104127
  } catch (err) {
105011
104128
  if (err.code === "ENOENT") return null;
105012
104129
  throw err;
@@ -105014,7 +104131,7 @@ async function readOrNull2(path12) {
105014
104131
  }
105015
104132
  async function loadPermissionRules(deps) {
105016
104133
  const none = (line) => ({ rules: void 0, defaultMode: void 0, statusLines: [line] });
105017
- const configPath = join11(deps.configDir, "imports.json");
104134
+ const configPath = join12(deps.configDir, "imports.json");
105018
104135
  try {
105019
104136
  const loaded = parseImportsConfig(await readOrNull2(configPath), configPath);
105020
104137
  if (loaded.error) return none("permission rules: off (imports.json unreadable)");
@@ -105023,13 +104140,13 @@ async function loadPermissionRules(deps) {
105023
104140
  return none(`permission rules: off (imports.json not read: ${err instanceof Error ? err.message : String(err)})`);
105024
104141
  }
105025
104142
  const lines2 = [];
105026
- const files = [{ scope: "user", path: join11(deps.homeDir, ".claude", "settings.json") }];
104143
+ const files = [{ scope: "user", path: join12(deps.homeDir, ".claude", "settings.json") }];
105027
104144
  try {
105028
104145
  const sources2 = await readRepoSources(deps.cwd);
105029
104146
  if (sources2[".claude/settings.json"] !== void 0 || sources2[".claude/settings.local.json"] !== void 0) {
105030
104147
  const status = await createNodeTrustStore(deps.configDir).repoStatus(deps.cwd, sources2);
105031
104148
  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") });
104149
+ files.push({ scope: "repo", path: join12(deps.cwd, ".claude", "settings.json") }, { scope: "local", path: join12(deps.cwd, ".claude", "settings.local.json") });
105033
104150
  } else lines2.push(`permission rules: repo settings NOT loaded (${status === "never" ? "marked never trust" : "untrusted repo"})`);
105034
104151
  }
105035
104152
  } catch (err) {
@@ -105073,7 +104190,7 @@ var init_permission_rules_loader = __esm({
105073
104190
  // src/import-reload.ts
105074
104191
  async function checkRepoSources(deps, loaded) {
105075
104192
  const files = await readRepoSources(deps.cwd);
105076
- const current = fingerprintSources(files, sha2562);
104193
+ const current = fingerprintSources(files, sha256);
105077
104194
  const status = Object.keys(files).length === 0 ? "untrusted" : await createNodeTrustStore(deps.configDir).repoStatus(deps.cwd, files);
105078
104195
  return { current, changes: diffSources(loaded, current), trusted: status === "trusted" || status === "session" };
105079
104196
  }
@@ -105094,7 +104211,7 @@ async function describeReload(deps, loaded) {
105094
104211
  async function trustCurrentSources(deps) {
105095
104212
  const files = await readRepoSources(deps.cwd);
105096
104213
  await createNodeTrustStore(deps.configDir).setRepo(deps.cwd, files, "trusted");
105097
- return fingerprintSources(files, sha2562);
104214
+ return fingerprintSources(files, sha256);
105098
104215
  }
105099
104216
  var init_import_reload = __esm({
105100
104217
  "src/import-reload.ts"() {
@@ -105106,7 +104223,7 @@ var init_import_reload = __esm({
105106
104223
  });
105107
104224
 
105108
104225
  // src/command-loader.ts
105109
- import { join as join12 } from "node:path";
104226
+ import { join as join13 } from "node:path";
105110
104227
  async function userCommands(deps, lines2) {
105111
104228
  try {
105112
104229
  const apiKey = await resolveApiKey({ env: deps.env, readFile: deps.readFile, homedir: deps.homedir });
@@ -105161,7 +104278,7 @@ async function repoCommands(deps, lines2) {
105161
104278
  }
105162
104279
  }
105163
104280
  async function loadCommands(deps) {
105164
- const configPath = join12(deps.configDir, "imports.json");
104281
+ const configPath = join13(deps.configDir, "imports.json");
105165
104282
  try {
105166
104283
  const loaded = parseImportsConfig(await deps.readFile(configPath).catch((err) => {
105167
104284
  if (err.code === "ENOENT") return null;
@@ -105194,9 +104311,9 @@ var init_command_loader = __esm({
105194
104311
  });
105195
104312
 
105196
104313
  // src/useReplController.ts
105197
- import { readFile as readFile9, writeFile as writeFile6, mkdir as mkdir6 } from "node:fs/promises";
104314
+ import { readFile as readFile11, writeFile as writeFile6, mkdir as mkdir6 } from "node:fs/promises";
105198
104315
  import { homedir as homedir2 } from "node:os";
105199
- import { join as join13 } from "node:path";
104316
+ import { join as join14 } from "node:path";
105200
104317
  import { execFile as execFile4 } from "node:child_process";
105201
104318
  function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mentionsOpt, sessions, resumeLatest: resumeLatest2, onViewTurns, onViewResume, isEscOwned }) {
105202
104319
  const { exit } = use_app_default();
@@ -105219,7 +104336,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
105219
104336
  }, [turnStartedAt]);
105220
104337
  const projectRoot = (0, import_react30.useMemo)(() => process.cwd(), []);
105221
104338
  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 }),
104339
+ () => loadPromptTemplates({ env: process.env, readFile: (p) => readFile11(p, "utf-8"), writeFile: (p, d) => writeFile6(p, d, "utf-8"), mkdir: (p) => mkdir6(p, { recursive: true }).then(() => void 0), homedir: homedir2, fetch }),
105223
104340
  []
105224
104341
  );
105225
104342
  (0, import_react30.useEffect)(() => {
@@ -105235,11 +104352,11 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
105235
104352
  const recordingRef = (0, import_react30.useRef)(null);
105236
104353
  const recording = () => recordingRef.current ??= createRecordingRuntime({
105237
104354
  env: process.env,
105238
- configDir: process.env.SPACECODE_CONFIG_DIR?.trim() || join13(homedir2(), ".spacecode"),
104355
+ configDir: process.env.SPACECODE_CONFIG_DIR?.trim() || join14(homedir2(), ".spacecode"),
105239
104356
  projectRoot,
105240
104357
  fetch,
105241
104358
  baseUrl: backendUrl(process.env),
105242
- resolveApiKey: () => resolveApiKey({ env: process.env, readFile: (p) => readFile9(p, "utf-8"), homedir: homedir2 }).catch(() => null),
104359
+ resolveApiKey: () => resolveApiKey({ env: process.env, readFile: (p) => readFile11(p, "utf-8"), homedir: homedir2 }).catch(() => null),
105243
104360
  log: (m2) => console.error(`[agent-ui] ${m2}`)
105244
104361
  });
105245
104362
  const resumeListRef = (0, import_react30.useRef)([]);
@@ -105263,10 +104380,10 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
105263
104380
  (0, import_react30.useEffect)(() => {
105264
104381
  let live2 = true;
105265
104382
  loadCommands({
105266
- configDir: process.env.SPACECODE_CONFIG_DIR?.trim() || join13(homedir2(), ".spacecode"),
104383
+ configDir: process.env.SPACECODE_CONFIG_DIR?.trim() || join14(homedir2(), ".spacecode"),
105267
104384
  cwd: process.cwd(),
105268
104385
  env: process.env,
105269
- readFile: (p) => readFile9(p, "utf-8"),
104386
+ readFile: (p) => readFile11(p, "utf-8"),
105270
104387
  homedir: homedir2,
105271
104388
  fetch,
105272
104389
  builtins: SLASH_COMMANDS.map((c) => c.name)
@@ -105484,7 +104601,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
105484
104601
  notice(formatContextReport({ contextTokens: estimateTotalTokens("", history), maxContextTokens: DEFAULT_MAX_CONTEXT_TOKENS }));
105485
104602
  return;
105486
104603
  case "import": {
105487
- const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() || join13(homedir2(), ".spacecode");
104604
+ const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() || join14(homedir2(), ".spacecode");
105488
104605
  const deps = { configDir, cwd: process.cwd() };
105489
104606
  const [sub, ...rest] = slash.args.split(/\s+/).filter(Boolean);
105490
104607
  if (sub !== "reload" || rest.length > 1 || rest[0] !== void 0 && rest[0] !== "trust") {
@@ -105492,12 +104609,12 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
105492
104609
  return;
105493
104610
  }
105494
104611
  try {
105495
- const baseline = loadedSourcesRef.current ?? fingerprintSources(await readRepoSources(process.cwd()), sha2562);
104612
+ const baseline = loadedSourcesRef.current ?? fingerprintSources(await readRepoSources(process.cwd()), sha256);
105496
104613
  if (rest[0] === "trust") {
105497
104614
  loadedSourcesRef.current = await trustCurrentSources(deps);
105498
104615
  const loaded = await loadPermissionRules({ configDir, homeDir: homedir2(), cwd: process.cwd() });
105499
104616
  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) });
104617
+ const cmds = await loadCommands({ configDir, cwd: process.cwd(), env: process.env, readFile: (p) => readFile11(p, "utf-8"), homedir: homedir2, fetch, builtins: SLASH_COMMANDS.map((c) => c.name) });
105501
104618
  setCommandsLoaded(cmds);
105502
104619
  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
104620
  } else notice((await describeReload(deps, baseline)).join("\n"));
@@ -105577,7 +104694,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
105577
104694
  }
105578
104695
  setIsRunning(true);
105579
104696
  try {
105580
- const apiKey2 = await resolveApiKey({ env: process.env, readFile: (p) => readFile9(p, "utf-8"), homedir: homedir2 });
104697
+ const apiKey2 = await resolveApiKey({ env: process.env, readFile: (p) => readFile11(p, "utf-8"), homedir: homedir2 });
105581
104698
  if (!apiKey2) {
105582
104699
  notice(MISSING_KEY_MESSAGE);
105583
104700
  return;
@@ -105599,7 +104716,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
105599
104716
  }
105600
104717
  case "model": {
105601
104718
  try {
105602
- const apiKey2 = await resolveApiKey({ env: process.env, readFile: (p) => readFile9(p, "utf-8"), homedir: homedir2 });
104719
+ const apiKey2 = await resolveApiKey({ env: process.env, readFile: (p) => readFile11(p, "utf-8"), homedir: homedir2 });
105603
104720
  if (!apiKey2) {
105604
104721
  notice(MISSING_KEY_MESSAGE);
105605
104722
  return;
@@ -105634,7 +104751,7 @@ ${list2.map((c) => ` /${c.name} (${c.scope})${c.description ? ` ${c.descripti
105634
104751
  }
105635
104752
  case "skills": {
105636
104753
  const state = profileRef.current;
105637
- const apiKey2 = await resolveApiKey({ env: process.env, readFile: (p) => readFile9(p, "utf-8"), homedir: homedir2 }).catch(() => null);
104754
+ const apiKey2 = await resolveApiKey({ env: process.env, readFile: (p) => readFile11(p, "utf-8"), homedir: homedir2 }).catch(() => null);
105638
104755
  const client = apiKey2 ? createProfileClient({ baseUrl: backendUrl(process.env), apiKey: apiKey2, fetch }) : null;
105639
104756
  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
104757
  return;
@@ -105656,7 +104773,7 @@ ${list2.map((c) => ` /${c.name} (${c.scope})${c.description ? ` ${c.descripti
105656
104773
  try {
105657
104774
  apiKey = await resolveApiKey({
105658
104775
  env: process.env,
105659
- readFile: (p) => readFile9(p, "utf-8"),
104776
+ readFile: (p) => readFile11(p, "utf-8"),
105660
104777
  homedir: homedir2
105661
104778
  });
105662
104779
  } catch (err) {
@@ -105692,7 +104809,7 @@ ${list2.map((c) => ` /${c.name} (${c.scope})${c.description ? ` ${c.descripti
105692
104809
  let keepForRetry = false;
105693
104810
  try {
105694
104811
  if (loadedSourcesRef.current) {
105695
- const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() || join13(homedir2(), ".spacecode");
104812
+ const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() || join14(homedir2(), ".spacecode");
105696
104813
  try {
105697
104814
  const check = await checkRepoSources({ configDir, cwd: process.cwd() }, loadedSourcesRef.current);
105698
104815
  if (check.changes.changed) {
@@ -105709,7 +104826,7 @@ ${list2.map((c) => ` /${c.name} (${c.scope})${c.description ? ` ${c.descripti
105709
104826
  }
105710
104827
  profilePromiseRef.current ??= prepareProfile({
105711
104828
  env: process.env,
105712
- readFile: (p) => readFile9(p, "utf-8"),
104829
+ readFile: (p) => readFile11(p, "utf-8"),
105713
104830
  writeFile: (p, d) => writeFile6(p, d, { encoding: "utf-8", mode: 384 }),
105714
104831
  mkdir: async (p) => {
105715
104832
  await mkdir6(p, { recursive: true });
@@ -105719,8 +104836,8 @@ ${list2.map((c) => ` /${c.name} (${c.scope})${c.description ? ` ${c.descripti
105719
104836
  cwd: process.cwd(),
105720
104837
  gitRoot: gitTopLevel,
105721
104838
  importGate: async () => {
105722
- const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() || join13(homedir2(), ".spacecode");
105723
- const fingerprint = fingerprintSources(await readRepoSources(process.cwd()), sha2562);
104839
+ const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() || join14(homedir2(), ".spacecode");
104840
+ const fingerprint = fingerprintSources(await readRepoSources(process.cwd()), sha256);
105724
104841
  const gate = await importGateStatus({ configDir, cwd: process.cwd(), interactive: Boolean(process.stdin.isTTY) });
105725
104842
  const loaded = await loadPermissionRules({ configDir, homeDir: homedir2(), cwd: process.cwd() });
105726
104843
  rulesRef.current = loaded.rules;
@@ -105731,7 +104848,7 @@ ${list2.map((c) => ` /${c.name} (${c.scope})${c.description ? ` ${c.descripti
105731
104848
  });
105732
104849
  profileRef.current = await profilePromiseRef.current;
105733
104850
  if (mcpRef.current === null && (profileRef.current?.mcpDefs?.length ?? 0) > 0) {
105734
- const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() || join13(homedir2(), ".spacecode");
104851
+ const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() || join14(homedir2(), ".spacecode");
105735
104852
  const trust = createNodeTrustStore(configDir);
105736
104853
  const manager = new McpManager({
105737
104854
  defs: profileRef.current.mcpDefs,
@@ -105741,10 +104858,12 @@ ${list2.map((c) => ` /${c.name} (${c.scope})${c.description ? ` ${c.descripti
105741
104858
  ...rulesRef.current ? { rules: rulesRef.current } : {},
105742
104859
  permissions,
105743
104860
  confirm: askUser,
105744
- audit: new FileAuditLog(join13(homedir2(), ".spacecode", "audit.jsonl")),
105745
- clientVersion: process.env.SPACECODE_VERSION ?? "0"
104861
+ audit: new FileAuditLog(join14(homedir2(), ".spacecode", "audit.jsonl")),
104862
+ clientVersion: process.env.SPACECODE_VERSION ?? "0",
104863
+ toolCache: nodeToolCache(configDir)
105746
104864
  });
105747
104865
  mcpRef.current = manager;
104866
+ await manager.prime();
105748
104867
  void manager.start();
105749
104868
  }
105750
104869
  const toolset = createToolset();
@@ -105882,6 +105001,7 @@ var init_useReplController = __esm({
105882
105001
  init_skills_command();
105883
105002
  init_src();
105884
105003
  init_mcp_manager();
105004
+ init_tool_cache_file();
105885
105005
  init_import_gate();
105886
105006
  init_permission_rules_loader();
105887
105007
  init_import_reload();
@@ -106013,12 +105133,12 @@ var init_TurnViewer = __esm({
106013
105133
  });
106014
105134
 
106015
105135
  // src/render/resume-rows.ts
106016
- import path9 from "node:path";
105136
+ import path10 from "node:path";
106017
105137
  function orderSessions(sessions, projectRoot) {
106018
105138
  return [...sessions.filter((s) => s.projectRoot === projectRoot), ...sessions.filter((s) => s.projectRoot !== projectRoot)];
106019
105139
  }
106020
105140
  function formatResumeRow(meta, now, columns, sep3 = "\xB7", ellipsis = "\u2026") {
106021
- const project = path9.basename(meta.projectRoot) || meta.projectRoot;
105141
+ const project = path10.basename(meta.projectRoot) || meta.projectRoot;
106022
105142
  const tail = [meta.turns !== void 0 ? `${meta.turns} turn${meta.turns === 1 ? "" : "s"}` : null, formatCost(estimateCostUsd(meta.tokens.input, meta.tokens.output))].filter(Boolean).join(` ${sep3} `);
106023
105143
  const head = `${formatAge(meta.updatedAt, now)} ${sep3} ${project} ${sep3} `;
106024
105144
  const fixed = head.length + 2 + ` ${sep3} ${tail}`.length;
@@ -106163,14 +105283,14 @@ var App_exports = {};
106163
105283
  __export(App_exports, {
106164
105284
  App: () => App2
106165
105285
  });
106166
- import os4 from "node:os";
106167
- import path10 from "node:path";
105286
+ import os5 from "node:os";
105287
+ import path11 from "node:path";
106168
105288
  function App2({ repoName: repoName2, onboardingMode: onboardingMode2, launchCount: launchCount2, yolo: yolo2, history, sessions, resumeLatest: resumeLatest2, git: gitRunner, interactivePicker = true, fullscreen: fullscreen2 = false }) {
106169
105289
  const promptHistory = (0, import_react34.useMemo)(
106170
- () => history ?? new PromptHistory(createFileHistoryIO(path10.join(os4.homedir(), ".spacecode", "history"))),
105290
+ () => history ?? new PromptHistory(createFileHistoryIO(path11.join(os5.homedir(), ".spacecode", "history"))),
106171
105291
  [history]
106172
105292
  );
106173
- const sessionStore = (0, import_react34.useMemo)(() => sessions ?? new SessionStore(path10.join(os4.homedir(), ".spacecode", "sessions")), [sessions]);
105293
+ const sessionStore = (0, import_react34.useMemo)(() => sessions ?? new SessionStore(path11.join(os5.homedir(), ".spacecode", "sessions")), [sessions]);
106174
105294
  const runGit = (0, import_react34.useMemo)(() => gitRunner ?? createNodeGitRunner(process.cwd()), [gitRunner]);
106175
105295
  const startedAtRef = (0, import_react34.useRef)(Date.now());
106176
105296
  const [viewingRaw, setViewing] = (0, import_react34.useState)(false);
@@ -106260,7 +105380,7 @@ function App2({ repoName: repoName2, onboardingMode: onboardingMode2, launchCoun
106260
105380
  viewing && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TurnViewer, { turns: viewerTurns, steps: turnSteps, rows: rows - 2, columns, onClose: () => setViewing(false), onEscape: swallowEsc }),
106261
105381
  picking && resumeFound && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ResumePicker, { sessions: resumeFound.sessions, errors: resumeFound.errors, projectRoot: process.cwd(), now: /* @__PURE__ */ new Date(), rows: rows - 2, columns, preview: resume.preview, onResume: resume.load, onDelete: resume.remove, onClose: () => setResumeFound(null), onEscape: swallowEsc }),
106262
105382
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, display: hidden ? "none" : "flex", children: [
106263
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Box_default, { flexShrink: 0, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Header, { project: repoName2 ?? path10.basename(process.cwd()), git: gitState, turns: turns.length, elapsedMs: Date.now() - startedAtRef.current, columns }) }),
105383
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Box_default, { flexShrink: 0, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Header, { project: repoName2 ?? path11.basename(process.cwd()), git: gitState, turns: turns.length, elapsedMs: Date.now() - startedAtRef.current, columns }) }),
106264
105384
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Transcript, { scroll, onMeasure, children: [
106265
105385
  notices.map((n, i) => typeof n === "string" ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { ...paint("action"), children: n }, i) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ErrorBlock, { view: n.error }, i)),
106266
105386
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(WelcomeBanner, { repoName: repoName2, onboardingMode: onboardingMode2, launchCount: launchCount2 }),
@@ -106290,7 +105410,7 @@ function App2({ repoName: repoName2, onboardingMode: onboardingMode2, launchCoun
106290
105410
  ] });
106291
105411
  }
106292
105412
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Box_default, { flexDirection: "column", children: [
106293
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Header, { project: repoName2 ?? path10.basename(process.cwd()), git: gitState, turns: turns.length, elapsedMs: Date.now() - startedAtRef.current, columns }),
105413
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Header, { project: repoName2 ?? path11.basename(process.cwd()), git: gitState, turns: turns.length, elapsedMs: Date.now() - startedAtRef.current, columns }),
106294
105414
  notices.map((n, i) => typeof n === "string" ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { ...paint("action"), children: n }, i) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ErrorBlock, { view: n.error }, i)),
106295
105415
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Static, { items: [BANNER, ...turns], children: (turn) => typeof turn === "symbol" ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(WelcomeBanner, { repoName: repoName2, onboardingMode: onboardingMode2, launchCount: launchCount2 }, "banner") : /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Box_default, { flexDirection: "column", children: [
106296
105416
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Text, { ...paint("secondary"), children: [
@@ -106384,8 +105504,8 @@ init_color_policy();
106384
105504
  applyColorPolicy(process.env, Boolean(process.stdout.isTTY));
106385
105505
 
106386
105506
  // src/index.tsx
106387
- import os5 from "node:os";
106388
- import path11 from "node:path";
105507
+ import os6 from "node:os";
105508
+ import path12 from "node:path";
106389
105509
  import fs5 from "node:fs";
106390
105510
  import util2 from "node:util";
106391
105511
 
@@ -106471,7 +105591,7 @@ function clearOnResize(stdout) {
106471
105591
  var import_jsx_runtime14 = __toESM(require_jsx_runtime(), 1);
106472
105592
  if (process.argv[2] === "import-claude") {
106473
105593
  const { runImportClaude: runImportClaude2 } = await Promise.resolve().then(() => (init_import_claude(), import_claude_exports));
106474
- const { readFile: readFile10, readdir: readdir6 } = await import("node:fs/promises");
105594
+ const { readFile: readFile12, readdir: readdir6 } = await import("node:fs/promises");
106475
105595
  const { execFile: execFile5 } = await import("node:child_process");
106476
105596
  const gitRoot = () => new Promise((resolve2) => {
106477
105597
  execFile5("git", ["rev-parse", "--show-toplevel"], { cwd: process.cwd() }, (err, stdout) => resolve2(err ? null : stdout.trim() || null));
@@ -106479,9 +105599,9 @@ if (process.argv[2] === "import-claude") {
106479
105599
  const code = await runImportClaude2({
106480
105600
  argv: process.argv.slice(2),
106481
105601
  env: process.env,
106482
- fs: { readFile: (p) => readFile10(p, "utf-8"), readdir: (p) => readdir6(p) },
106483
- readFile: (p) => readFile10(p, "utf-8"),
106484
- homedir: os5.homedir,
105602
+ fs: { readFile: (p) => readFile12(p, "utf-8"), readdir: (p) => readdir6(p) },
105603
+ readFile: (p) => readFile12(p, "utf-8"),
105604
+ homedir: os6.homedir,
106485
105605
  cwd: process.cwd(),
106486
105606
  gitRoot,
106487
105607
  fetch,
@@ -106500,37 +105620,8 @@ if (process.argv[2] === "import-claude") {
106500
105620
  process.exit(code);
106501
105621
  }
106502
105622
  if (process.argv[2] === "approve-mcp") {
106503
- const { runApproveMcp: runApproveMcp2 } = await Promise.resolve().then(() => (init_approve_mcp(), approve_mcp_exports));
106504
- const { createNodeTrustStore: createNodeTrustStore2 } = await Promise.resolve().then(() => (init_import_gate(), import_gate_exports));
106505
- const { resolveApiKey: resolveApiKey2 } = await Promise.resolve().then(() => (init_api_key(), api_key_exports));
106506
- const { backendUrl: backendUrl2 } = await Promise.resolve().then(() => (init_profile_runtime(), profile_runtime_exports));
106507
- const { createProfileClient: createProfileClient2 } = await Promise.resolve().then(() => (init_src(), src_exports));
106508
- const { readFile: readFile10 } = await import("node:fs/promises");
106509
- 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);
106511
- const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
106512
- const { createInterface } = interactive ? await import("node:readline/promises") : { createInterface: void 0 };
106513
- const rl = createInterface ? createInterface({ input: process.stdin, output: process.stdout }) : void 0;
106514
- try {
106515
- const code = await runApproveMcp2({
106516
- configDir,
106517
- cwd: process.cwd(),
106518
- readFile: (p) => readFile10(p, "utf-8"),
106519
- client: apiKey ? createProfileClient2({ baseUrl: backendUrl2(process.env), apiKey, fetch }) : null,
106520
- trust: createNodeTrustStore2(configDir),
106521
- ...rl ? { ask: async (q2) => {
106522
- try {
106523
- return await rl.question(q2);
106524
- } catch {
106525
- return null;
106526
- }
106527
- } } : {},
106528
- log: (line) => console.log(line)
106529
- });
106530
- process.exitCode = code;
106531
- } finally {
106532
- rl?.close();
106533
- }
105623
+ const { runApproveMcpOnNode: runApproveMcpOnNode2 } = await Promise.resolve().then(() => (init_approve_mcp_node(), approve_mcp_node_exports));
105624
+ process.exitCode = await runApproveMcpOnNode2({ quiet: false });
106534
105625
  process.exit(process.exitCode ?? 0);
106535
105626
  }
106536
105627
  var repoName = process.env.REPO_NAME || process.cwd().split("/").pop() || void 0;
@@ -106547,7 +105638,7 @@ if (process.stdin.isTTY && process.stdout.isTTY) {
106547
105638
  const { promptRepoTrust: promptRepoTrust2 } = await Promise.resolve().then(() => (init_repo_trust_prompt(), repo_trust_prompt_exports));
106548
105639
  const files = await readRepoSources2(process.cwd());
106549
105640
  if (Object.keys(files).length > 0) {
106550
- const store = createNodeTrustStore2(process.env.SPACECODE_CONFIG_DIR?.trim() || path11.join(os5.homedir(), ".spacecode"));
105641
+ const store = createNodeTrustStore2(process.env.SPACECODE_CONFIG_DIR?.trim() || path12.join(os6.homedir(), ".spacecode"));
106551
105642
  if (await store.repoStatus(process.cwd(), files) === "untrusted") {
106552
105643
  const { createInterface } = await import("node:readline/promises");
106553
105644
  const rl = createInterface({ input: process.stdin, output: process.stdout });
@@ -106575,6 +105666,15 @@ if (process.stdin.isTTY && process.stdout.isTTY) {
106575
105666
  `);
106576
105667
  }
106577
105668
  }
105669
+ if (process.stdin.isTTY && process.stdout.isTTY) {
105670
+ try {
105671
+ const { runApproveMcpOnNode: runApproveMcpOnNode2 } = await Promise.resolve().then(() => (init_approve_mcp_node(), approve_mcp_node_exports));
105672
+ await runApproveMcpOnNode2({ quiet: true });
105673
+ } catch (err) {
105674
+ process.stderr.write(`MCP approval check failed (${err instanceof Error ? err.message : String(err)}); imported MCP servers stay unapproved.
105675
+ `);
105676
+ }
105677
+ }
106578
105678
  var restoreCiVariables = hideCiVariables(process.env, Boolean(process.stdout.isTTY));
106579
105679
  var render3;
106580
105680
  var App3;
@@ -106586,9 +105686,9 @@ try {
106586
105686
  var fullscreen = shouldUseFullscreen(process.env, process.stdout, Boolean(process.stdin.isTTY));
106587
105687
  var session;
106588
105688
  if (fullscreen) {
106589
- const dir = path11.join(os5.homedir(), ".spacecode");
105689
+ const dir = path12.join(os6.homedir(), ".spacecode");
106590
105690
  fs5.mkdirSync(dir, { recursive: true });
106591
- const restoreConsole = redirectConsole(path11.join(dir, "repl.log"));
105691
+ const restoreConsole = redirectConsole(path12.join(dir, "repl.log"));
106592
105692
  session = enterFullscreen(process.stdout, process);
106593
105693
  session.onLeave(restoreConsole);
106594
105694
  session.onLeave(clearOnResize(process.stdout));