@proxysoul/soulforge 2.16.5 → 2.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -45146,7 +45146,17 @@ class SessionManager {
45146
45146
  const updatedTabs = updatedTabsRaw.map((t) => {
45147
45147
  let msgs;
45148
45148
  if (t.id === tabMeta.id) {
45149
- msgs = messages;
45149
+ if (tabIdx >= 0) {
45150
+ const prevRange = oldTabs[tabIdx]?.messageRange;
45151
+ const priorSlice = prevRange ? existingAllMessages.slice(prevRange.startLine, prevRange.endLine) : [];
45152
+ if (messages.length < priorSlice.length) {
45153
+ msgs = priorSlice;
45154
+ } else {
45155
+ msgs = messages;
45156
+ }
45157
+ } else {
45158
+ msgs = messages;
45159
+ }
45150
45160
  } else {
45151
45161
  const prevRange = t.messageRange;
45152
45162
  msgs = existingAllMessages.slice(prevRange.startLine, prevRange.endLine);
@@ -45195,6 +45205,97 @@ class SessionManager {
45195
45205
  await rename(coreTmp, corePath);
45196
45206
  }
45197
45207
  }
45208
+ async pruneTabsNotIn(sessionId, keepIds) {
45209
+ const sessionDir = join10(this.dir, sessionId);
45210
+ const metaPath = join10(sessionDir, "meta.json");
45211
+ if (!existsSync9(metaPath))
45212
+ return;
45213
+ const prev = this.saveChains.get(sessionId) ?? Promise.resolve();
45214
+ const next = prev.catch(() => {}).then(async () => {
45215
+ let meta3;
45216
+ try {
45217
+ meta3 = JSON.parse(readFileSync9(metaPath, "utf-8"));
45218
+ } catch {
45219
+ return;
45220
+ }
45221
+ const keptTabs = meta3.tabs.filter((t) => keepIds.has(t.id));
45222
+ if (keptTabs.length === meta3.tabs.length)
45223
+ return;
45224
+ const jsonlPath = join10(sessionDir, "messages.jsonl");
45225
+ const allMessages = [];
45226
+ if (existsSync9(jsonlPath)) {
45227
+ const content = readFileSync9(jsonlPath, "utf-8").trim();
45228
+ if (content) {
45229
+ for (const line of content.split(`
45230
+ `)) {
45231
+ if (!line.trim())
45232
+ continue;
45233
+ try {
45234
+ allMessages.push(JSON.parse(line));
45235
+ } catch {
45236
+ break;
45237
+ }
45238
+ }
45239
+ }
45240
+ }
45241
+ const rebuiltAll = [];
45242
+ const updatedTabs = keptTabs.map((t) => {
45243
+ const { startLine, endLine } = t.messageRange;
45244
+ const slice = allMessages.slice(startLine, endLine);
45245
+ const newStart = rebuiltAll.length;
45246
+ for (const m of slice)
45247
+ rebuiltAll.push(m);
45248
+ return { ...t, messageRange: { startLine: newStart, endLine: rebuiltAll.length } };
45249
+ });
45250
+ const updatedMeta = { ...meta3, tabs: updatedTabs, updatedAt: Date.now() };
45251
+ const corePath = join10(sessionDir, "core.json");
45252
+ let updatedCore = null;
45253
+ if (existsSync9(corePath)) {
45254
+ try {
45255
+ const coreData = JSON.parse(readFileSync9(corePath, "utf-8"));
45256
+ updatedCore = {};
45257
+ for (const id of keepIds) {
45258
+ if (coreData[id])
45259
+ updatedCore[id] = coreData[id];
45260
+ }
45261
+ } catch {
45262
+ updatedCore = null;
45263
+ }
45264
+ }
45265
+ const lines = rebuiltAll.map((m) => JSON.stringify(m)).join(`
45266
+ `);
45267
+ const suffix = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
45268
+ const metaTmp = `${metaPath}.${suffix}.tmp`;
45269
+ const jsonlTmp = `${jsonlPath}.${suffix}.tmp`;
45270
+ await writeFile(metaTmp, JSON.stringify(updatedMeta, null, 2), {
45271
+ encoding: "utf-8",
45272
+ mode: 384
45273
+ });
45274
+ await writeFile(jsonlTmp, lines ? `${lines}
45275
+ ` : "", {
45276
+ encoding: "utf-8",
45277
+ mode: 384
45278
+ });
45279
+ await rename(jsonlTmp, jsonlPath);
45280
+ await rename(metaTmp, metaPath);
45281
+ if (updatedCore) {
45282
+ const coreTmp = `${corePath}.${suffix}.tmp`;
45283
+ await writeFile(coreTmp, JSON.stringify(updatedCore), {
45284
+ encoding: "utf-8",
45285
+ mode: 384
45286
+ });
45287
+ await rename(coreTmp, corePath);
45288
+ }
45289
+ });
45290
+ this.saveChains.set(sessionId, next);
45291
+ try {
45292
+ await next;
45293
+ } finally {
45294
+ if (this.saveChains.get(sessionId) === next) {
45295
+ this.saveChains.delete(sessionId);
45296
+ }
45297
+ }
45298
+ }
45198
45299
  }
45199
45300
  var init_manager = __esm(() => {
45200
45301
  init_errors();
@@ -64339,7 +64440,7 @@ var package_default;
64339
64440
  var init_package = __esm(() => {
64340
64441
  package_default = {
64341
64442
  name: "@proxysoul/soulforge",
64342
- version: "2.16.5",
64443
+ version: "2.17.0",
64343
64444
  description: "Graph-powered code intelligence \u2014 multi-agent coding with codebase-aware AI",
64344
64445
  repository: {
64345
64446
  type: "git",
@@ -97616,7 +97717,7 @@ Speak once, at the end. Exceptions: (a) destructive/irreversible action needs co
97616
97717
 
97617
97718
  Reasoning is unchanged \u2014 think as deeply as the task needs, internally. Compression applies to OUTPUT only.
97618
97719
 
97619
- Commit boundary \u2014 when a turn uses 2+ tool calls (parallel batches count), call \`set_lockin({on:false})\` as the last tool immediately before the final answer. Skip on zero/one-tool turns. Never call before another tool.
97720
+ Commit boundary \u2014 when a turn uses 2+ tool calls (parallel batches count), the LAST tool call before your final answer text MUST be \`set_lockin({on:false})\`. Sequence is strict: [last real tool] \u2192 [set_lockin({on:false})] \u2192 [final answer text]. Never write prose, then call lockin \u2014 by then the answer is already streaming and the marker arrives too late. Skip lockin entirely on zero/one-tool turns.
97620
97721
  </tool_loop>
97621
97722
 
97622
97723
  <answer_voice>
@@ -98494,6 +98595,19 @@ var init_hooks = __esm(() => {
98494
98595
  init_tool_names();
98495
98596
  });
98496
98597
 
98598
+ // src/core/utils/hash.ts
98599
+ function hash32(s) {
98600
+ let h = 2166136261;
98601
+ for (let i2 = 0;i2 < s.length; i2++) {
98602
+ h ^= s.charCodeAt(i2);
98603
+ h = Math.imul(h, 16777619);
98604
+ }
98605
+ return h >>> 0;
98606
+ }
98607
+ function hash32Hex(s) {
98608
+ return hash32(s).toString(16).padStart(8, "0");
98609
+ }
98610
+
98497
98611
  // src/core/memory/embedder.ts
98498
98612
  var exports_embedder = {};
98499
98613
  __export(exports_embedder, {
@@ -98562,18 +98676,10 @@ function charNgrams(text2, n) {
98562
98676
  out2.push(compact.slice(i2, i2 + n));
98563
98677
  return out2;
98564
98678
  }
98565
- function hashToken(token) {
98566
- let h = 2166136261;
98567
- for (let i2 = 0;i2 < token.length; i2++) {
98568
- h ^= token.charCodeAt(i2);
98569
- h = Math.imul(h, 16777619);
98570
- }
98571
- return h >>> 0;
98572
- }
98573
98679
  function project(vec, token, weight, dim) {
98574
- const h = hashToken(token);
98680
+ const h = hash32(token);
98575
98681
  const bin = h % dim;
98576
- const sign = (hashToken(`s:${token}`) & 1) === 0 ? 1 : -1;
98682
+ const sign = (hash32(`s:${token}`) & 1) === 0 ? 1 : -1;
98577
98683
  vec[bin] = (vec[bin] ?? 0) + sign * weight;
98578
98684
  }
98579
98685
  function embed2(text2, dim = EMBED_DIM) {
@@ -99215,6 +99321,9 @@ ${normalize(details)}`;
99215
99321
  }
99216
99322
  }
99217
99323
  close() {
99324
+ try {
99325
+ this.db.run("PRAGMA optimize");
99326
+ } catch {}
99218
99327
  this.db.close();
99219
99328
  }
99220
99329
  findDuplicates() {
@@ -99536,6 +99645,32 @@ ${normalize(details)}`;
99536
99645
  }
99537
99646
  } catch {}
99538
99647
  }
99648
+ topCategoriesByPath(paths) {
99649
+ const out2 = new Map;
99650
+ if (paths.length === 0)
99651
+ return out2;
99652
+ const placeholders = paths.map(() => "?").join(",");
99653
+ const rows = this.db.query(`SELECT mf.path, m.category, m.pinned
99654
+ FROM memories m
99655
+ JOIN memory_files mf ON mf.memory_id = m.id
99656
+ WHERE mf.path IN (${placeholders}) AND m.hidden = 0`).all(...paths);
99657
+ const priority = { gotcha: 4, pref: 3, decision: 2, context: 1 };
99658
+ for (const r of rows) {
99659
+ const cur = out2.get(r.path);
99660
+ if (!cur) {
99661
+ out2.set(r.path, { category: r.category, count: 1, pinned: r.pinned });
99662
+ continue;
99663
+ }
99664
+ cur.count += 1;
99665
+ if (r.pinned > cur.pinned)
99666
+ cur.pinned = r.pinned;
99667
+ const a = cur.category ? priority[cur.category] ?? 0 : 0;
99668
+ const b = r.category ? priority[r.category] ?? 0 : 0;
99669
+ if (b > a)
99670
+ cur.category = r.category;
99671
+ }
99672
+ return out2;
99673
+ }
99539
99674
  }
99540
99675
  function normalize(s) {
99541
99676
  return s.replace(/\s+/g, " ").trim().toLowerCase();
@@ -100322,16 +100457,25 @@ var init_cache = () => {};
100322
100457
 
100323
100458
  // src/core/intelligence/types.ts
100324
100459
  function detectLanguageFromPath(file2) {
100325
- const dot = file2.lastIndexOf(".");
100326
- if (dot === -1) {
100327
- const name38 = file2.slice(file2.lastIndexOf("/") + 1);
100328
- if (name38 === "Dockerfile" || name38.startsWith("Dockerfile."))
100329
- return "dockerfile";
100460
+ const slash = file2.lastIndexOf("/");
100461
+ const base = (slash === -1 ? file2 : file2.slice(slash + 1)).toLowerCase();
100462
+ const bare = BARE_FILENAME_TO_LANGUAGE[base];
100463
+ if (bare)
100464
+ return bare;
100465
+ if (base.startsWith("dockerfile."))
100466
+ return "dockerfile";
100467
+ if (base.startsWith("containerfile."))
100468
+ return "dockerfile";
100469
+ if (base.startsWith("makefile."))
100470
+ return "makefile";
100471
+ if (base.startsWith("justfile."))
100472
+ return "just";
100473
+ const dot = base.lastIndexOf(".");
100474
+ if (dot === -1)
100330
100475
  return "unknown";
100331
- }
100332
- return EXT_TO_LANGUAGE[file2.slice(dot).toLowerCase()] ?? "unknown";
100476
+ return EXT_TO_LANGUAGE[base.slice(dot)] ?? "unknown";
100333
100477
  }
100334
- var EXT_TO_LANGUAGE;
100478
+ var EXT_TO_LANGUAGE, BARE_FILENAME_TO_LANGUAGE;
100335
100479
  var init_types2 = __esm(() => {
100336
100480
  EXT_TO_LANGUAGE = {
100337
100481
  ".ts": "typescript",
@@ -100385,7 +100529,35 @@ var init_types2 = __esm(() => {
100385
100529
  ".toml": "toml",
100386
100530
  ".yaml": "yaml",
100387
100531
  ".yml": "yaml",
100532
+ ".xml": "xml",
100533
+ ".md": "markdown",
100534
+ ".markdown": "markdown",
100535
+ ".mdx": "mdx",
100536
+ ".sql": "sql",
100537
+ ".graphql": "graphql",
100538
+ ".gql": "graphql",
100539
+ ".proto": "proto",
100540
+ ".env": "env",
100541
+ ".ini": "ini",
100542
+ ".cfg": "ini",
100543
+ ".conf": "ini",
100544
+ ".properties": "properties",
100388
100545
  ".dockerfile": "dockerfile",
100546
+ ".mk": "makefile",
100547
+ ".nix": "nix",
100548
+ ".hcl": "hcl",
100549
+ ".tf": "hcl",
100550
+ ".tfvars": "hcl",
100551
+ ".bzl": "bazel",
100552
+ ".star": "bazel",
100553
+ ".bazel": "bazel",
100554
+ ".jsonnet": "jsonnet",
100555
+ ".libsonnet": "jsonnet",
100556
+ ".svg": "svg",
100557
+ ".csv": "csv",
100558
+ ".tsv": "csv",
100559
+ ".lock": "lockfile",
100560
+ ".lockb": "lockfile",
100389
100561
  ".vue": "vue",
100390
100562
  ".res": "rescript",
100391
100563
  ".resi": "rescript",
@@ -100393,6 +100565,39 @@ var init_types2 = __esm(() => {
100393
100565
  ".tla": "tlaplus",
100394
100566
  ".el": "elisp"
100395
100567
  };
100568
+ BARE_FILENAME_TO_LANGUAGE = {
100569
+ dockerfile: "dockerfile",
100570
+ containerfile: "dockerfile",
100571
+ makefile: "makefile",
100572
+ gnumakefile: "makefile",
100573
+ justfile: "just",
100574
+ ".justfile": "just",
100575
+ build: "bazel",
100576
+ "build.bazel": "bazel",
100577
+ workspace: "bazel",
100578
+ "workspace.bazel": "bazel",
100579
+ "module.bazel": "bazel",
100580
+ ".env": "env",
100581
+ ".gitignore": "ignore",
100582
+ ".dockerignore": "ignore",
100583
+ ".npmignore": "ignore",
100584
+ ".prettierignore": "ignore",
100585
+ ".eslintignore": "ignore",
100586
+ ".editorconfig": "ini",
100587
+ "bun.lock": "lockfile",
100588
+ "bun.lockb": "lockfile",
100589
+ "package-lock.json": "lockfile",
100590
+ "pnpm-lock.yaml": "lockfile",
100591
+ "yarn.lock": "lockfile",
100592
+ "cargo.lock": "lockfile",
100593
+ "poetry.lock": "lockfile",
100594
+ "composer.lock": "lockfile",
100595
+ "gemfile.lock": "lockfile",
100596
+ "go.sum": "lockfile",
100597
+ gemfile: "ruby",
100598
+ rakefile: "ruby",
100599
+ podfile: "ruby"
100600
+ };
100396
100601
  });
100397
100602
 
100398
100603
  // src/core/intelligence/backends/lsp/protocol.ts
@@ -358647,7 +358852,17 @@ var init_forbidden = __esm(() => {
358647
358852
  // src/core/intelligence/repo-map-utils.ts
358648
358853
  import { readdir as readdir2, stat as stat2 } from "fs/promises";
358649
358854
  import { homedir as homedir20 } from "os";
358650
- import { extname as extname2, join as join24, resolve as resolve12 } from "path";
358855
+ import { join as join24, resolve as resolve12 } from "path";
358856
+ function isIndexablePath(file2) {
358857
+ return detectLanguageFromPath(file2) !== "unknown" || isBareIndexable(file2);
358858
+ }
358859
+ function isBareIndexable(file2) {
358860
+ const slash = file2.lastIndexOf("/");
358861
+ const base = (slash === -1 ? file2 : file2.slice(slash + 1)).toLowerCase();
358862
+ if (BARE_FILENAME_TO_LANGUAGE[base])
358863
+ return true;
358864
+ return base.startsWith("dockerfile.") || base.startsWith("containerfile.") || base.startsWith("makefile.") || base.startsWith("justfile.");
358865
+ }
358651
358866
  function barrelToDir(barrelPath) {
358652
358867
  return barrelPath.replace(BARREL_RE, "");
358653
358868
  }
@@ -358860,8 +359075,7 @@ async function collectFilesViaGit(dir) {
358860
359075
  `)) {
358861
359076
  if (!line)
358862
359077
  continue;
358863
- const ext = extname2(line).toLowerCase();
358864
- if (!(ext in INDEXABLE_EXTENSIONS))
359078
+ if (!isIndexablePath(line))
358865
359079
  continue;
358866
359080
  const fullPath = join24(dir, line);
358867
359081
  if (isForbidden(fullPath))
@@ -358888,8 +359102,10 @@ async function collectFilesWalk(dir, depth, counter, out2) {
358888
359102
  for (const entry of await readdir2(dir, { withFileTypes: true })) {
358889
359103
  if (ctx.n >= WALK_FILE_CAP)
358890
359104
  break;
358891
- if (entry.name.startsWith(".") && entry.name !== ".")
358892
- continue;
359105
+ if (entry.name.startsWith(".") && entry.name !== ".") {
359106
+ if (!entry.isFile() || !isIndexablePath(entry.name))
359107
+ continue;
359108
+ }
358893
359109
  const fullPath = join24(dir, entry.name);
358894
359110
  if (entry.isDirectory()) {
358895
359111
  if (!IGNORED_DIRS.has(entry.name)) {
@@ -358898,8 +359114,7 @@ async function collectFilesWalk(dir, depth, counter, out2) {
358898
359114
  } else if (entry.isFile()) {
358899
359115
  if (isForbidden(fullPath))
358900
359116
  continue;
358901
- const ext = extname2(entry.name).toLowerCase();
358902
- if (ext in INDEXABLE_EXTENSIONS) {
359117
+ if (isIndexablePath(entry.name)) {
358903
359118
  try {
358904
359119
  const s = await stat2(fullPath);
358905
359120
  if (s.size < MAX_FILE_SIZE) {
@@ -358919,84 +359134,35 @@ var INDEXABLE_EXTENSIONS, NON_CODE_LANGUAGES, IMPORT_TRACKABLE_LANGUAGES, BARREL
358919
359134
  var init_repo_map_utils = __esm(() => {
358920
359135
  init_file_tree();
358921
359136
  init_forbidden();
358922
- INDEXABLE_EXTENSIONS = {
358923
- ".ts": "typescript",
358924
- ".tsx": "typescript",
358925
- ".mts": "typescript",
358926
- ".cts": "typescript",
358927
- ".js": "javascript",
358928
- ".jsx": "javascript",
358929
- ".mjs": "javascript",
358930
- ".cjs": "javascript",
358931
- ".py": "python",
358932
- ".go": "go",
358933
- ".rs": "rust",
358934
- ".java": "java",
358935
- ".c": "c",
358936
- ".h": "c",
358937
- ".cpp": "cpp",
358938
- ".cc": "cpp",
358939
- ".cxx": "cpp",
358940
- ".hpp": "cpp",
358941
- ".hh": "cpp",
358942
- ".hxx": "cpp",
358943
- ".cs": "csharp",
358944
- ".rb": "ruby",
358945
- ".php": "php",
358946
- ".swift": "swift",
358947
- ".kt": "kotlin",
358948
- ".kts": "kotlin",
358949
- ".scala": "scala",
358950
- ".sc": "scala",
358951
- ".lua": "lua",
358952
- ".ex": "elixir",
358953
- ".exs": "elixir",
358954
- ".dart": "dart",
358955
- ".zig": "zig",
358956
- ".sh": "bash",
358957
- ".bash": "bash",
358958
- ".zsh": "bash",
358959
- ".ml": "ocaml",
358960
- ".mli": "ocaml",
358961
- ".m": "objc",
358962
- ".el": "elisp",
358963
- ".res": "rescript",
358964
- ".resi": "rescript",
358965
- ".sol": "solidity",
358966
- ".tla": "tlaplus",
358967
- ".vue": "vue",
358968
- ".pyw": "python",
358969
- ".erb": "ruby",
358970
- ".json": "unknown",
358971
- ".jsonc": "unknown",
358972
- ".yaml": "unknown",
358973
- ".yml": "unknown",
358974
- ".toml": "unknown",
358975
- ".xml": "unknown",
358976
- ".md": "unknown",
358977
- ".css": "css",
358978
- ".scss": "css",
358979
- ".less": "css",
358980
- ".html": "html",
358981
- ".htm": "html",
358982
- ".sql": "unknown",
358983
- ".graphql": "unknown",
358984
- ".gql": "unknown",
358985
- ".proto": "unknown",
358986
- ".env": "unknown",
358987
- ".conf": "unknown",
358988
- ".ini": "unknown",
358989
- ".cfg": "unknown",
358990
- ".dockerfile": "unknown"
358991
- };
359137
+ init_types2();
359138
+ INDEXABLE_EXTENSIONS = EXT_TO_LANGUAGE;
358992
359139
  NON_CODE_LANGUAGES = new Set([
358993
359140
  "unknown",
358994
359141
  "css",
358995
359142
  "html",
358996
359143
  "json",
359144
+ "jsonnet",
358997
359145
  "toml",
358998
359146
  "yaml",
358999
- "dockerfile"
359147
+ "xml",
359148
+ "markdown",
359149
+ "mdx",
359150
+ "sql",
359151
+ "graphql",
359152
+ "proto",
359153
+ "properties",
359154
+ "ini",
359155
+ "env",
359156
+ "dockerfile",
359157
+ "makefile",
359158
+ "nix",
359159
+ "hcl",
359160
+ "bazel",
359161
+ "just",
359162
+ "svg",
359163
+ "csv",
359164
+ "ignore",
359165
+ "lockfile"
359000
359166
  ]);
359001
359167
  IMPORT_TRACKABLE_LANGUAGES = new Set([
359002
359168
  "typescript",
@@ -359034,9 +359200,9 @@ var init_repo_map_utils = __esm(() => {
359034
359200
  // src/core/intelligence/repo-map.ts
359035
359201
  import { Database as Database2 } from "bun:sqlite";
359036
359202
  import { execSync as execSync6 } from "child_process";
359037
- import { chmodSync as chmodSync3, existsSync as existsSync22, readFileSync as readFileSync16, statSync as statSync3 } from "fs";
359203
+ import { chmodSync as chmodSync3, existsSync as existsSync22, readdirSync as readdirSync7, readFileSync as readFileSync16, statSync as statSync3 } from "fs";
359038
359204
  import { stat as statAsync } from "fs/promises";
359039
- import { dirname as dirname8, extname as extname3, join as join25, relative as relative2, resolve as resolve13 } from "path";
359205
+ import { dirname as dirname8, extname as extname2, join as join25, relative as relative2, resolve as resolve13 } from "path";
359040
359206
 
359041
359207
  class RepoMap {
359042
359208
  static testFileMatch(alias = "f") {
@@ -359133,6 +359299,7 @@ class RepoMap {
359133
359299
  weight REAL NOT NULL DEFAULT 1.0,
359134
359300
  PRIMARY KEY (source_file_id, target_file_id)
359135
359301
  );
359302
+ CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_file_id);
359136
359303
  `);
359137
359304
  this.db.run(`
359138
359305
  CREATE TABLE IF NOT EXISTS refs (
@@ -359363,8 +359530,7 @@ class RepoMap {
359363
359530
  const existing = existingFiles.get(relPath);
359364
359531
  if (existing && existing.mtime_ms === file2.mtimeMs)
359365
359532
  continue;
359366
- const ext = extname3(file2.path).toLowerCase();
359367
- const language = INDEXABLE_EXTENSIONS[ext] ?? "unknown";
359533
+ const language = detectLanguageFromPath(file2.path);
359368
359534
  toIndex.push({ absPath: file2.path, relPath, mtime: file2.mtimeMs, language });
359369
359535
  }
359370
359536
  const stale = [...existingFiles.keys()].filter((p) => !currentPaths.has(p));
@@ -359868,7 +360034,7 @@ class RepoMap {
359868
360034
  const candidates = [base];
359869
360035
  if (stripped !== relBase)
359870
360036
  candidates.push(join25(this.cwd, relBase));
359871
- const ext = extname3(stripped);
360037
+ const ext = extname2(stripped);
359872
360038
  if (!ext) {
359873
360039
  for (const tryExt of Object.keys(INDEXABLE_EXTENSIONS)) {
359874
360040
  candidates.push(base + tryExt);
@@ -360306,15 +360472,18 @@ class RepoMap {
360306
360472
  }
360307
360473
  async computePageRank(personalization) {
360308
360474
  const tick = () => new Promise((r) => setTimeout(r, 1));
360309
- const files = this.db.query("SELECT id FROM files").all();
360475
+ const files = this.db.query("SELECT id, pagerank, language FROM files").all();
360310
360476
  if (files.length === 0)
360311
360477
  return;
360312
360478
  const n = files.length;
360313
360479
  const idToIdx = new Map;
360314
360480
  const ids = [];
360481
+ const isCode = new Array(n);
360315
360482
  for (const file2 of files) {
360316
- idToIdx.set(file2.id, ids.length);
360483
+ const idx = ids.length;
360484
+ idToIdx.set(file2.id, idx);
360317
360485
  ids.push(file2.id);
360486
+ isCode[idx] = !NON_CODE_LANGUAGES.has(file2.language);
360318
360487
  }
360319
360488
  const outWeight = new Array(n).fill(0);
360320
360489
  const adj = [];
@@ -360330,34 +360499,51 @@ class RepoMap {
360330
360499
  }
360331
360500
  }
360332
360501
  const pv = new Float64Array(n);
360333
- const uniform = 1 / n;
360502
+ let codeCount = 0;
360503
+ for (let i2 = 0;i2 < n; i2++)
360504
+ if (isCode[i2])
360505
+ codeCount++;
360506
+ const uniform = codeCount > 0 ? 1 / codeCount : 1 / n;
360334
360507
  if (personalization && personalization.size > 0) {
360335
360508
  let boostSum = 0;
360336
360509
  for (const [fileId, boost] of personalization) {
360337
360510
  const idx = idToIdx.get(fileId);
360338
- if (idx !== undefined) {
360511
+ if (idx !== undefined && isCode[idx]) {
360339
360512
  pv[idx] = boost;
360340
360513
  boostSum += boost;
360341
360514
  }
360342
360515
  }
360343
360516
  if (boostSum > 0) {
360344
360517
  for (let i2 = 0;i2 < n; i2++) {
360345
- pv[i2] = 0.7 * uniform + 0.3 * ((pv[i2] ?? 0) / boostSum);
360518
+ pv[i2] = isCode[i2] ? 0.7 * uniform + 0.3 * ((pv[i2] ?? 0) / boostSum) : 0;
360346
360519
  }
360347
360520
  } else {
360348
- pv.fill(uniform);
360521
+ for (let i2 = 0;i2 < n; i2++)
360522
+ pv[i2] = isCode[i2] ? uniform : 0;
360349
360523
  }
360350
360524
  } else {
360351
- pv.fill(uniform);
360525
+ for (let i2 = 0;i2 < n; i2++)
360526
+ pv[i2] = isCode[i2] ? uniform : 0;
360527
+ }
360528
+ let priorSum = 0;
360529
+ for (let i2 = 0;i2 < n; i2++)
360530
+ if (isCode[i2])
360531
+ priorSum += files[i2]?.pagerank || 0;
360532
+ let rank = new Float64Array(n);
360533
+ if (priorSum > 0.5 && priorSum < 1.5) {
360534
+ for (let i2 = 0;i2 < n; i2++)
360535
+ rank[i2] = isCode[i2] ? files[i2]?.pagerank ?? uniform : 0;
360536
+ } else {
360537
+ for (let i2 = 0;i2 < n; i2++)
360538
+ rank[i2] = isCode[i2] ? uniform : 0;
360352
360539
  }
360353
- let rank = new Float64Array(n).fill(1 / n);
360354
360540
  let next = new Float64Array(n);
360355
360541
  for (let iter = 0;iter < PAGERANK_ITERATIONS; iter++) {
360356
360542
  for (let j = 0;j < n; j++)
360357
360543
  next[j] = (1 - PAGERANK_DAMPING) * (pv[j] ?? 0);
360358
360544
  let danglingSum = 0;
360359
360545
  for (let i2 = 0;i2 < n; i2++) {
360360
- if ((outWeight[i2] ?? 0) === 0)
360546
+ if (isCode[i2] && (outWeight[i2] ?? 0) === 0)
360361
360547
  danglingSum += rank[i2] ?? 0;
360362
360548
  }
360363
360549
  for (let j = 0;j < n; j++) {
@@ -360367,9 +360553,14 @@ class RepoMap {
360367
360553
  const contribution = PAGERANK_DAMPING * (rank[from] ?? 0) * weight / (outWeight[from] ?? 1);
360368
360554
  next[to] = (next[to] ?? 0) + contribution;
360369
360555
  }
360556
+ let delta = 0;
360557
+ for (let i2 = 0;i2 < n; i2++)
360558
+ delta += Math.abs((next[i2] ?? 0) - (rank[i2] ?? 0));
360370
360559
  [rank, next] = [next, rank];
360371
360560
  if (iter % 5 === 4)
360372
360561
  await tick();
360562
+ if (delta < 0.000001)
360563
+ break;
360373
360564
  }
360374
360565
  const update = this.db.prepare("UPDATE files SET pagerank = ? WHERE id = ?");
360375
360566
  const tx = this.db.transaction(() => {
@@ -360382,15 +360573,18 @@ class RepoMap {
360382
360573
  } catch {}
360383
360574
  }
360384
360575
  computePageRankSync(personalization) {
360385
- const files = this.db.query("SELECT id FROM files").all();
360576
+ const files = this.db.query("SELECT id, pagerank, language FROM files").all();
360386
360577
  if (files.length === 0)
360387
360578
  return;
360388
360579
  const n = files.length;
360389
360580
  const idToIdx = new Map;
360390
360581
  const ids = [];
360582
+ const isCode = new Array(n);
360391
360583
  for (const file2 of files) {
360392
- idToIdx.set(file2.id, ids.length);
360584
+ const idx = ids.length;
360585
+ idToIdx.set(file2.id, idx);
360393
360586
  ids.push(file2.id);
360587
+ isCode[idx] = !NON_CODE_LANGUAGES.has(file2.language);
360394
360588
  }
360395
360589
  const outWeight = new Array(n).fill(0);
360396
360590
  const adj = [];
@@ -360406,34 +360600,51 @@ class RepoMap {
360406
360600
  }
360407
360601
  }
360408
360602
  const pv = new Float64Array(n);
360409
- const uniform = 1 / n;
360603
+ let codeCount = 0;
360604
+ for (let i2 = 0;i2 < n; i2++)
360605
+ if (isCode[i2])
360606
+ codeCount++;
360607
+ const uniform = codeCount > 0 ? 1 / codeCount : 1 / n;
360410
360608
  if (personalization && personalization.size > 0) {
360411
360609
  let boostSum = 0;
360412
360610
  for (const [fileId, boost] of personalization) {
360413
360611
  const idx = idToIdx.get(fileId);
360414
- if (idx !== undefined) {
360612
+ if (idx !== undefined && isCode[idx]) {
360415
360613
  pv[idx] = boost;
360416
360614
  boostSum += boost;
360417
360615
  }
360418
360616
  }
360419
360617
  if (boostSum > 0) {
360420
360618
  for (let i2 = 0;i2 < n; i2++) {
360421
- pv[i2] = 0.7 * uniform + 0.3 * ((pv[i2] ?? 0) / boostSum);
360619
+ pv[i2] = isCode[i2] ? 0.7 * uniform + 0.3 * ((pv[i2] ?? 0) / boostSum) : 0;
360422
360620
  }
360423
360621
  } else {
360424
- pv.fill(uniform);
360622
+ for (let i2 = 0;i2 < n; i2++)
360623
+ pv[i2] = isCode[i2] ? uniform : 0;
360425
360624
  }
360426
360625
  } else {
360427
- pv.fill(uniform);
360626
+ for (let i2 = 0;i2 < n; i2++)
360627
+ pv[i2] = isCode[i2] ? uniform : 0;
360628
+ }
360629
+ let priorSum = 0;
360630
+ for (let i2 = 0;i2 < n; i2++)
360631
+ if (isCode[i2])
360632
+ priorSum += files[i2]?.pagerank || 0;
360633
+ let rank = new Float64Array(n);
360634
+ if (priorSum > 0.5 && priorSum < 1.5) {
360635
+ for (let i2 = 0;i2 < n; i2++)
360636
+ rank[i2] = isCode[i2] ? files[i2]?.pagerank ?? uniform : 0;
360637
+ } else {
360638
+ for (let i2 = 0;i2 < n; i2++)
360639
+ rank[i2] = isCode[i2] ? uniform : 0;
360428
360640
  }
360429
- let rank = new Float64Array(n).fill(1 / n);
360430
360641
  let next = new Float64Array(n);
360431
360642
  for (let iter = 0;iter < PAGERANK_ITERATIONS; iter++) {
360432
360643
  for (let j = 0;j < n; j++)
360433
360644
  next[j] = (1 - PAGERANK_DAMPING) * (pv[j] ?? 0);
360434
360645
  let danglingSum = 0;
360435
360646
  for (let i2 = 0;i2 < n; i2++) {
360436
- if ((outWeight[i2] ?? 0) === 0)
360647
+ if (isCode[i2] && (outWeight[i2] ?? 0) === 0)
360437
360648
  danglingSum += rank[i2] ?? 0;
360438
360649
  }
360439
360650
  for (let j = 0;j < n; j++) {
@@ -360443,7 +360654,12 @@ class RepoMap {
360443
360654
  const contribution = PAGERANK_DAMPING * (rank[from] ?? 0) * weight / (outWeight[from] ?? 1);
360444
360655
  next[to] = (next[to] ?? 0) + contribution;
360445
360656
  }
360657
+ let delta = 0;
360658
+ for (let i2 = 0;i2 < n; i2++)
360659
+ delta += Math.abs((next[i2] ?? 0) - (rank[i2] ?? 0));
360446
360660
  [rank, next] = [next, rank];
360661
+ if (delta < 0.000001)
360662
+ break;
360447
360663
  }
360448
360664
  const update = this.db.prepare("UPDATE files SET pagerank = ? WHERE id = ?");
360449
360665
  const tx = this.db.transaction(() => {
@@ -360602,15 +360818,36 @@ class RepoMap {
360602
360818
  "manage.py",
360603
360819
  "src/main/java/Main.java",
360604
360820
  "src/main/kotlin/Main.kt",
360821
+ "src/main/scala/Main.scala",
360605
360822
  "Sources/main.swift",
360606
360823
  "Sources/App.swift",
360607
360824
  "src/main.c",
360608
360825
  "src/main.cpp",
360826
+ "Program.cs",
360609
360827
  "lib/main.dart",
360610
360828
  "lib/application.ex",
360611
360829
  "app.rb",
360612
- "config.ru"
360830
+ "config.ru",
360831
+ "public/index.php",
360832
+ "artisan",
360833
+ "bin/console",
360834
+ "index.php",
360835
+ "src/main.zig",
360836
+ "build.zig",
360837
+ "app/Main.hs",
360838
+ "src/Main.hs",
360839
+ "Main.hs",
360840
+ "src/Main.elm"
360613
360841
  ];
360842
+ try {
360843
+ const binsDir = join25(this.cwd, "src", "bin");
360844
+ if (existsSync22(binsDir)) {
360845
+ for (const entry of readdirSync7(binsDir)) {
360846
+ if (entry.endsWith(".rs"))
360847
+ commonEntryPoints.push(`src/bin/${entry}`);
360848
+ }
360849
+ }
360850
+ } catch {}
360614
360851
  for (const p of commonEntryPoints) {
360615
360852
  if (existsSync22(join25(this.cwd, p)))
360616
360853
  this.entryPointsCache.push(p);
@@ -360946,7 +361183,7 @@ class RepoMap {
360946
361183
  if (relPath === "package.json" || relPath === "Cargo.toml" || relPath === "go.mod") {
360947
361184
  this.entryPointsCache = null;
360948
361185
  }
360949
- const ext = extname3(absPath).toLowerCase();
361186
+ const ext = extname2(absPath).toLowerCase();
360950
361187
  const language = INDEXABLE_EXTENSIONS[ext];
360951
361188
  if (!language)
360952
361189
  return;
@@ -361538,10 +361775,14 @@ class RepoMap {
361538
361775
  const blastRadius = this.db.query("SELECT COUNT(DISTINCT source_file_id) AS c FROM edges WHERE target_file_id = ?").get(fileRow.id)?.c ?? 0;
361539
361776
  const symbols = this.db.query(`SELECT s.name, s.kind, s.signature, s.line
361540
361777
  FROM symbols s
361778
+ LEFT JOIN (
361779
+ SELECT callee_symbol_id, COUNT(*) AS c FROM calls
361780
+ WHERE callee_symbol_id IS NOT NULL GROUP BY callee_symbol_id
361781
+ ) cc ON cc.callee_symbol_id = s.id
361541
361782
  WHERE s.file_id = ?
361542
361783
  AND s.is_exported = 1
361543
361784
  AND s.kind IN ('interface','type','class','function','enum','method')
361544
- ORDER BY s.line
361785
+ ORDER BY COALESCE(cc.c, 0) DESC, s.line ASC
361545
361786
  LIMIT 10`).all(fileRow.id);
361546
361787
  return { blastRadius, symbols };
361547
361788
  }
@@ -361704,7 +361945,7 @@ class RepoMap {
361704
361945
  ORDER BY f.pagerank DESC
361705
361946
  LIMIT ?`).all(limit);
361706
361947
  const trackable = rows.filter((row) => {
361707
- const ext = extname3(row.path).toLowerCase();
361948
+ const ext = extname2(row.path).toLowerCase();
361708
361949
  const lang = INDEXABLE_EXTENSIONS[ext];
361709
361950
  return lang != null && IMPORT_TRACKABLE_LANGUAGES.has(lang);
361710
361951
  });
@@ -362418,6 +362659,9 @@ class RepoMap {
362418
362659
  this.onError?.(`error awaiting pending flush during close: ${e instanceof Error ? e.message : String(e)}`);
362419
362660
  }
362420
362661
  }
362662
+ try {
362663
+ this.db.run("PRAGMA optimize");
362664
+ } catch {}
362421
362665
  this.db.close();
362422
362666
  }
362423
362667
  metaGet(key) {
@@ -362469,6 +362713,7 @@ var init_repo_map = __esm(() => {
362469
362713
  init_clone_detection();
362470
362714
  init_repo_map_constants();
362471
362715
  init_repo_map_utils();
362716
+ init_types2();
362472
362717
  });
362473
362718
 
362474
362719
  // src/core/intelligence/index.ts
@@ -363142,6 +363387,34 @@ function memoryHintComposite(opts) {
363142
363387
  return "";
363143
363388
  }
363144
363389
  }
363390
+ function memoryMarkersForPaths(paths) {
363391
+ const result = new Map;
363392
+ if (!_manager || paths.length === 0)
363393
+ return result;
363394
+ try {
363395
+ const projectDb = _manager.getDbForScope("project");
363396
+ const globalDb = _manager.getDbForScope("global");
363397
+ const project2 = projectDb.topCategoriesByPath(paths);
363398
+ const global2 = globalDb.topCategoriesByPath(paths);
363399
+ const priority = { gotcha: 4, pref: 3, decision: 2, context: 1 };
363400
+ for (const path of paths) {
363401
+ const p = project2.get(path);
363402
+ const g = global2.get(path);
363403
+ if (!p && !g)
363404
+ continue;
363405
+ const count = (p?.count ?? 0) + (g?.count ?? 0);
363406
+ const pinned = (p?.pinned ?? 0) + (g?.pinned ?? 0) > 0;
363407
+ const a = p?.category ? priority[p.category] ?? 0 : 0;
363408
+ const b = g?.category ? priority[g.category] ?? 0 : 0;
363409
+ const category = b > a ? g?.category ?? null : p?.category ?? g?.category ?? null;
363410
+ result.set(path, { category, count, pinned });
363411
+ }
363412
+ return result;
363413
+ } catch (err2) {
363414
+ reportHintError("paths", err2);
363415
+ return result;
363416
+ }
363417
+ }
363145
363418
  var _manager = null, GLOBAL_TAB = "__global__", _tabs, _scope, SUMMARY_MAX = 60, COOLDOWN_TURNS = 10, SESSION_BUDGET = 60, SUBAGENT_BUDGET = 10, LOUD_LINES_MAX = 3;
363146
363419
  var init_hints = __esm(() => {
363147
363420
  init_errors();
@@ -363150,6 +363423,10 @@ var init_hints = __esm(() => {
363150
363423
  });
363151
363424
 
363152
363425
  // src/core/tools/file-events.ts
363426
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
363427
+ function runWithEditOrigin(origin, fn) {
363428
+ return _editOriginScope.run(origin, fn);
363429
+ }
363153
363430
  function onFileEdited(cb) {
363154
363431
  editListeners.add(cb);
363155
363432
  return () => {
@@ -363169,8 +363446,9 @@ function onCacheReset(cb) {
363169
363446
  };
363170
363447
  }
363171
363448
  function emitFileEdited(absPath, content) {
363449
+ const origin = _editOriginScope.getStore() ?? null;
363172
363450
  for (const cb of editListeners)
363173
- cb(absPath, content);
363451
+ cb(absPath, content, origin);
363174
363452
  }
363175
363453
  function emitFileRead(absPath) {
363176
363454
  for (const cb of readListeners)
@@ -363180,11 +363458,12 @@ function emitCacheReset() {
363180
363458
  for (const cb of cacheResetListeners)
363181
363459
  cb();
363182
363460
  }
363183
- var editListeners, readListeners, cacheResetListeners;
363461
+ var editListeners, readListeners, cacheResetListeners, _editOriginScope;
363184
363462
  var init_file_events = __esm(() => {
363185
363463
  editListeners = new Set;
363186
363464
  readListeners = new Set;
363187
363465
  cacheResetListeners = new Set;
363466
+ _editOriginScope = new AsyncLocalStorage2;
363188
363467
  });
363189
363468
 
363190
363469
  // src/core/tools/edit-stack.ts
@@ -383046,7 +383325,7 @@ var init_lib2 = () => {};
383046
383325
 
383047
383326
  // src/core/tools/binary-detect.ts
383048
383327
  import { existsSync as existsSync23, statSync as statSync5 } from "fs";
383049
- import { extname as extname4, resolve as resolve26 } from "path";
383328
+ import { extname as extname3, resolve as resolve26 } from "path";
383050
383329
  function binaryHint(ext) {
383051
383330
  if (IMAGE_EXTS.has(ext))
383052
383331
  return " This is an image file. Describe what you need from it or ask the user to describe its contents.";
@@ -383073,7 +383352,7 @@ function checkBinaryFile(filePath) {
383073
383352
  return null;
383074
383353
  if (!isBinaryFileSync(filePath))
383075
383354
  return null;
383076
- const ext = extname4(filePath).toLowerCase();
383355
+ const ext = extname3(filePath).toLowerCase();
383077
383356
  const sizeStr = stat5.size > 1024 * 1024 ? `${(stat5.size / (1024 * 1024)).toFixed(1)}MB` : `${(stat5.size / 1024).toFixed(0)}KB`;
383078
383357
  return `Cannot read binary file: "${filePath}" (${ext || "no extension"}, ${sizeStr}).${binaryHint(ext)}`;
383079
383358
  } catch {
@@ -383144,7 +383423,7 @@ var init_binary_detect = __esm(() => {
383144
383423
 
383145
383424
  // src/core/tools/read-file.ts
383146
383425
  import { access as access4, stat as statAsync7 } from "fs/promises";
383147
- import { extname as extname5, resolve as resolve27 } from "path";
383426
+ import { extname as extname4, resolve as resolve27 } from "path";
383148
383427
  function toRelPath(abs) {
383149
383428
  const cwd2 = process.cwd();
383150
383429
  return abs.startsWith(`${cwd2}/`) ? abs.slice(cwd2.length + 1) : abs;
@@ -383265,7 +383544,7 @@ async function readOnMainThread(filePath, args2) {
383265
383544
  };
383266
383545
  }
383267
383546
  if (await isBinaryFile(filePath)) {
383268
- const ext = extname5(filePath).toLowerCase();
383547
+ const ext = extname4(filePath).toLowerCase();
383269
383548
  const sizeStr = fileStat.size > 1024 * 1024 ? `${(fileStat.size / (1024 * 1024)).toFixed(1)}MB` : `${(fileStat.size / 1024).toFixed(0)}KB`;
383270
383549
  const hint = binaryHint(ext);
383271
383550
  return {
@@ -383932,7 +384211,7 @@ ${fixed}`);
383932
384211
 
383933
384212
  // src/core/tools/rename-symbol.ts
383934
384213
  import { readFile as readFile17, stat as statAsync9, writeFile as writeFile12 } from "fs/promises";
383935
- import { extname as extname6, resolve as resolve30 } from "path";
384214
+ import { extname as extname5, resolve as resolve30 } from "path";
383936
384215
  async function applyEdits2(edits, tabId) {
383937
384216
  for (const edit of edits) {
383938
384217
  const blocked = isForbidden(edit.file);
@@ -383956,7 +384235,7 @@ function getCommentSyntax(filePath) {
383956
384235
  ocamlBlock: false
383957
384236
  };
383958
384237
  }
383959
- const ext = extname6(filePath).toLowerCase();
384238
+ const ext = extname5(filePath).toLowerCase();
383960
384239
  return {
383961
384240
  hash: HASH_COMMENT_EXTS.has(ext),
383962
384241
  doubleDash: DOUBLE_DASH_EXTS.has(ext),
@@ -384982,7 +385261,7 @@ import {
384982
385261
  writeSync
384983
385262
  } from "fs";
384984
385263
  import { tmpdir as tmpdir2 } from "os";
384985
- import { basename as basename5, extname as extname7, resolve as resolve31 } from "path";
385264
+ import { basename as basename5, extname as extname6, resolve as resolve31 } from "path";
384986
385265
  import { inflateSync } from "zlib";
384987
385266
  function _resetKittyVersionCache(override) {
384988
385267
  _kittyVersion = override === undefined ? undefined : override;
@@ -385038,7 +385317,7 @@ function supportsKittyAnimation() {
385038
385317
  return isKittyVersionAtMost(0, 37);
385039
385318
  }
385040
385319
  function isRenderableImage(filePath) {
385041
- const ext = extname7(filePath).toLowerCase();
385320
+ const ext = extname6(filePath).toLowerCase();
385042
385321
  if (!IMAGE_EXTENSIONS2.has(ext))
385043
385322
  return false;
385044
385323
  try {
@@ -385471,14 +385750,14 @@ var init_tool_progress = __esm(() => {
385471
385750
  import { spawn as spawn12, spawnSync as spawnSync4 } from "child_process";
385472
385751
  import {
385473
385752
  existsSync as existsSync25,
385474
- readdirSync as readdirSync7,
385753
+ readdirSync as readdirSync8,
385475
385754
  readFileSync as readFileSync18,
385476
385755
  statSync as statSync7,
385477
385756
  unlinkSync as unlinkSync6,
385478
385757
  writeFileSync as writeFileSync13
385479
385758
  } from "fs";
385480
385759
  import { tmpdir as tmpdir3 } from "os";
385481
- import { basename as basename6, extname as extname8, resolve as resolve32 } from "path";
385760
+ import { basename as basename6, extname as extname7, resolve as resolve32 } from "path";
385482
385761
  function safeUnlink2(path) {
385483
385762
  try {
385484
385763
  if (existsSync25(path))
@@ -385567,7 +385846,7 @@ function hasSips() {
385567
385846
  }
385568
385847
  async function resizeImageToTarget(data, name38, targetBytes, signal) {
385569
385848
  const id = `soul-vision-resize-${String(Date.now())}-${String(Math.random()).slice(2, 8)}`;
385570
- const ext = extname8(name38).toLowerCase() || ".jpg";
385849
+ const ext = extname7(name38).toLowerCase() || ".jpg";
385571
385850
  const srcPath = resolve32(tmpdir3(), `${id}${ext}`);
385572
385851
  const dstPath = resolve32(tmpdir3(), `${id}-resized${ext}`);
385573
385852
  try {
@@ -385773,7 +386052,7 @@ ${INSTALL_FFMPEG}` };
385773
386052
  }
385774
386053
  })();
385775
386054
  const id = `soul-vision-direct-${String(Date.now())}-${String(Math.random()).slice(2, 8)}`;
385776
- const ext = extname8(urlName).toLowerCase() || ".mp4";
386055
+ const ext = extname7(urlName).toLowerCase() || ".mp4";
385777
386056
  const videoPath = resolve32(tmpdir3(), `${id}${ext}`);
385778
386057
  try {
385779
386058
  progress(toolCallId, "FETCH", `Downloading ${urlName}\u2026`);
@@ -385908,7 +386187,7 @@ async function convertLocalVideo(filePath, displayName, toolCallId, signal) {
385908
386187
  return { error: `Video files require ffmpeg to convert:
385909
386188
  ${INSTALL_FFMPEG}` };
385910
386189
  }
385911
- const baseName = basename6(displayName, extname8(displayName));
386190
+ const baseName = basename6(displayName, extname7(displayName));
385912
386191
  if (supportsKittyAnimation()) {
385913
386192
  for (let attempt = 0;attempt < 2; attempt++) {
385914
386193
  const gif = await videoToGif(filePath, toolCallId, MAX_GIF_DURATION, signal);
@@ -385927,7 +386206,7 @@ async function ensurePng(data, name38, signal) {
385927
386206
  if (data.length >= 4 && data.subarray(0, 4).equals(PNG_SIGNATURE)) {
385928
386207
  return data;
385929
386208
  }
385930
- const ext = extname8(name38).toLowerCase() || ".jpg";
386209
+ const ext = extname7(name38).toLowerCase() || ".jpg";
385931
386210
  return convertToPng(data, ext, signal);
385932
386211
  }
385933
386212
  function isGif(data) {
@@ -385966,7 +386245,7 @@ async function extractGifFrames(data, signal) {
385966
386245
  return null;
385967
386246
  const dir = tmpdir3();
385968
386247
  const prefix = `${id}-frame-`;
385969
- const frameFiles = readdirSync7(dir).filter((f) => f.startsWith(prefix) && f.endsWith(".png")).sort().map((f) => resolve32(dir, f));
386248
+ const frameFiles = readdirSync8(dir).filter((f) => f.startsWith(prefix) && f.endsWith(".png")).sort().map((f) => resolve32(dir, f));
385970
386249
  if (frameFiles.length === 0)
385971
386250
  return null;
385972
386251
  const frames = [];
@@ -385980,7 +386259,7 @@ async function extractGifFrames(data, signal) {
385980
386259
  try {
385981
386260
  const dir = tmpdir3();
385982
386261
  const prefix = `${id}-frame-`;
385983
- for (const f of readdirSync7(dir)) {
386262
+ for (const f of readdirSync8(dir)) {
385984
386263
  if (f.startsWith(prefix) && f.endsWith(".png")) {
385985
386264
  safeUnlink2(resolve32(dir, f));
385986
386265
  }
@@ -386291,7 +386570,7 @@ var init_show_image = __esm(() => {
386291
386570
  });
386292
386571
 
386293
386572
  // src/core/skills/manager.ts
386294
- import { existsSync as existsSync26, readdirSync as readdirSync8, readFileSync as readFileSync19, realpathSync as realpathSync3, rmSync as rmSync3, statSync as statSync8 } from "fs";
386573
+ import { existsSync as existsSync26, readdirSync as readdirSync9, readFileSync as readFileSync19, realpathSync as realpathSync3, rmSync as rmSync3, statSync as statSync8 } from "fs";
386295
386574
  import { homedir as homedir22 } from "os";
386296
386575
  import { dirname as dirname14, join as join30 } from "path";
386297
386576
  async function searchSkills(query2) {
@@ -386372,7 +386651,7 @@ function listInstalledSkills() {
386372
386651
  return [...byName.values()];
386373
386652
  }
386374
386653
  function scanSkillDir(dir, scope, byName, seenPaths) {
386375
- const entries2 = readdirSync8(dir, { withFileTypes: true });
386654
+ const entries2 = readdirSync9(dir, { withFileTypes: true });
386376
386655
  for (const entry of entries2) {
386377
386656
  if (entry.name.startsWith("."))
386378
386657
  continue;
@@ -386687,7 +386966,7 @@ function execFileAsync(cmd, args2, opts) {
386687
386966
  var init_util3 = () => {};
386688
386967
 
386689
386968
  // src/core/tools/soul-analyze.ts
386690
- import { extname as extname9, relative as relative8 } from "path";
386969
+ import { extname as extname8, relative as relative8 } from "path";
386691
386970
  async function identifierFrequency(repoMap, cwd2, name38, limit) {
386692
386971
  if (name38) {
386693
386972
  const symbols = await repoMap.findSymbols(name38);
@@ -386750,7 +387029,7 @@ async function unusedExports(repoMap, cwd2, limit) {
386750
387029
  }
386751
387030
  }
386752
387031
  const canTrackFileImports = (filePath) => {
386753
- const ext = extname9(filePath).toLowerCase();
387032
+ const ext = extname8(filePath).toLowerCase();
386754
387033
  const lang = INDEXABLE_EXTENSIONS[ext];
386755
387034
  return lang != null && IMPORT_TRACKABLE_LANGUAGES.has(lang);
386756
387035
  };
@@ -391822,14 +392101,25 @@ function buildSubagentExploreTools(opts) {
391822
392101
  }
391823
392102
  function buildEmberExploreTools(opts) {
391824
392103
  const all = buildSubagentExploreTools(opts);
391825
- const { read, navigate, soul_grep, soul_find, soul_analyze, soul_impact } = all;
392104
+ const {
392105
+ read,
392106
+ navigate,
392107
+ soul_grep,
392108
+ soul_find,
392109
+ soul_analyze,
392110
+ soul_impact,
392111
+ web_search,
392112
+ fetch_page
392113
+ } = all;
391826
392114
  return {
391827
392115
  ...read ? { read } : {},
391828
392116
  ...navigate ? { navigate } : {},
391829
392117
  ...soul_grep ? { soul_grep } : {},
391830
392118
  ...soul_find ? { soul_find } : {},
391831
392119
  ...soul_analyze ? { soul_analyze } : {},
391832
- ...soul_impact ? { soul_impact } : {}
392120
+ ...soul_impact ? { soul_impact } : {},
392121
+ ...web_search ? { web_search } : {},
392122
+ ...fetch_page ? { fetch_page } : {}
391833
392123
  };
391834
392124
  }
391835
392125
  function buildSubagentCodeTools(opts) {
@@ -402308,7 +402598,11 @@ ${enrichedPrompt}`;
402308
402598
  };
402309
402599
  const parentTabId = models.tabId;
402310
402600
  const parentSurfaced = getSurfacedHintIds(parentTabId);
402311
- result = await runInSubagentScope(parentSurfaced, () => agent2.generate(generateArgs), parentTabId);
402601
+ result = await runInSubagentScope(parentSurfaced, () => runWithEditOrigin({
402602
+ tabId: parentTabId ?? null,
402603
+ agentId: task.agentId,
402604
+ agentLabel: task.task ? task.task.slice(0, 32) : task.agentId
402605
+ }, () => agent2.generate(generateArgs)), parentTabId);
402312
402606
  } catch (genErr) {
402313
402607
  const errWithSteps = genErr;
402314
402608
  const recoveredSteps = errWithSteps.steps && Array.isArray(errWithSteps.steps) ? errWithSteps.steps : callbacks._steps.length > 0 ? callbacks._steps : [];
@@ -402612,6 +402906,7 @@ var init_agent_runner = __esm(() => {
402612
402906
  init_config2();
402613
402907
  init_settings();
402614
402908
  init_model_events();
402909
+ init_file_events();
402615
402910
  init_tool_timeout();
402616
402911
  RETURN_FORMAT_INSTRUCTIONS = {
402617
402912
  summary: "Return concise findings and reasoning. No code blocks or raw file content. " + "Focus on what you found, what it means, and what the implications are. " + "Anchor every claim with file:line so the parent can surgically read more.",
@@ -403632,7 +403927,7 @@ function lastStepHadPlanCall(messages) {
403632
403927
  }
403633
403928
  return false;
403634
403929
  }
403635
- function buildForgePrepareStep(isPlanMode, drainSteering, contextManager, tabId, codeExecution3, parentMessagesRef, proxyInstructions, cacheOpts = EPHEMERAL_CACHE) {
403930
+ function buildForgePrepareStep(isPlanMode, drainSteering, contextManager, tabId, codeExecution3, parentMessagesRef, proxyInstructions, cacheOpts = EPHEMERAL_CACHE, modelId) {
403636
403931
  const previousInjects = [];
403637
403932
  const recallInjects = [];
403638
403933
  let lastUserTurnCount = 0;
@@ -403678,7 +403973,7 @@ ${proxyInstructions}
403678
403973
  const hints = [];
403679
403974
  let soulMapDiff = null;
403680
403975
  if (contextManager?.hasSoulMapDiff?.()) {
403681
- soulMapDiff = contextManager.buildSoulMapDiff();
403976
+ soulMapDiff = contextManager.buildSoulMapDiff(modelId);
403682
403977
  }
403683
403978
  if (contextManager) {
403684
403979
  const userTurnCount = countUserTurns(sanitized);
@@ -403963,7 +404258,7 @@ function buildInstructions(cm, modelId) {
403963
404258
  if (cached4 && cached4.key === key2)
403964
404259
  return cached4.text;
403965
404260
  const parts2 = [cm.buildSystemPrompt(modelId)];
403966
- const snapshot = cm.buildSoulMapSnapshot(false);
404261
+ const snapshot = cm.buildSoulMapSnapshot({ modelId });
403967
404262
  if (snapshot)
403968
404263
  parts2.push(snapshot);
403969
404264
  const skills = cm.buildSkillsBlock();
@@ -404197,6 +404492,21 @@ function createForgeAgent({
404197
404492
  if (isAbnormalFinish(step.finishReason)) {
404198
404493
  logBackgroundError("agent-error", `forge: ${describeAbnormalFinish(step.finishReason)}`);
404199
404494
  }
404495
+ const results = step.toolResults;
404496
+ if (Array.isArray(results)) {
404497
+ for (const r of results) {
404498
+ const output = r.output;
404499
+ const value = output?.value;
404500
+ const success2 = value?.success;
404501
+ if (success2 !== false)
404502
+ continue;
404503
+ const tool4 = String(r.toolName ?? "tool");
404504
+ const input = r.input ?? {};
404505
+ const target = String(input.path ?? input.file ?? input.absPath ?? input.relPath ?? "");
404506
+ const reason = typeof value?.error === "string" ? value.error : typeof value?.output === "string" ? value.output.slice(0, 80) : "failed";
404507
+ contextManager.recordToolFailure(tool4, target, reason);
404508
+ }
404509
+ }
404200
404510
  },
404201
404511
  instructions: isProxyClaude ? undefined : {
404202
404512
  role: "system",
@@ -404213,7 +404523,7 @@ function createForgeAgent({
404213
404523
  ...activeTools ? { activeTools } : {}
404214
404524
  };
404215
404525
  },
404216
- prepareStep: buildForgePrepareStep(forgeMode === "plan", drainSteering, contextManager, tabId, canUseCodeExecution, parentMessagesRef, isProxyClaude ? buildInstructions(contextManager, modelId) : undefined, cacheOpts),
404526
+ prepareStep: buildForgePrepareStep(forgeMode === "plan", drainSteering, contextManager, tabId, canUseCodeExecution, parentMessagesRef, isProxyClaude ? buildInstructions(contextManager, modelId) : undefined, cacheOpts, modelId),
404217
404527
  experimental_repairToolCall: repairToolCall,
404218
404528
  providerOptions: wrappedProviderOptions,
404219
404529
  ...subagentHeaders ? { headers: subagentHeaders } : {}
@@ -417922,6 +418232,38 @@ var init_neovim = __esm(() => {
417922
418232
  _onFileWrittenHandlers = new Set;
417923
418233
  });
417924
418234
 
418235
+ // src/core/llm/cache-support.ts
418236
+ function supportsPromptCache(modelId) {
418237
+ const family = detectModelFamily(modelId);
418238
+ switch (family) {
418239
+ case "claude":
418240
+ return EXPLICIT;
418241
+ case "google":
418242
+ return IMPLICIT;
418243
+ case "openai":
418244
+ return IMPLICIT;
418245
+ case "deepseek":
418246
+ case "deepseek-reasoner":
418247
+ return IMPLICIT;
418248
+ case "xai":
418249
+ return IMPLICIT;
418250
+ case "other":
418251
+ return NONE2;
418252
+ default:
418253
+ return NONE2;
418254
+ }
418255
+ }
418256
+ function cacheTtlToMs(ttl) {
418257
+ return ttl === "1h" ? 60 * 60000 : 5 * 60000;
418258
+ }
418259
+ var NONE2, EXPLICIT, IMPLICIT;
418260
+ var init_cache_support = __esm(() => {
418261
+ init_provider_options();
418262
+ NONE2 = { enabled: false, explicit: false };
418263
+ EXPLICIT = { enabled: true, explicit: true };
418264
+ IMPLICIT = { enabled: true, explicit: false };
418265
+ });
418266
+
417925
418267
  // src/core/prompts/families/claude.ts
417926
418268
  var CLAUDE_PROMPT;
417927
418269
  var init_claude = __esm(() => {
@@ -418172,12 +418514,18 @@ function invalidateDirectoryTree(cwd2) {
418172
418514
  dirTreeCache.delete(key2);
418173
418515
  }
418174
418516
  }
418175
- function buildSoulMapUserMessage(rendered, isMinimal, dirTree) {
418517
+ function buildSoulMapUserMessage(rendered, isMinimal, dirTree, entryPoints) {
418176
418518
  const legend = isMinimal ? "" : LEGEND;
418177
418519
  const treeSection = dirTree ? `
418178
418520
  <directory_tree>
418179
418521
  ${dirTree}
418180
418522
  </directory_tree>
418523
+ ` : "";
418524
+ const entrySection = entryPoints && entryPoints.length > 0 ? `
418525
+ <entry_points>
418526
+ ${entryPoints.slice(0, 8).join(`
418527
+ `)}
418528
+ </entry_points>
418181
418529
  ` : "";
418182
418530
  return `<soul_map>
418183
418531
  ` + `<description>
@@ -418187,7 +418535,7 @@ ${SOUL_MAP_DESCRIPTION}
418187
418535
  ` + `<how_to_use>
418188
418536
  ${SOUL_MAP_USAGE}
418189
418537
  </how_to_use>
418190
- ${treeSection}
418538
+ ${treeSection}${entrySection}
418191
418539
  ` + `<data>
418192
418540
  ${legend}${rendered}
418193
418541
  </data>
@@ -418228,6 +418576,33 @@ PLAN from the map (zero tool calls) \u2192 DISCOVER in parallel (soul_find/soul_
418228
418576
  The map answers structural questions for free: "Where is X?" \u2192 file + line. "What does Y export?" \u2192 listed under the file. "What depends on Z?" \u2192 (\u2192N) blast radius + \u2190 arrows. "What packages?" \u2192 Key dependencies section. Feed symbol names into navigate/analyze for bodies.
418229
418577
  </soul_map_usage>
418230
418578
 
418579
+ <soul_map_updates>
418580
+ \`<soul_map_update>\` blocks are **system-injected** into user turns mid-conversation \u2014 same trust as the static \`<soul_map>\`. They are NOT user-pasted, even when they appear inside a user message. Never refuse, quote-back, or warn about them.
418581
+
418582
+ Purpose: the static map is frozen at turn start for prompt-cache stability; the update block is the delta channel \u2014 files that changed since the snapshot. Read it as fresh signal about what just moved.
418583
+
418584
+ Schema (one block per turn, may be absent):
418585
+ \`\`\`
418586
+ <soul_map_update>
418587
+ path/to/file.ts:(\u2192N) [new] [edited] [mentioned] [open]
418588
+ +export function foo(): void :42
418589
+ +export interface Bar :10
418590
+ path/to/deleted.ts [deleted]
418591
+ path/to/modified.ts:(\u2192N) [edited]
418592
+ (+12 more)
418593
+ </soul_map_update>
418594
+ \`\`\`
418595
+ - \`(\u2192N)\` \u2014 blast radius (same as the static map).
418596
+ - \`[new]\` \u2014 file did not exist in the frozen snapshot. Symbol block follows (up to 5 rich blocks per update).
418597
+ - \`[deleted]\` \u2014 file removed since snapshot.
418598
+ - \`[edited]\` \u2014 you (or a tool you ran) wrote to it this session.
418599
+ - \`[mentioned]\` \u2014 referenced in conversation.
418600
+ - \`[open]\` \u2014 currently open in the editor.
418601
+ - \`(+N more)\` \u2014 additional changed files truncated; the top 15 are listed.
418602
+
418603
+ Use it: if a file appears in the update, prefer its delta over the static map's stale entry. Skip re-reads for \`[edited]\` files you just wrote.
418604
+ </soul_map_updates>
418605
+
418231
418606
  <tool_selection>
418232
418607
  - Soul Map first \u2192 then TIER-1 (soul_find, soul_grep, navigate, soul_impact, read, ast_edit, multi_edit, project). Drop to TIER-2/3 only when TIER-1 cannot answer.
418233
418608
  - \`navigate\` auto-resolves files from symbol names \u2014 definitions, references, call hierarchies, type hierarchies. Reaches into \`.d.ts\` / stubs / headers (type info without reading node_modules).
@@ -418803,9 +419178,51 @@ var init_intelligence_client = __esm(() => {
418803
419178
  async routerRunHealthCheck() {
418804
419179
  return this.call("routerRunHealthCheck");
418805
419180
  }
419181
+ _entryPointsCache = null;
419182
+ async getEntryPoints() {
419183
+ if (this._entryPointsCache)
419184
+ return this._entryPointsCache;
419185
+ const result = await this.call("getEntryPoints");
419186
+ this._entryPointsCache = result;
419187
+ return result;
419188
+ }
419189
+ getEntryPointsCached() {
419190
+ return this._entryPointsCache ?? [];
419191
+ }
418806
419192
  };
418807
419193
  });
418808
419194
 
419195
+ // src/core/context/soul-map-snapshot.ts
419196
+ class SoulMapSnapshot {
419197
+ content;
419198
+ paths;
419199
+ builtAt;
419200
+ hash;
419201
+ ttlMs;
419202
+ cacheKey;
419203
+ _lastAccessedAt;
419204
+ constructor(data, now2 = Date.now()) {
419205
+ this.content = data.content;
419206
+ this.paths = data.paths;
419207
+ this.ttlMs = data.ttlMs;
419208
+ this.builtAt = now2;
419209
+ this._lastAccessedAt = now2;
419210
+ this.hash = hash32Hex(data.content);
419211
+ this.cacheKey = data.cacheKey ?? this.hash;
419212
+ }
419213
+ read(now2 = Date.now()) {
419214
+ this._lastAccessedAt = now2;
419215
+ return this.content;
419216
+ }
419217
+ isIdleExpired(now2 = Date.now()) {
419218
+ return now2 - this._lastAccessedAt >= this.ttlMs;
419219
+ }
419220
+ get lastAccessedAt() {
419221
+ return this._lastAccessedAt;
419222
+ }
419223
+ }
419224
+ var init_soul_map_snapshot = () => {};
419225
+
418809
419226
  // src/core/context/toolchain.ts
418810
419227
  import { existsSync as existsSync30 } from "fs";
418811
419228
  import { join as join36 } from "path";
@@ -419075,11 +419492,13 @@ import { join as join37 } from "path";
419075
419492
  var DEFAULT_CONTEXT_WINDOW2 = 200000, ContextManager;
419076
419493
  var init_manager5 = __esm(() => {
419077
419494
  init_dist5();
419495
+ init_config2();
419078
419496
  init_errors();
419079
419497
  init_model_events();
419080
419498
  init_repomap();
419081
419499
  init_neovim();
419082
419500
  init_instance2();
419501
+ init_cache_support();
419083
419502
  init_provider();
419084
419503
  init_provider_options();
419085
419504
  init_hints();
@@ -419090,6 +419509,7 @@ var init_manager5 = __esm(() => {
419090
419509
  init_file_events();
419091
419510
  init_intelligence_client();
419092
419511
  init_file_tree();
419512
+ init_soul_map_snapshot();
419093
419513
  init_toolchain();
419094
419514
  init_conversation_terms();
419095
419515
  ContextManager = class ContextManager {
@@ -419120,6 +419540,7 @@ var init_manager5 = __esm(() => {
419120
419540
  soulMapDiffSeq = 0;
419121
419541
  soulMapSnapshotPaths = new Set;
419122
419542
  soulMapDiffBlocks = new Map;
419543
+ soulMapSnapshot = null;
419123
419544
  taskRouter;
419124
419545
  semanticSummaryLimit = 500;
419125
419546
  semanticAutoRegen = false;
@@ -419287,8 +419708,19 @@ var init_manager5 = __esm(() => {
419287
419708
  unsubEdit = null;
419288
419709
  unsubRead = null;
419289
419710
  wireFileEventHandlers() {
419290
- this.unsubEdit = onFileEdited((absPath) => {
419711
+ this.unsubEdit = onFileEdited((absPath, _content, origin) => {
419291
419712
  this.recallEditEpoch++;
419713
+ if (origin) {
419714
+ const sameTab = origin.tabId && this.tabId && origin.tabId === this.tabId;
419715
+ const isSubagent = !!origin.agentId;
419716
+ if (!sameTab || isSubagent) {
419717
+ const rel = absPath.startsWith(`${this.cwd}/`) ? absPath.slice(this.cwd.length + 1) : absPath;
419718
+ this.foreignEditOrigins.set(rel, {
419719
+ tabId: origin.tabId ?? null,
419720
+ agentLabel: origin.agentLabel ?? origin.agentId ?? null
419721
+ });
419722
+ }
419723
+ }
419292
419724
  this.onFileChanged(absPath);
419293
419725
  });
419294
419726
  this.unsubRead = onFileRead((absPath) => this.trackMentionedFile(absPath));
@@ -419502,9 +419934,11 @@ var init_manager5 = __esm(() => {
419502
419934
  this.soulMapDiffBlocks.clear();
419503
419935
  this.pendingSoulMapDiff = null;
419504
419936
  this.lastEmittedSoulMapDiff = null;
419937
+ this.soulMapSnapshot = null;
419505
419938
  this.warmRepoMapCache();
419506
419939
  }
419507
419940
  resetForCompaction() {
419941
+ this.postCompactionPending = true;
419508
419942
  this.recallCache = null;
419509
419943
  resetSurfacedHints(this.tabId ?? undefined);
419510
419944
  if (this.repoMapCache)
@@ -419516,6 +419950,7 @@ var init_manager5 = __esm(() => {
419516
419950
  this.soulMapDiffBlocks.clear();
419517
419951
  this.pendingSoulMapDiff = null;
419518
419952
  this.lastEmittedSoulMapDiff = null;
419953
+ this.soulMapSnapshot = null;
419519
419954
  this.warmRepoMapCache();
419520
419955
  }
419521
419956
  repoMapRefreshing = false;
@@ -419536,6 +419971,7 @@ var init_manager5 = __esm(() => {
419536
419971
  if (this.repoMapRefreshing)
419537
419972
  return;
419538
419973
  this.repoMapRefreshing = true;
419974
+ this.repoMap.getEntryPoints().catch(() => {});
419539
419975
  try {
419540
419976
  const result = await this.repoMap.render({
419541
419977
  editorFile: this.editorFile,
@@ -419611,6 +420047,7 @@ var init_manager5 = __esm(() => {
419611
420047
  return;
419612
420048
  this.repoMapEnabled = enabled;
419613
420049
  this.repoMapCache = null;
420050
+ this.soulMapSnapshot = null;
419614
420051
  if (!enabled) {
419615
420052
  this.repoMap.onProgress = null;
419616
420053
  this.repoMap.onScanComplete = null;
@@ -419943,6 +420380,7 @@ ${s.signature ? `${s.signature}
419943
420380
  async refreshRepoMap() {
419944
420381
  this.repoMapReady = false;
419945
420382
  this.repoMapCache = null;
420383
+ this.soulMapSnapshot = null;
419946
420384
  this.syncRepoMapStore("scanning");
419947
420385
  useRepoMapStore.getState().setScanError("");
419948
420386
  await this.repoMap.scan().catch((err2) => this.handleScanError(err2));
@@ -419951,6 +420389,7 @@ ${s.signature ? `${s.signature}
419951
420389
  await this.repoMap.clear();
419952
420390
  this.repoMapReady = false;
419953
420391
  this.repoMapCache = null;
420392
+ this.soulMapSnapshot = null;
419954
420393
  const store = useRepoMapStore.getState();
419955
420394
  store.setStats(0, 0, 0, 0);
419956
420395
  store.setScanProgress("");
@@ -420095,29 +420534,59 @@ ${skillBlocks}
420095
420534
  { role: "assistant", content: assistantAck }
420096
420535
  ];
420097
420536
  }
420098
- buildSoulMapSnapshot(clearDiffTracker = true) {
420537
+ buildSoulMapSnapshot(opts = false) {
420538
+ const { modelId, force } = typeof opts === "boolean" ? { modelId: undefined, force: opts } : opts;
420099
420539
  if (!this.isRepoMapReady())
420100
420540
  return null;
420541
+ const cacheSupport = modelId ? supportsPromptCache(modelId) : { enabled: true, explicit: false };
420542
+ const ttlMs = cacheTtlToMs(loadConfig().cache?.ttl ?? "5m");
420543
+ const now2 = Date.now();
420544
+ if (!cacheSupport.enabled) {
420545
+ const rendered2 = this.renderSnapshotContent();
420546
+ if (rendered2) {
420547
+ this.soulMapSnapshot = new SoulMapSnapshot({ content: rendered2, paths: new Set(this.soulMapSnapshotPaths), ttlMs: 0 }, now2);
420548
+ this.clearDeltaState();
420549
+ }
420550
+ return rendered2;
420551
+ }
420552
+ const expired = !this.soulMapSnapshot || this.soulMapSnapshot.isIdleExpired(now2);
420553
+ if (!force && !expired && this.soulMapSnapshot) {
420554
+ return this.soulMapSnapshot.read(now2);
420555
+ }
420556
+ const rendered = this.renderSnapshotContent();
420557
+ if (!rendered)
420558
+ return null;
420559
+ this.soulMapSnapshot = new SoulMapSnapshot({ content: rendered, paths: new Set(this.soulMapSnapshotPaths), ttlMs }, now2);
420560
+ this.clearDeltaState();
420561
+ return rendered;
420562
+ }
420563
+ renderSnapshotContent() {
420101
420564
  const rendered = this.renderRepoMap();
420102
420565
  if (!rendered)
420103
420566
  return null;
420104
420567
  const isMinimal = this.contextWindowTokens <= 32000;
420105
420568
  const treeLimit = this.repoMapTokenBudget ? Math.ceil(this.repoMapTokenBudget / 100) : 60;
420106
420569
  const dirTree = buildDirectoryTree(this.cwd, treeLimit);
420107
- if (clearDiffTracker) {
420108
- this.soulMapDiffChangedFiles.clear();
420109
- this.soulMapDiffSeq = 0;
420110
- this.soulMapDiffBlocks.clear();
420111
- this.pendingSoulMapDiff = null;
420112
- this.lastEmittedSoulMapDiff = null;
420113
- }
420114
- return buildSoulMapUserMessage(rendered, isMinimal, dirTree);
420570
+ const entryPoints = this.repoMap.getEntryPointsCached();
420571
+ return buildSoulMapUserMessage(rendered, isMinimal, dirTree, entryPoints);
420572
+ }
420573
+ clearDeltaState() {
420574
+ this.soulMapDiffChangedFiles.clear();
420575
+ this.soulMapDiffSeq = 0;
420576
+ this.soulMapDiffBlocks.clear();
420577
+ this.pendingSoulMapDiff = null;
420578
+ this.lastEmittedSoulMapDiff = null;
420579
+ this.soulMapNewFilesEmitted.clear();
420580
+ this.recentToolFailures.length = 0;
420581
+ this.foreignEditOrigins.clear();
420115
420582
  }
420116
420583
  pendingSoulMapDiff = null;
420117
420584
  lastEmittedSoulMapDiff = null;
420118
- buildSoulMapDiff() {
420585
+ buildSoulMapDiff(modelId) {
420119
420586
  if (!this.isRepoMapReady())
420120
420587
  return null;
420588
+ if (modelId && !supportsPromptCache(modelId).enabled)
420589
+ return null;
420121
420590
  for (const path of this.soulMapDiffChangedFiles.keys()) {
420122
420591
  if (path.startsWith("/"))
420123
420592
  this.soulMapDiffChangedFiles.delete(path);
@@ -420125,26 +420594,55 @@ ${skillBlocks}
420125
420594
  if (this.soulMapDiffChangedFiles.size === 0)
420126
420595
  return null;
420127
420596
  if (!this.pendingSoulMapDiff) {
420128
- const changed = [...this.soulMapDiffChangedFiles.entries()].sort((a, b) => b[1] - a[1]).map(([path]) => path);
420597
+ const changed = [...this.soulMapDiffChangedFiles.entries()].sort((a, b) => {
420598
+ const recency = b[1] - a[1];
420599
+ return recency !== 0 ? recency : a[0].localeCompare(b[0]);
420600
+ }).map(([path]) => path);
420129
420601
  const hasSnapshot = this.soulMapSnapshotPaths.size > 0;
420130
420602
  const lines = ["<soul_map_update>"];
420603
+ const hasForeign = this.foreignEditOrigins.size > 0;
420604
+ const postCompaction = this.postCompactionPending;
420605
+ if (this.editedFiles.size > 0 && (hasForeign || postCompaction)) {
420606
+ const hotFiles = [...this.soulMapDiffChangedFiles.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([p]) => p.split("/").pop() ?? p);
420607
+ const hot = hotFiles.length > 0 ? ` (hot: ${hotFiles.join(", ")})` : "";
420608
+ const causes = [];
420609
+ if (postCompaction)
420610
+ causes.push("post-compaction");
420611
+ if (hasForeign) {
420612
+ const labels = new Set;
420613
+ for (const o of this.foreignEditOrigins.values()) {
420614
+ if (o.agentLabel)
420615
+ labels.add(`subagent ${o.agentLabel}`);
420616
+ else if (o.tabId && o.tabId !== this.tabId)
420617
+ labels.add(`tab ${o.tabId.slice(0, 8)}`);
420618
+ }
420619
+ if (labels.size > 0)
420620
+ causes.push([...labels].sort().join(", "));
420621
+ else
420622
+ causes.push("foreign edits");
420623
+ }
420624
+ const causeTag = causes.length > 0 ? ` \u2014 ${causes.join("; ")}` : "";
420625
+ lines.push(`# session: ${String(this.editedFiles.size)} files edited${hot}${causeTag}`);
420626
+ }
420131
420627
  const MAX_RICH_BLOCKS = 5;
420132
420628
  let richBlockCount = 0;
420629
+ const memoryMarkers = memoryMarkersForPaths(changed.slice(0, 15));
420133
420630
  for (const file2 of changed.slice(0, 15)) {
420134
420631
  const absPath = join37(this.cwd, file2);
420135
420632
  const fileExists = existsSync31(absPath);
420136
420633
  const block = this.soulMapDiffBlocks.get(file2);
420634
+ const provenance = this.classifyDeltaFile(absPath, file2, memoryMarkers.get(file2));
420137
420635
  if (!fileExists) {
420138
- lines.push(`- ${file2}`);
420636
+ lines.push(`- ${file2} [deleted]`);
420139
420637
  } else if (hasSnapshot && !this.soulMapSnapshotPaths.has(file2)) {
420140
- const tag = block ? `${file2}:${block.radiusTag} [NEW FILE]` : `${file2}: [NEW FILE]`;
420638
+ const tag = block ? `${file2}:${block.radiusTag} [new] ${provenance}`.trimEnd() : `${file2}: [new] ${provenance}`.trimEnd();
420141
420639
  lines.push(tag);
420142
420640
  if (block?.symbolBlock && richBlockCount < MAX_RICH_BLOCKS) {
420143
420641
  lines.push(block.symbolBlock);
420144
420642
  richBlockCount++;
420145
420643
  }
420146
420644
  } else {
420147
- const tag = block ? `${file2}:${block.radiusTag}` : `${file2}:`;
420645
+ const tag = block ? `${file2}:${block.radiusTag} ${provenance}`.trimEnd() : `${file2}: ${provenance}`.trimEnd();
420148
420646
  lines.push(tag);
420149
420647
  if (block?.symbolBlock && richBlockCount < MAX_RICH_BLOCKS) {
420150
420648
  lines.push(block.symbolBlock);
@@ -420162,10 +420660,45 @@ ${skillBlocks}
420162
420660
  return null;
420163
420661
  return this.pendingSoulMapDiff;
420164
420662
  }
420663
+ classifyDeltaFile(absPath, rel, memMarker) {
420664
+ const tags = [];
420665
+ if (this.editedFiles.has(absPath))
420666
+ tags.push("[edited]");
420667
+ if (this.mentionedFiles.has(absPath))
420668
+ tags.push("[mentioned]");
420669
+ if (this.editorFile === absPath)
420670
+ tags.push("[open]");
420671
+ if (this.soulMapNewFilesEmitted.has(rel) && existsSync31(absPath)) {
420672
+ tags.push("[modified-since-new]");
420673
+ }
420674
+ const failure = this.recentToolFailures.find((f) => f.target === absPath || f.target === rel);
420675
+ if (failure)
420676
+ tags.push(`[recent failure: ${failure.tool} \u2014 ${failure.reason}]`);
420677
+ if (memMarker && memMarker.count > 0) {
420678
+ const cat = memMarker.category ?? "memory";
420679
+ const pin = memMarker.pinned ? "pinned " : "";
420680
+ const n = memMarker.count > 1 ? ` \xD7${String(memMarker.count)}` : "";
420681
+ tags.push(`[${pin}${cat}${n}]`);
420682
+ }
420683
+ return tags.join(" ");
420684
+ }
420685
+ forceSnapshotRefresh() {
420686
+ this.soulMapSnapshot = null;
420687
+ }
420165
420688
  commitSoulMapDiff() {
420166
420689
  if (this.pendingSoulMapDiff) {
420690
+ const hasSnapshot = this.soulMapSnapshotPaths.size > 0;
420691
+ if (hasSnapshot) {
420692
+ for (const rel of this.soulMapDiffChangedFiles.keys()) {
420693
+ if (!this.soulMapSnapshotPaths.has(rel)) {
420694
+ this.soulMapNewFilesEmitted.add(rel);
420695
+ }
420696
+ }
420697
+ }
420167
420698
  this.lastEmittedSoulMapDiff = this.pendingSoulMapDiff;
420168
420699
  this.pendingSoulMapDiff = null;
420700
+ this.postCompactionPending = false;
420701
+ this.foreignEditOrigins.clear();
420169
420702
  }
420170
420703
  }
420171
420704
  buildSkillsBlock() {
@@ -420373,6 +420906,27 @@ Project commands: ${parts2.join(" \xB7 ")}` : "";
420373
420906
  return true;
420374
420907
  }
420375
420908
  pendingSemanticMode = null;
420909
+ soulMapNewFilesEmitted = new Set;
420910
+ recentToolFailures = [];
420911
+ recordToolFailure(tool4, target, reason) {
420912
+ const at = Date.now();
420913
+ const key2 = `${tool4}:${target}`;
420914
+ const existing = this.recentToolFailures.findIndex((f) => `${f.tool}:${f.target}` === key2);
420915
+ if (existing >= 0)
420916
+ this.recentToolFailures.splice(existing, 1);
420917
+ this.recentToolFailures.push({ tool: tool4, target, reason, at });
420918
+ if (this.recentToolFailures.length > 5)
420919
+ this.recentToolFailures.shift();
420920
+ this.pendingSoulMapDiff = null;
420921
+ if (target) {
420922
+ const rel = target.startsWith(`${this.cwd}/`) ? target.slice(this.cwd.length + 1) : target;
420923
+ if (!this.soulMapDiffChangedFiles.has(rel)) {
420924
+ this.soulMapDiffChangedFiles.set(rel, ++this.soulMapDiffSeq);
420925
+ }
420926
+ }
420927
+ }
420928
+ foreignEditOrigins = new Map;
420929
+ postCompactionPending = false;
420376
420930
  };
420377
420931
  });
420378
420932
 
@@ -492695,7 +493249,7 @@ var init_checkpoints = __esm(() => {
492695
493249
  });
492696
493250
 
492697
493251
  // src/core/commands/utils.ts
492698
- import { existsSync as existsSync42, readdirSync as readdirSync9, statSync as statSync10 } from "fs";
493252
+ import { existsSync as existsSync42, readdirSync as readdirSync10, statSync as statSync10 } from "fs";
492699
493253
  import { homedir as homedir31 } from "os";
492700
493254
  import { join as join45 } from "path";
492701
493255
  function sysMsg(ctx, content) {
@@ -492720,7 +493274,7 @@ function dirSize(dirPath) {
492720
493274
  if (!existsSync42(dirPath))
492721
493275
  return 0;
492722
493276
  let total = 0;
492723
- for (const entry of readdirSync9(dirPath)) {
493277
+ for (const entry of readdirSync10(dirPath)) {
492724
493278
  const fp = join45(dirPath, entry);
492725
493279
  try {
492726
493280
  const s2 = statSync10(fp);
@@ -498728,13 +499282,6 @@ function getRestartSpec(options = {}) {
498728
499282
  }
498729
499283
 
498730
499284
  // src/core/sessions/emergency-save.ts
498731
- function updateEmergencySnapshot(manager, meta4, tabMessages, tabCoreMessages) {
498732
- const frozen = new Map;
498733
- for (const [id, msgs] of tabMessages) {
498734
- frozen.set(id, msgs.map((m6) => ({ ...m6 })));
498735
- }
498736
- _snapshot = { manager, meta: { ...meta4 }, tabMessages: frozen, tabCoreMessages };
498737
- }
498738
499285
  function flushEmergencySession() {
498739
499286
  if (!_snapshot)
498740
499287
  return false;
@@ -500600,13 +501147,12 @@ function hardRestart() {
500600
501147
  child.unref();
500601
501148
  process.exit(0);
500602
501149
  }
500603
- function reraiseSignal(signal) {
501150
+ function reraiseSignal(_signal) {
500604
501151
  flushEmergencySession();
500605
501152
  runCleanup();
500606
501153
  renderer?.destroy();
500607
501154
  printExitBanner();
500608
- process.removeAllListeners(signal);
500609
- process.kill(process.pid, signal);
501155
+ process.exit(0);
500610
501156
  }
500611
501157
  function RestartSplash({ onComplete }) {
500612
501158
  const t2 = useTheme();
@@ -501932,7 +502478,7 @@ async function handleExportAll(ctx) {
501932
502478
  const tokenUsage = ctx.chat.tokenUsage;
501933
502479
  const forgeMode = ctx.chat.forgeMode;
501934
502480
  const repoMapReady = ctx.contextManager.isRepoMapReady();
501935
- const soulMapBlock = ctx.contextManager.buildSoulMapSnapshot(false);
502481
+ const soulMapBlock = ctx.contextManager.buildSoulMapSnapshot();
501936
502482
  const skillsMessages = ctx.contextManager.buildSkillsMessages();
501937
502483
  const payload = {
501938
502484
  exportedAt: new Date().toISOString(),
@@ -504085,83 +504631,6 @@ var init_useNeovim = __esm(() => {
504085
504631
  });
504086
504632
 
504087
504633
  // src/hooks/useSessionBuilder.ts
504088
- function buildSessionMeta({
504089
- sessionId,
504090
- title,
504091
- customTitle,
504092
- cwd: cwd2,
504093
- snapshot,
504094
- currentTabMessages,
504095
- currentTabCoreMessages
504096
- }) {
504097
- const tabMessages = new Map;
504098
- const tabCoreMessages = new Map;
504099
- const tabs = [];
504100
- for (const tabState of snapshot.tabStates) {
504101
- const isActiveTab = tabState.id === snapshot.activeTabId;
504102
- const msgs = isActiveTab ? currentTabMessages : tabState.messages.filter((m6) => m6.role !== "system" || m6.showInChat);
504103
- tabMessages.set(tabState.id, msgs);
504104
- const cores = isActiveTab && currentTabCoreMessages ? currentTabCoreMessages : tabState.coreMessages;
504105
- tabCoreMessages.set(tabState.id, cores);
504106
- const cpStore = useCheckpointStore.getState();
504107
- const cpState = cpStore.getCheckpoints(tabState.id);
504108
- const redoStack = cpStore.getTab(tabState.id).redoStack;
504109
- const seen = new Set;
504110
- const checkpointTags = [];
504111
- for (const cp2 of cpState) {
504112
- if (cp2.gitTag && !seen.has(cp2.gitTag)) {
504113
- seen.add(cp2.gitTag);
504114
- checkpointTags.push({
504115
- index: cp2.index,
504116
- anchorMessageId: cp2.anchorMessageId,
504117
- gitTag: cp2.gitTag
504118
- });
504119
- }
504120
- }
504121
- for (const entry of redoStack) {
504122
- const cp2 = entry.checkpoint;
504123
- if (cp2.gitTag && !seen.has(cp2.gitTag)) {
504124
- seen.add(cp2.gitTag);
504125
- checkpointTags.push({
504126
- index: cp2.index,
504127
- anchorMessageId: cp2.anchorMessageId,
504128
- gitTag: cp2.gitTag
504129
- });
504130
- }
504131
- }
504132
- const uiSnapshot = useUIStore.getState();
504133
- const verboseForTab = uiSnapshot.verboseByTab[tabState.id];
504134
- tabs.push({
504135
- id: tabState.id,
504136
- label: tabState.label,
504137
- activeModel: tabState.activeModel,
504138
- sessionId: tabState.sessionId,
504139
- planMode: tabState.planMode,
504140
- planRequest: tabState.planRequest,
504141
- coAuthorCommits: tabState.coAuthorCommits,
504142
- forgeMode: tabState.forgeMode,
504143
- tokenUsage: tabState.tokenUsage,
504144
- messageRange: { startLine: 0, endLine: msgs.length },
504145
- ...checkpointTags.length > 0 ? { checkpointTags } : {},
504146
- ...verboseForTab !== undefined ? { verbose: verboseForTab } : {}
504147
- });
504148
- }
504149
- const allMsgs = [...tabMessages.values()].flat();
504150
- const startedAt = allMsgs[0]?.timestamp ?? Date.now();
504151
- const activeTabState = snapshot.tabStates.find((t2) => t2.id === snapshot.activeTabId);
504152
- const meta4 = {
504153
- id: sessionId,
504154
- title,
504155
- ...customTitle ? { customTitle } : {},
504156
- cwd: cwd2,
504157
- startedAt,
504158
- updatedAt: Date.now(),
504159
- activeTabId: snapshot.activeTabId,
504160
- forgeMode: activeTabState?.forgeMode ?? "default",
504161
- tabs
504162
- };
504163
- return { meta: meta4, tabMessages, tabCoreMessages };
504164
- }
504165
504634
  function buildTabMeta(args2) {
504166
504635
  const msgs = args2.messages.filter((m6) => m6.role !== "system" || m6.showInChat);
504167
504636
  const cpStore = useCheckpointStore.getState();
@@ -504384,13 +504853,23 @@ function useTabs() {
504384
504853
  }
504385
504854
  return states;
504386
504855
  }, []);
504387
- const restoreFromMeta = import_react37.useCallback((tabMetas, activeId, tabMessages, tabCoreMessages) => {
504388
- if (tabMetas.length === 0)
504856
+ const restoreFromMeta = import_react37.useCallback((incomingMetas, activeId, tabMessages, tabCoreMessages) => {
504857
+ if (incomingMetas.length === 0)
504389
504858
  return;
504390
504859
  for (const chat of chatRegistry.current.values()) {
504391
504860
  chat.abort();
504392
504861
  }
504393
504862
  chatRegistry.current.clear();
504863
+ const seenIds = new Set;
504864
+ const tabMetas = [];
504865
+ for (const tm of incomingMetas) {
504866
+ if (seenIds.has(tm.id))
504867
+ continue;
504868
+ seenIds.add(tm.id);
504869
+ tabMetas.push(tm);
504870
+ if (tabMetas.length >= MAX_TABS)
504871
+ break;
504872
+ }
504394
504873
  const restoredTabs = tabMetas.map((tm) => ({
504395
504874
  id: tm.id,
504396
504875
  label: tm.label
@@ -506767,7 +507246,21 @@ function useChat({
506767
507246
  visible = true,
506768
507247
  onModelChange
506769
507248
  }) {
506770
- const [messages, setMessages] = import_react46.useState(initialState?.messages ?? []);
507249
+ const [messages, setMessagesRaw] = import_react46.useState(initialState?.messages ?? []);
507250
+ const messagesRef = import_react46.useRef(initialState?.messages ?? []);
507251
+ messagesRef.current = messages;
507252
+ const setMessages = import_react46.useCallback((action) => {
507253
+ if (typeof action === "function") {
507254
+ setMessagesRaw((prev) => {
507255
+ const next = action(prev);
507256
+ messagesRef.current = next;
507257
+ return next;
507258
+ });
507259
+ } else {
507260
+ messagesRef.current = action;
507261
+ setMessagesRaw(action);
507262
+ }
507263
+ }, []);
506771
507264
  const [coreMessages, setCoreMessages] = import_react46.useState(initialState?.coreMessages ?? []);
506772
507265
  const [isLoading, setIsLoading] = import_react46.useState(false);
506773
507266
  const [loadingStartedAt, setLoadingStartedAt] = import_react46.useState(0);
@@ -506982,10 +507475,12 @@ function useChat({
506982
507475
  }, [activeModelForEffect]);
506983
507476
  const [tokenUsage, setTokenUsageRaw] = import_react46.useState(initialState?.tokenUsage ?? { ...ZERO_USAGE });
506984
507477
  const sessionIdRef = import_react46.useRef(initialState?.sessionId ?? getAppSessionId());
506985
- if (initialState?.sessionId && initialState.sessionId !== getAppSessionId()) {
506986
- setAppSessionId(initialState.sessionId);
506987
- sessionIdRef.current = initialState.sessionId;
506988
- }
507478
+ import_react46.useEffect(() => {
507479
+ if (initialState?.sessionId && initialState.sessionId !== getAppSessionId()) {
507480
+ setAppSessionId(initialState.sessionId);
507481
+ sessionIdRef.current = initialState.sessionId;
507482
+ }
507483
+ }, []);
506989
507484
  import_react46.useEffect(() => {
506990
507485
  const unsub = useSessionStore.subscribe((s2) => {
506991
507486
  sessionIdRef.current = s2.appSessionId;
@@ -507603,7 +508098,7 @@ INCLUDE the plan progress above VERBATIM in ## Current State so the agent knows
507603
508098
  });
507604
508099
  }
507605
508100
  }
507606
- }, [setTokenUsage, effectiveConfig, contextManager, cwd2, tabId]);
508101
+ }, [setTokenUsage, effectiveConfig, contextManager, cwd2, tabId, setMessages]);
507607
508102
  summarizeConversationRef.current = summarizeConversation;
507608
508103
  const autoSummarizedRef = import_react46.useRef(false);
507609
508104
  import_react46.useEffect(() => {
@@ -507915,7 +508410,7 @@ ${description}`,
507915
508410
  };
507916
508411
  setMessages((prev) => {
507917
508412
  const allMsgs = [...prev, userMsg];
507918
- queueMicrotask(() => persistThisTab(allMsgs, coreMessagesRef.current));
508413
+ queueMicrotask(() => persistThisTab(messagesRef.current, coreMessagesRef.current));
507919
508414
  return allMsgs;
507920
508415
  });
507921
508416
  const currentCoreMessages = coreMessagesRef.current;
@@ -508164,8 +508659,21 @@ ${description}`,
508164
508659
  desloppify: desloppifyModelId ? resolveModel(desloppifyModelId) : undefined,
508165
508660
  verify: verifyModelId ? resolveModel(verifyModelId) : undefined
508166
508661
  } : undefined;
508167
- const webSearchModel = webSearchModelId ? resolveModel(webSearchModelId) : undefined;
508168
- webSearchModelLabelRef.current = webSearchModelId ? getShortModelLabel(webSearchModelId) : null;
508662
+ let webSearchModel;
508663
+ try {
508664
+ webSearchModel = webSearchModelId ? resolveModel(webSearchModelId) : undefined;
508665
+ } catch (err2) {
508666
+ logBackgroundError("web-search-resolve", `webSearch model "${webSearchModelId}" failed to resolve: ${err2 instanceof Error ? err2.message : String(err2)}`);
508667
+ webSearchModel = undefined;
508668
+ }
508669
+ webSearchModelLabelRef.current = webSearchModel ? (() => {
508670
+ const id = webSearchModelId;
508671
+ const slash = id.indexOf("/");
508672
+ const providerId = slash > 0 ? id.slice(0, slash) : "";
508673
+ const short = getShortModelLabel(id);
508674
+ return providerId ? `${providerId}/${short}` : short;
508675
+ })() : null;
508676
+ logBackgroundError("router:web-search", webSearchModel ? `agent mode \u2192 ${webSearchModelId}` : webSearchModelId ? `agent disabled (resolve failed) \u2192 fallback to direct scraper` : `agent disabled (no taskRouter.webSearch set) \u2192 fallback to direct scraper`);
508169
508677
  const webSearchEnabled = effectiveConfig2.webSearch !== false;
508170
508678
  const webSearchApproval = webSearchEnabled ? interactiveCallbacks.onWebSearchApproval : undefined;
508171
508679
  const fetchPageApproval = interactiveCallbacks.onFetchPageApproval;
@@ -508404,11 +508912,11 @@ Proceeding without it will significantly reduce capabilities \u2014 no soul tool
508404
508912
  tabLabel
508405
508913
  });
508406
508914
  })();
508407
- result = await currentAgent.stream({
508915
+ result = await runWithEditOrigin({ tabId: tabId ?? null, agentId: null, agentLabel: null }, () => currentAgent.stream({
508408
508916
  messages: newCoreMessages,
508409
508917
  abortSignal: abortController.signal,
508410
508918
  options: { userMessage: input }
508411
- });
508919
+ }));
508412
508920
  break;
508413
508921
  } catch (err2) {
508414
508922
  if (!isProviderOptionsError(err2) || degradeLevel === 2)
@@ -508898,10 +509406,7 @@ Proceeding without it will significantly reduce capabilities \u2014 no soul tool
508898
509406
  segments: finalSegments.length > 0 ? [...finalSegments] : undefined,
508899
509407
  ...lockInCommittedAt !== undefined ? { lockInCommittedAt } : {}
508900
509408
  };
508901
- setMessages((prev) => {
508902
- persistThisTab([...prev, partialMsg], coreMessagesRef.current);
508903
- return prev;
508904
- });
509409
+ persistThisTab([...messagesRef.current, partialMsg], coreMessagesRef.current);
508905
509410
  } catch {}
508906
509411
  });
508907
509412
  }
@@ -508988,7 +509493,6 @@ ${errStack}` : `Error: ${displayErr}`);
508988
509493
  timestamp: Date.now(),
508989
509494
  showInChat: true
508990
509495
  } : null;
508991
- let finalMsgsForSave = [];
508992
509496
  setMessages((prev) => {
508993
509497
  const allMsgs = [
508994
509498
  ...prev,
@@ -508996,7 +509500,6 @@ ${errStack}` : `Error: ${displayErr}`);
508996
509500
  ...errorMsgs,
508997
509501
  ...prematureStopMsg ? [prematureStopMsg] : []
508998
509502
  ];
508999
- finalMsgsForSave = allMsgs;
509000
509503
  if (assistantMsg) {
509001
509504
  Promise.resolve().then(() => (init_bridge(), exports_bridge)).then(({ bridgeStreamEmitter: bridgeStreamEmitter2, reasoningStreamEmitter: reasoningStreamEmitter2 }) => {
509002
509505
  reasoningStreamEmitter2.flushNow(tabId);
@@ -509040,7 +509543,7 @@ ${errStack}` : `Error: ${displayErr}`);
509040
509543
  const target = effectiveConfig2.contextManagement?.pruningTarget ?? "none";
509041
509544
  return ["main", "both"].includes(target) ? pruneOldToolResults(updated) : updated;
509042
509545
  });
509043
- persistThisTab(finalMsgsForSave, coreMessagesRef.current);
509546
+ persistThisTab(messagesRef.current, coreMessagesRef.current);
509044
509547
  streamSegmentsBuffer.current = [];
509045
509548
  liveToolCallsBuffer.current = [];
509046
509549
  lastFlushedSegments.current = [];
@@ -509650,7 +510153,8 @@ ${pContent}`;
509650
510153
  setForgeMode,
509651
510154
  onModelChange,
509652
510155
  setCoreMessagesEager,
509653
- persistThisTab
510156
+ persistThisTab,
510157
+ setMessages
509654
510158
  ]);
509655
510159
  handleSubmitRef.current = handleSubmit;
509656
510160
  const abort2 = import_react46.useCallback(() => {
@@ -509707,7 +510211,7 @@ ${pContent}`;
509707
510211
  segmentsDirty.current = false;
509708
510212
  toolCallsDirty.current = false;
509709
510213
  }
509710
- }, [setActivePlan, tabId]);
510214
+ }, [setActivePlan, tabId, setMessages]);
509711
510215
  const snapshot = import_react46.useCallback((label) => ({
509712
510216
  id: sessionIdRef.current,
509713
510217
  label,
@@ -511257,20 +511761,20 @@ var init_useHover = __esm(() => {
511257
511761
  });
511258
511762
 
511259
511763
  // src/core/utils/syntax.ts
511260
- import { existsSync as existsSync52, readdirSync as readdirSync10 } from "fs";
511764
+ import { existsSync as existsSync52, readdirSync as readdirSync11 } from "fs";
511261
511765
  import { homedir as homedir37 } from "os";
511262
511766
  import { dirname as dirname22, join as join54, resolve as resolve40 } from "path";
511263
511767
  function discoverParsers() {
511264
511768
  const parsers = [];
511265
511769
  let dirs;
511266
511770
  try {
511267
- dirs = readdirSync10(coreAssetsDir, { withFileTypes: true }).filter((d4) => d4.isDirectory()).map((d4) => d4.name);
511771
+ dirs = readdirSync11(coreAssetsDir, { withFileTypes: true }).filter((d4) => d4.isDirectory()).map((d4) => d4.name);
511268
511772
  } catch {
511269
511773
  return parsers;
511270
511774
  }
511271
511775
  for (const dir of dirs) {
511272
511776
  const langDir = resolve40(coreAssetsDir, dir);
511273
- const wasmFiles = readdirSync10(langDir).filter((f3) => f3.endsWith(".wasm"));
511777
+ const wasmFiles = readdirSync11(langDir).filter((f3) => f3.endsWith(".wasm"));
511274
511778
  const wasmFile = wasmFiles[0];
511275
511779
  if (!wasmFile)
511276
511780
  continue;
@@ -535706,10 +536210,10 @@ function getAllPackageStatus(category) {
535706
536210
  }
535707
536211
  function detectProjectLanguages(cwd2) {
535708
536212
  const languages = [];
535709
- const { readdirSync: readdirSync11 } = __require("fs");
536213
+ const { readdirSync: readdirSync12 } = __require("fs");
535710
536214
  let files;
535711
536215
  try {
535712
- files = readdirSync11(cwd2);
536216
+ files = readdirSync12(cwd2);
535713
536217
  } catch {
535714
536218
  return [];
535715
536219
  }
@@ -539764,7 +540268,7 @@ function RouterSettings({
539764
540268
  const rowBg2 = isSelected ? t2.bgPopupHighlight : t2.bgPopup;
539765
540269
  const fbs = modelFallback?.[row.modelId] ?? [];
539766
540270
  const fallbackLabelCol = Math.min(28, Math.max(18, Math.floor(contentW * 0.32)));
539767
- const label2 = truncate4(shortModel2(row.modelId), fallbackLabelCol).padEnd(fallbackLabelCol).slice(0, fallbackLabelCol);
540271
+ const label2 = truncate4(row.modelId, fallbackLabelCol).padEnd(fallbackLabelCol).slice(0, fallbackLabelCol);
539768
540272
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
539769
540273
  flexDirection: "row",
539770
540274
  height: 1,
@@ -539790,7 +540294,7 @@ function RouterSettings({
539790
540294
  bg: rowBg2,
539791
540295
  fg: t2.brandAlt,
539792
540296
  attributes: BOLD19,
539793
- children: truncate4(`\u2192 ${fbs.map((m6) => shortModel2(m6)).join(", ")}`, Math.max(8, contentW - 4 - fallbackLabelCol - 2))
540297
+ children: truncate4(`\u2192 ${fbs.join(", ")}`, Math.max(8, contentW - 4 - fallbackLabelCol - 2))
539794
540298
  }, undefined, false, undefined, this) : /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
539795
540299
  bg: rowBg2,
539796
540300
  fg: t2.textDim,
@@ -539850,7 +540354,7 @@ function RouterSettings({
539850
540354
  bg: rowBg,
539851
540355
  fg: t2.brandAlt,
539852
540356
  attributes: BOLD19,
539853
- children: truncate4(shortModel2(modelId), modelCol)
540357
+ children: truncate4(modelId, modelCol)
539854
540358
  }, undefined, false, undefined, this) : /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
539855
540359
  bg: rowBg,
539856
540360
  fg: t2.textDim,
@@ -539900,18 +540404,24 @@ var init_RouterSettings = __esm(async () => {
539900
540404
  ]
539901
540405
  },
539902
540406
  {
539903
- id: "dispatch",
539904
- title: "Dispatch",
540407
+ id: "tools",
540408
+ title: "Tools",
539905
540409
  defs: [
539906
- { kind: "slot", key: "spark", label: "Explore", icon: "read_only", hint: "Read-only agents" },
539907
- { kind: "slot", key: "ember", label: "Code", icon: "edit", hint: "Edit agents" },
539908
540410
  {
539909
540411
  kind: "slot",
539910
540412
  key: "webSearch",
539911
540413
  label: "Web Search",
539912
540414
  icon: "web",
539913
540415
  hint: "Web search & fetch"
539914
- },
540416
+ }
540417
+ ]
540418
+ },
540419
+ {
540420
+ id: "dispatch",
540421
+ title: "Dispatch",
540422
+ defs: [
540423
+ { kind: "slot", key: "spark", label: "Explore", icon: "read_only", hint: "Read-only agents" },
540424
+ { kind: "slot", key: "ember", label: "Code", icon: "edit", hint: "Edit agents" },
539915
540425
  {
539916
540426
  kind: "picker",
539917
540427
  key: "maxConcurrentAgents",
@@ -542032,23 +542542,48 @@ function App({
542032
542542
  setShutdownPhase(1);
542033
542543
  schedule(async () => {
542034
542544
  try {
542545
+ const sid = getAppSessionId();
542546
+ const liveTabs = tabMgrRef.current.tabs;
542035
542547
  const activeChat = tabMgrRef.current.getActiveChat();
542036
- const hasUserMessages = activeChat?.messages.some((m6) => m6.role === "user" || m6.role === "assistant");
542037
- const snapshot = workspaceSnapshotRef.current?.();
542038
- if (snapshot && hasUserMessages && activeChat) {
542039
- const { meta: meta4, tabMessages, tabCoreMessages } = buildSessionMeta({
542040
- sessionId: getAppSessionId(),
542041
- title: activeChat.customTitle ?? SessionManager.deriveTitle(activeChat.messages),
542042
- customTitle: activeChat.customTitle,
542043
- cwd: cwd2,
542044
- snapshot,
542045
- currentTabMessages: activeChat.messages.filter((m6) => m6.role !== "system" || m6.showInChat),
542046
- currentTabCoreMessages: activeChat.coreMessages
542047
- });
542048
- updateEmergencySnapshot(sessionManager, meta4, tabMessages, tabCoreMessages);
542049
- await sessionManager.saveSession(meta4, tabMessages, tabCoreMessages);
542050
- setExitSessionId(meta4.id);
542051
- savedSessionIdRef.current = meta4.id;
542548
+ const activeTabId2 = tabMgrRef.current.activeTabId;
542549
+ const anyContent = liveTabs.some((t3) => {
542550
+ const c = tabMgrRef.current.getChat(t3.id);
542551
+ return c?.messages.some((m6) => m6.role === "user" || m6.role === "assistant");
542552
+ });
542553
+ if (anyContent) {
542554
+ const liveIds = new Set(liveTabs.map((t3) => t3.id));
542555
+ const fallbackTitle = activeChat?.customTitle ?? SessionManager.deriveTitle(activeChat?.messages ?? []);
542556
+ for (const tab of liveTabs) {
542557
+ const chat = tabMgrRef.current.getChat(tab.id);
542558
+ if (!chat)
542559
+ continue;
542560
+ const filtered = chat.messages.filter((m6) => m6.role !== "system" || m6.showInChat);
542561
+ const { tabMeta } = buildTabMeta({
542562
+ tabId: tab.id,
542563
+ tabLabel: tab.label,
542564
+ activeModel: chat.activeModel,
542565
+ sessionId: sid,
542566
+ planMode: chat.planMode,
542567
+ planRequest: chat.planRequest,
542568
+ coAuthorCommits: chat.coAuthorCommits,
542569
+ forgeMode: chat.forgeMode,
542570
+ tokenUsage: chat.tokenUsage,
542571
+ messages: filtered,
542572
+ coreMessages: chat.coreMessages
542573
+ });
542574
+ await sessionManager.saveTab(sid, tabMeta, filtered, chat.coreMessages, {
542575
+ title: fallbackTitle,
542576
+ customTitle: activeChat?.customTitle ?? null,
542577
+ cwd: cwd2,
542578
+ forgeMode: activeChat?.forgeMode ?? "default",
542579
+ activeTabId: activeTabId2
542580
+ });
542581
+ }
542582
+ try {
542583
+ await sessionManager.pruneTabsNotIn(sid, liveIds);
542584
+ } catch {}
542585
+ setExitSessionId(sid);
542586
+ savedSessionIdRef.current = sid;
542052
542587
  }
542053
542588
  } catch (err2) {
542054
542589
  logBackgroundError("shutdown", `session save failed: ${err2 instanceof Error ? err2.message : String(err2)}`);
@@ -542082,10 +542617,10 @@ function App({
542082
542617
  }
542083
542618
  const data = sessionManager.loadSession(fullId);
542084
542619
  if (data) {
542620
+ setAppSessionId(data.meta.id);
542085
542621
  tabMgr.restoreFromMeta(data.meta.tabs, data.meta.activeTabId, data.tabMessages, data.tabCoreMessages);
542086
542622
  setForgeModeHeader(data.meta.forgeMode);
542087
542623
  setExitSessionId(data.meta.id);
542088
- setAppSessionId(data.meta.id);
542089
542624
  for (const tab of data.meta.tabs) {
542090
542625
  if (tab.checkpointTags?.length) {
542091
542626
  useCheckpointStore.getState().restoreTagsFromMeta(tab.id, tab.checkpointTags ?? []);
@@ -542403,23 +542938,43 @@ function App({
542403
542938
  const activeChat = tabMgrRef.current?.getActiveChat();
542404
542939
  const hasContent = activeChat?.messages.some((m6) => m6.role === "user" || m6.role === "assistant");
542405
542940
  if (hasContent && activeChat) {
542406
- const snapshot = workspaceSnapshotRef.current?.();
542407
- if (snapshot) {
542408
- try {
542409
- const { meta: meta4, tabMessages, tabCoreMessages } = buildSessionMeta({
542410
- sessionId: getAppSessionId(),
542411
- title: activeChat.customTitle ?? SessionManager.deriveTitle(activeChat.messages),
542412
- customTitle: activeChat.customTitle,
542941
+ try {
542942
+ const sid = getAppSessionId();
542943
+ const liveTabs = tabMgrRef.current?.tabs ?? [];
542944
+ const activeTabId2 = tabMgrRef.current?.activeTabId ?? "";
542945
+ const liveIds = new Set(liveTabs.map((t3) => t3.id));
542946
+ const fallbackTitle = activeChat.customTitle ?? SessionManager.deriveTitle(activeChat.messages);
542947
+ for (const tab of liveTabs) {
542948
+ const chat = tabMgrRef.current?.getChat(tab.id);
542949
+ if (!chat)
542950
+ continue;
542951
+ const filtered = chat.messages.filter((m6) => m6.role !== "system" || m6.showInChat);
542952
+ const { tabMeta } = buildTabMeta({
542953
+ tabId: tab.id,
542954
+ tabLabel: tab.label,
542955
+ activeModel: chat.activeModel,
542956
+ sessionId: sid,
542957
+ planMode: chat.planMode,
542958
+ planRequest: chat.planRequest,
542959
+ coAuthorCommits: chat.coAuthorCommits,
542960
+ forgeMode: chat.forgeMode,
542961
+ tokenUsage: chat.tokenUsage,
542962
+ messages: filtered,
542963
+ coreMessages: chat.coreMessages
542964
+ });
542965
+ await sessionManager.saveTab(sid, tabMeta, filtered, chat.coreMessages, {
542966
+ title: fallbackTitle,
542967
+ customTitle: activeChat.customTitle ?? null,
542413
542968
  cwd: cwd2,
542414
- snapshot,
542415
- currentTabMessages: activeChat.messages.filter((m6) => m6.role !== "system" || m6.showInChat),
542416
- currentTabCoreMessages: activeChat.coreMessages
542969
+ forgeMode: activeChat.forgeMode,
542970
+ activeTabId: activeTabId2
542417
542971
  });
542418
- updateEmergencySnapshot(sessionManager, meta4, tabMessages, tabCoreMessages);
542419
- await sessionManager.saveSession(meta4, tabMessages, tabCoreMessages);
542420
- } catch (err2) {
542421
- logBackgroundError("new-session", `session save failed: ${err2 instanceof Error ? err2.message : String(err2)}`);
542422
542972
  }
542973
+ try {
542974
+ await sessionManager.pruneTabsNotIn(sid, liveIds);
542975
+ } catch {}
542976
+ } catch (err2) {
542977
+ logBackgroundError("new-session", `session save failed: ${err2 instanceof Error ? err2.message : String(err2)}`);
542423
542978
  }
542424
542979
  }
542425
542980
  const cpStore = useCheckpointStore.getState();
@@ -542863,7 +543418,18 @@ function App({
542863
543418
  }, undefined, false, undefined, this),
542864
543419
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(LlmSelector, {
542865
543420
  visible: modalLlmSelector,
542866
- activeModel: activeModelForHeader,
543421
+ activeModel: (() => {
543422
+ const slot = routerSlotPicking;
543423
+ const fb = useUIStore.getState().fallbackForModel;
543424
+ if (slot) {
543425
+ const v5 = effectiveConfig.taskRouter?.[slot];
543426
+ if (typeof v5 === "string" && v5.trim())
543427
+ return v5;
543428
+ }
543429
+ if (fb)
543430
+ return fb;
543431
+ return activeModelForHeader;
543432
+ })(),
542867
543433
  onSelect: (modelId) => {
542868
543434
  const slot = useUIStore.getState().routerSlotPicking;
542869
543435
  const fallbackForModel = useUIStore.getState().fallbackForModel;
@@ -542913,13 +543479,61 @@ function App({
542913
543479
  visible: modalSessionPicker,
542914
543480
  cwd: cwd2,
542915
543481
  onClose: getCloser2("sessionPicker"),
542916
- onRestore: (sessionId) => {
543482
+ onRestore: async (sessionId) => {
543483
+ try {
543484
+ const prevSid = getAppSessionId();
543485
+ if (prevSid !== sessionId) {
543486
+ const liveTabs = tabMgrRef.current?.tabs ?? [];
543487
+ const activeTabId2 = tabMgrRef.current?.activeTabId ?? "";
543488
+ const activeChat = tabMgrRef.current?.getActiveChat();
543489
+ const fallbackTitle = activeChat?.customTitle ?? SessionManager.deriveTitle(activeChat?.messages ?? []);
543490
+ const liveIds = new Set(liveTabs.map((t3) => t3.id));
543491
+ let savedAny = false;
543492
+ for (const tab of liveTabs) {
543493
+ const chat = tabMgrRef.current?.getChat(tab.id);
543494
+ if (!chat)
543495
+ continue;
543496
+ const filtered = chat.messages.filter((m6) => m6.role !== "system" || m6.showInChat);
543497
+ const hasContent = filtered.some((m6) => m6.role === "user" || m6.role === "assistant");
543498
+ if (!hasContent)
543499
+ continue;
543500
+ savedAny = true;
543501
+ const { tabMeta } = buildTabMeta({
543502
+ tabId: tab.id,
543503
+ tabLabel: tab.label,
543504
+ activeModel: chat.activeModel,
543505
+ sessionId: prevSid,
543506
+ planMode: chat.planMode,
543507
+ planRequest: chat.planRequest,
543508
+ coAuthorCommits: chat.coAuthorCommits,
543509
+ forgeMode: chat.forgeMode,
543510
+ tokenUsage: chat.tokenUsage,
543511
+ messages: filtered,
543512
+ coreMessages: chat.coreMessages
543513
+ });
543514
+ await sessionManager.saveTab(prevSid, tabMeta, filtered, chat.coreMessages, {
543515
+ title: fallbackTitle,
543516
+ customTitle: activeChat?.customTitle ?? null,
543517
+ cwd: cwd2,
543518
+ forgeMode: activeChat?.forgeMode ?? "default",
543519
+ activeTabId: activeTabId2
543520
+ });
543521
+ }
543522
+ if (savedAny) {
543523
+ try {
543524
+ await sessionManager.pruneTabsNotIn(prevSid, liveIds);
543525
+ } catch {}
543526
+ }
543527
+ }
543528
+ } catch (err2) {
543529
+ logBackgroundError("session-switch", `pre-switch save failed: ${err2 instanceof Error ? err2.message : String(err2)}`);
543530
+ }
542917
543531
  const data = sessionManager.loadSession(sessionId);
542918
543532
  if (data) {
543533
+ setAppSessionId(data.meta.id);
542919
543534
  tabMgr.restoreFromMeta(data.meta.tabs, data.meta.activeTabId, data.tabMessages, data.tabCoreMessages);
542920
543535
  setForgeModeHeader(data.meta.forgeMode);
542921
543536
  setExitSessionId(data.meta.id);
542922
- setAppSessionId(data.meta.id);
542923
543537
  for (const tab of data.meta.tabs) {
542924
543538
  if (tab.checkpointTags?.length) {
542925
543539
  useCheckpointStore.getState().restoreTagsFromMeta(tab.id, tab.checkpointTags ?? []);