@ai-matrx/coding-sessions 0.1.165 → 0.2.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.
@@ -1,5 +1,5 @@
1
- import { C as CodingSessionsMethod, a as CodingSessionsApi, b as CodingEvent, c as CodingSessionsEngine, d as CodingSessionsEngineOptions } from '../index-CDeDKQHe.cjs';
2
- export { e as CodingSessionsAgentsOptions } from '../index-CDeDKQHe.cjs';
1
+ import { C as CodingSessionsMethod, a as CodingSessionsApi, b as CodingEvent, c as CodingSessionsEngine, d as CodingSessionsEngineOptions } from '../index-29t7n472.cjs';
2
+ export { e as CodingSessionsAgentsOptions } from '../index-29t7n472.cjs';
3
3
  import '@ai-matrx/agents/portable';
4
4
  import '@ai-matrx/data';
5
5
  import '@ai-matrx/agents/portable/mcp';
@@ -1,5 +1,5 @@
1
- import { C as CodingSessionsMethod, a as CodingSessionsApi, b as CodingEvent, c as CodingSessionsEngine, d as CodingSessionsEngineOptions } from '../index-CDeDKQHe.js';
2
- export { e as CodingSessionsAgentsOptions } from '../index-CDeDKQHe.js';
1
+ import { C as CodingSessionsMethod, a as CodingSessionsApi, b as CodingEvent, c as CodingSessionsEngine, d as CodingSessionsEngineOptions } from '../index-29t7n472.js';
2
+ export { e as CodingSessionsAgentsOptions } from '../index-29t7n472.js';
3
3
  import '@ai-matrx/agents/portable';
4
4
  import '@ai-matrx/data';
5
5
  import '@ai-matrx/agents/portable/mcp';
@@ -1105,6 +1105,9 @@ var PROVIDER_LABEL = {
1105
1105
  claude: "Claude Code",
1106
1106
  codex: "Codex"
1107
1107
  };
1108
+ function chatKey(provider, providerSessionId, sessionId) {
1109
+ return providerSessionId ? `${provider}:${providerSessionId}` : `session:${sessionId ?? ""}`;
1110
+ }
1108
1111
  var TERMINAL_RUN_STATUSES = ["completed", "stopped", "failed"];
1109
1112
  function isTerminalRunStatus(status) {
1110
1113
  return TERMINAL_RUN_STATUSES.includes(status);
@@ -1419,6 +1422,78 @@ import fs4 from "fs/promises";
1419
1422
  import os3 from "os";
1420
1423
  import path5 from "path";
1421
1424
 
1425
+ // src/sessions/titles.ts
1426
+ var TITLE_MAX = 300;
1427
+ var BRIDGE_PLACEHOLDER = /^AI Matrx · [a-z-]+$/;
1428
+ var TITLE_PRECEDENCE = [
1429
+ /** The person's rename inside Matrx (Coding Sessions' preference). */
1430
+ "override",
1431
+ /** What the coding app's own sidebar shows: Claude's record `title` (renames included); Codex's thread name; Cursor's name. */
1432
+ "app",
1433
+ /** The title the app last showed, remembered by the claude-accounts engine after the record went away. */
1434
+ "app-remembered",
1435
+ /** Claude Code transcript entries (the last one of each kind that is not the bridge placeholder). */
1436
+ "custom-title",
1437
+ "ai-title",
1438
+ "agent-name",
1439
+ "summary",
1440
+ /** The person's first typed message (Codex: the thread's title / first user message). */
1441
+ "first-prompt",
1442
+ "last-prompt"
1443
+ ];
1444
+ var STAMPABLE = /* @__PURE__ */ new Set(["custom-title", "ai-title", "agent-name"]);
1445
+ function isBridgePlaceholder(value) {
1446
+ return typeof value === "string" && BRIDGE_PLACEHOLDER.test(value.trim());
1447
+ }
1448
+ function cleanTitle(value) {
1449
+ if (typeof value !== "string") return null;
1450
+ const line = value.split(/\r?\n/).map((l) => l.trim()).find((l) => l.length > 0);
1451
+ if (!line) return null;
1452
+ return line.length > TITLE_MAX ? `${line.slice(0, TITLE_MAX - 1)}\u2026` : line;
1453
+ }
1454
+ function resolveSessionTitle(sources) {
1455
+ for (const source of TITLE_PRECEDENCE) {
1456
+ const raw = sources[source];
1457
+ if (STAMPABLE.has(source) && isBridgePlaceholder(raw)) continue;
1458
+ const title = cleanTitle(raw);
1459
+ if (title) return { title, source };
1460
+ }
1461
+ return null;
1462
+ }
1463
+ function claudeTranscriptTitleSources(head, tail = []) {
1464
+ const out = {};
1465
+ const take = (source, value) => {
1466
+ if (typeof value !== "string" || !value.trim()) return;
1467
+ if (STAMPABLE.has(source) && isBridgePlaceholder(value)) return;
1468
+ out[source] = value;
1469
+ };
1470
+ const normalizer = createClaudeNormalizer();
1471
+ let firstPrompt = null;
1472
+ const scan = (entries, findPrompt) => {
1473
+ for (const entry of entries) {
1474
+ if (typeof entry !== "object" || entry === null) continue;
1475
+ const e = entry;
1476
+ if (e.type === "custom-title") take("custom-title", e.customTitle);
1477
+ else if (e.type === "ai-title") take("ai-title", e.aiTitle);
1478
+ else if (e.type === "agent-name") take("agent-name", e.agentName);
1479
+ else if (e.type === "summary") take("summary", e.summary);
1480
+ else if (e.type === "last-prompt") take("last-prompt", e.lastPrompt);
1481
+ else if (findPrompt && firstPrompt === null) {
1482
+ for (const item of normalizer.push(entry)) {
1483
+ if (item.kind === "user" && item.text.trim()) {
1484
+ firstPrompt = item.text;
1485
+ break;
1486
+ }
1487
+ }
1488
+ }
1489
+ }
1490
+ };
1491
+ scan(head, true);
1492
+ scan(tail, false);
1493
+ if (firstPrompt !== null) out["first-prompt"] = firstPrompt;
1494
+ return out;
1495
+ }
1496
+
1422
1497
  // src/engine/activity.ts
1423
1498
  import { execFile as execFile2 } from "child_process";
1424
1499
  var RECENT_WRITE_MS = 2 * 60 * 1e3;
@@ -1484,11 +1559,17 @@ function describeActivity(input) {
1484
1559
  }
1485
1560
 
1486
1561
  // src/engine/files.ts
1562
+ function codexHome(dir) {
1563
+ return {
1564
+ sessions: path5.join(dir, "sessions"),
1565
+ archived: path5.join(dir, "archived_sessions"),
1566
+ index: path5.join(dir, "session_index.jsonl")
1567
+ };
1568
+ }
1487
1569
  function defaultProviderHomes(home = os3.homedir()) {
1488
1570
  return {
1489
1571
  claudeProjects: path5.join(home, ".claude", "projects"),
1490
- codexSessions: path5.join(home, ".codex", "sessions"),
1491
- codexIndex: path5.join(home, ".codex", "session_index.jsonl")
1572
+ codex: [codexHome(path5.join(home, ".codex"))]
1492
1573
  };
1493
1574
  }
1494
1575
  var UUIDISH = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
@@ -1522,7 +1603,7 @@ async function claudeCandidates(root) {
1522
1603
  }
1523
1604
  return out;
1524
1605
  }
1525
- async function codexCandidates(root) {
1606
+ async function codexTree(root) {
1526
1607
  const out = [];
1527
1608
  for (const year of await safeReaddir(root)) {
1528
1609
  if (!year.isDirectory()) continue;
@@ -1530,17 +1611,23 @@ async function codexCandidates(root) {
1530
1611
  if (!month.isDirectory()) continue;
1531
1612
  for (const day of await safeReaddir(path5.join(root, year.name, month.name))) {
1532
1613
  if (!day.isDirectory()) continue;
1533
- const dir = path5.join(root, year.name, month.name, day.name);
1534
- const entries = await safeReaddir(dir);
1535
- const stats = await Promise.all(
1536
- entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", path5.join(dir, e.name)))
1537
- );
1538
- for (const s of stats) if (s) out.push(s);
1614
+ out.push(...await codexFlat(path5.join(root, year.name, month.name, day.name), false));
1539
1615
  }
1540
1616
  }
1541
1617
  }
1542
1618
  return out;
1543
1619
  }
1620
+ async function codexFlat(dir, archived) {
1621
+ const entries = await safeReaddir(dir);
1622
+ const stats = await Promise.all(
1623
+ entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", path5.join(dir, e.name)))
1624
+ );
1625
+ return stats.filter((s) => s !== null).map((s) => archived ? { ...s, archived: true } : s);
1626
+ }
1627
+ async function codexCandidates(homes) {
1628
+ const lists = await Promise.all(homes.flatMap((h) => [codexTree(h.sessions), codexFlat(h.archived, true)]));
1629
+ return lists.flat();
1630
+ }
1544
1631
  async function readSlice(file, start, length) {
1545
1632
  const handle = await fs4.open(file, "r");
1546
1633
  try {
@@ -1562,56 +1649,38 @@ function completeLines(text, droppedFirst) {
1562
1649
  }
1563
1650
  var HEAD_BYTES = 256 * 1024;
1564
1651
  var TAIL_BYTES = 128 * 1024;
1565
- function oneLine(text, max = 140) {
1566
- const flat = text.replace(/\s+/g, " ").trim();
1567
- return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
1568
- }
1569
1652
  async function describeClaude(candidate) {
1570
1653
  const head = parseJsonl(completeLines(await readSlice(candidate.file, 0, HEAD_BYTES), false));
1571
1654
  const tailStart = Math.max(0, candidate.size - TAIL_BYTES);
1572
1655
  const tail = tailStart > 0 ? parseJsonl(completeLines(await readSlice(candidate.file, tailStart, TAIL_BYTES), true)) : head;
1573
1656
  let cwd = null;
1574
- let firstPrompt = null;
1575
1657
  let entrypoint = null;
1576
- const normalizer = createClaudeNormalizer();
1577
1658
  for (const entry of head) {
1578
1659
  if (typeof entry !== "object" || entry === null) continue;
1579
1660
  const e = entry;
1580
1661
  if (!cwd && typeof e.cwd === "string") cwd = e.cwd;
1581
1662
  if (!entrypoint && typeof e.entrypoint === "string") entrypoint = e.entrypoint;
1582
- if (firstPrompt === null) {
1583
- for (const item of normalizer.push(entry)) {
1584
- if (item.kind === "user") {
1585
- firstPrompt = item.text;
1586
- break;
1587
- }
1588
- }
1589
- }
1590
- if (cwd && firstPrompt !== null) break;
1663
+ if (cwd && entrypoint) break;
1591
1664
  }
1592
- const titles = { custom: null, ai: null, agent: null, summary: null, last: null };
1593
- for (const entry of [...head, ...tail]) {
1594
- if (typeof entry !== "object" || entry === null) continue;
1595
- const e = entry;
1596
- if (e.type === "custom-title" && typeof e.customTitle === "string") titles.custom = e.customTitle;
1597
- if (e.type === "ai-title" && typeof e.aiTitle === "string") titles.ai = e.aiTitle;
1598
- if (e.type === "agent-name" && typeof e.agentName === "string") titles.agent = e.agentName;
1599
- if (e.type === "summary" && typeof e.summary === "string") titles.summary = e.summary;
1600
- if (e.type === "last-prompt" && typeof e.lastPrompt === "string") titles.last = e.lastPrompt;
1601
- }
1602
- const title = titles.custom ?? titles.ai ?? titles.agent ?? titles.summary ?? firstPrompt ?? titles.last;
1603
- if (!title) return null;
1665
+ const resolved = resolveSessionTitle(claudeTranscriptTitleSources(head, tailStart > 0 ? tail : []));
1666
+ if (!resolved) return null;
1604
1667
  return {
1605
1668
  provider: "claude",
1606
1669
  providerSessionId: candidate.id,
1607
1670
  cwd,
1608
- title: oneLine(title),
1671
+ title: resolved.title,
1672
+ titleSource: resolved.source,
1609
1673
  updatedAt: new Date(candidate.mtimeMs).toISOString(),
1610
1674
  source: entrypoint,
1611
1675
  activeElsewhere: null
1612
1676
  };
1613
1677
  }
1614
- async function readCodexIndex(file) {
1678
+ async function readCodexIndex(files) {
1679
+ const names = /* @__PURE__ */ new Map();
1680
+ for (const file of files) for (const [id, name] of await readCodexIndexFile(file)) if (!names.has(id)) names.set(id, name);
1681
+ return names;
1682
+ }
1683
+ async function readCodexIndexFile(file) {
1615
1684
  const names = /* @__PURE__ */ new Map();
1616
1685
  let text;
1617
1686
  try {
@@ -1657,13 +1726,15 @@ async function describeCodex(candidate, names) {
1657
1726
  }
1658
1727
  }
1659
1728
  if (isSubAgent) return null;
1660
- const title = names.get(candidate.id) ?? firstPrompt;
1661
- if (!title) return null;
1729
+ const resolved = resolveSessionTitle({ app: names.get(candidate.id), "first-prompt": firstPrompt });
1730
+ if (!resolved) return null;
1662
1731
  return {
1663
1732
  provider: "codex",
1664
1733
  providerSessionId: candidate.id,
1665
1734
  cwd,
1666
- title: oneLine(title),
1735
+ title: resolved.title,
1736
+ titleSource: resolved.source,
1737
+ ...candidate.archived ? { archived: true } : {},
1667
1738
  updatedAt: new Date(candidate.mtimeMs).toISOString(),
1668
1739
  source,
1669
1740
  activeElsewhere: null
@@ -1708,8 +1779,8 @@ async function discoverLocalSessions(options = {}) {
1708
1779
  const homes = options.homes ?? defaultProviderHomes();
1709
1780
  const [claude, codex, names] = await Promise.all([
1710
1781
  claudeCandidates(homes.claudeProjects),
1711
- codexCandidates(homes.codexSessions),
1712
- readCodexIndex(homes.codexIndex)
1782
+ codexCandidates(homes.codex),
1783
+ readCodexIndex(homes.codex.map((h) => h.index))
1713
1784
  ]);
1714
1785
  const candidates = [...claude, ...codex].sort((a, b) => b.mtimeMs - a.mtimeMs);
1715
1786
  const processes = await listProcesses();
@@ -1729,7 +1800,7 @@ async function discoverLocalSessions(options = {}) {
1729
1800
  }
1730
1801
  async function findSessionFile(provider, providerSessionId, homes = defaultProviderHomes()) {
1731
1802
  if (!/^[0-9a-f-]{36}$/i.test(providerSessionId)) return null;
1732
- const candidates = provider === "claude" ? await claudeCandidates(homes.claudeProjects) : await codexCandidates(homes.codexSessions);
1803
+ const candidates = provider === "claude" ? await claudeCandidates(homes.claudeProjects) : await codexCandidates(homes.codex);
1733
1804
  const hits = candidates.filter((c) => c.id === providerSessionId).sort((a, b) => b.mtimeMs - a.mtimeMs);
1734
1805
  return hits[0]?.file ?? null;
1735
1806
  }
@@ -1781,7 +1852,7 @@ async function readSessionFile(provider, file, endOffset, maxBytes = HISTORY_BUD
1781
1852
  async function listSessionFiles(homes = defaultProviderHomes()) {
1782
1853
  const [claude, codex] = await Promise.all([
1783
1854
  claudeCandidates(homes.claudeProjects),
1784
- codexCandidates(homes.codexSessions)
1855
+ codexCandidates(homes.codex)
1785
1856
  ]);
1786
1857
  return [...claude, ...codex].sort((a, b) => b.mtimeMs - a.mtimeMs);
1787
1858
  }
@@ -1824,7 +1895,7 @@ var PreferenceStore = class {
1824
1895
  if (!key || key.length > 200) throw new Error("That chat can't be found.");
1825
1896
  const next = { ...this.data[key] };
1826
1897
  if ("title" in patch) {
1827
- const title = typeof patch.title === "string" ? patch.title.replace(/\s+/g, " ").trim().slice(0, 200) : "";
1898
+ const title = typeof patch.title === "string" ? patch.title.replace(/\s+/g, " ").trim().slice(0, TITLE_MAX) : "";
1828
1899
  if (title) next.title = title;
1829
1900
  else delete next.title;
1830
1901
  }
@@ -1842,6 +1913,18 @@ var PreferenceStore = class {
1842
1913
  this.persist();
1843
1914
  return this.all();
1844
1915
  }
1916
+ /**
1917
+ * Move a preference to the chat's lasting key (`session:<id>` → `<provider>:<id>` once the
1918
+ * provider's id is known), so a rename made early is the one rename everywhere. A preference
1919
+ * already under the lasting key wins field by field.
1920
+ */
1921
+ rekey(from, to) {
1922
+ if (from === to || !this.data[from]) return false;
1923
+ this.data[to] = { ...this.data[from], ...this.data[to] };
1924
+ delete this.data[from];
1925
+ this.persist();
1926
+ return true;
1927
+ }
1845
1928
  persist() {
1846
1929
  const snapshot = JSON.stringify(this.data, null, 1);
1847
1930
  this.writing = this.writing.then(async () => {
@@ -1969,7 +2052,7 @@ async function searchLocalChats(query, options = {}) {
1969
2052
  const raw = JSON.stringify(needle).slice(1, -1);
1970
2053
  const limit = Math.max(1, Math.min(options.limit ?? 30, 100));
1971
2054
  const deadline = Date.now() + (options.budgetMs ?? 3500);
1972
- const names = await readCodexIndex(homes.codexIndex);
2055
+ const names = await readCodexIndex(homes.codex.map((h) => h.index));
1973
2056
  const hits = [];
1974
2057
  let i = cursor;
1975
2058
  for (; i < files.length && hits.length < limit && Date.now() < deadline; i += 1) {
@@ -2004,6 +2087,50 @@ async function searchLocalChats(query, options = {}) {
2004
2087
  };
2005
2088
  }
2006
2089
 
2090
+ // src/sessions/hints.ts
2091
+ function titleHintKey(provider, sessionId) {
2092
+ return `${provider}:${sessionId}`;
2093
+ }
2094
+
2095
+ // src/engine/title-hints.ts
2096
+ function overlayHint(chat, hints, expected) {
2097
+ if (!expected) return chat;
2098
+ if (hints === null) return { ...chat, titlePending: true };
2099
+ const hint = hints.get(titleHintKey(chat.provider, chat.providerSessionId));
2100
+ if (!hint) return chat;
2101
+ return {
2102
+ ...chat,
2103
+ title: hint.title,
2104
+ titleSource: hint.source,
2105
+ ...hint.archived || chat.archived ? { archived: true } : {},
2106
+ ...hint.scheduled ? { scheduled: true } : {}
2107
+ };
2108
+ }
2109
+ function unavailableFromHints(hints, covered) {
2110
+ if (!hints) return [];
2111
+ const out = [];
2112
+ for (const [key, hint] of hints) {
2113
+ if (!hint.unavailable || covered.has(key)) continue;
2114
+ const at = key.indexOf(":");
2115
+ const provider = key.slice(0, at);
2116
+ if (provider !== "claude" && provider !== "codex") continue;
2117
+ out.push({
2118
+ provider,
2119
+ providerSessionId: key.slice(at + 1),
2120
+ cwd: null,
2121
+ title: hint.title,
2122
+ titleSource: hint.source,
2123
+ updatedAt: new Date(hint.lastActiveMs ?? 0).toISOString(),
2124
+ source: null,
2125
+ activeElsewhere: null,
2126
+ unavailable: hint.unavailable,
2127
+ ...hint.archived ? { archived: true } : {},
2128
+ ...hint.scheduled ? { scheduled: true } : {}
2129
+ });
2130
+ }
2131
+ return out.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
2132
+ }
2133
+
2007
2134
  // src/engine/store.ts
2008
2135
  import { randomUUID as randomUUID3 } from "crypto";
2009
2136
  import fs6 from "fs/promises";
@@ -2354,6 +2481,13 @@ async function createCodingSessionsEngine(options = {}) {
2354
2481
  }
2355
2482
  }
2356
2483
  }
2484
+ const titleHints = options.titleHints;
2485
+ const hintsNow = () => titleHints?.current() ?? null;
2486
+ const unsubscribeHints = titleHints?.subscribe(() => emit({ type: "titles" })) ?? null;
2487
+ const withHintTitle = (session) => {
2488
+ const hint = session.providerSessionId ? hintsNow()?.get(titleHintKey(session.provider, session.providerSessionId)) : void 0;
2489
+ return hint ? { ...session, title: hint.title } : session;
2490
+ };
2357
2491
  function saveSession(session) {
2358
2492
  index.putSession(session);
2359
2493
  emit({ type: "session", session });
@@ -2510,7 +2644,12 @@ async function createCodingSessionsEngine(options = {}) {
2510
2644
  if (sid && sid !== knownProviderSession) {
2511
2645
  knownProviderSession = sid;
2512
2646
  const current = index.session(session.id);
2513
- if (current) saveSession({ ...current, providerSessionId: sid });
2647
+ if (current) {
2648
+ saveSession({ ...current, providerSessionId: sid });
2649
+ if (preferences.rekey(chatKey(current.provider, null, current.id), chatKey(current.provider, sid))) {
2650
+ emit({ type: "preferences", preferences: preferences.all() });
2651
+ }
2652
+ }
2514
2653
  }
2515
2654
  for (const item of items) emitItem(liveRun, item);
2516
2655
  }
@@ -2818,7 +2957,9 @@ async function createCodingSessionsEngine(options = {}) {
2818
2957
  );
2819
2958
  },
2820
2959
  async discoverSessions(opts) {
2821
- const found = await discoverLocalSessions({ limit: opts?.limit, homes, excludePids: ourPids() });
2960
+ const read = await discoverLocalSessions({ limit: opts?.limit, homes, excludePids: ourPids() });
2961
+ const hints = hintsNow();
2962
+ const found = read.map((chat) => overlayHint(chat, hints, titleHints !== void 0));
2822
2963
  if (backups) {
2823
2964
  for (const chat of found) {
2824
2965
  const key = `${chat.provider}:${chat.providerSessionId}`;
@@ -2829,7 +2970,12 @@ async function createCodingSessionsEngine(options = {}) {
2829
2970
  }
2830
2971
  }
2831
2972
  }
2832
- return found;
2973
+ const limit = opts?.limit ?? 60;
2974
+ const oldest = found.length >= limit ? found[found.length - 1]?.updatedAt ?? "" : "";
2975
+ const gone = unavailableFromHints(hints, new Set(found.map((c) => titleHintKey(c.provider, c.providerSessionId)))).filter(
2976
+ (c) => c.updatedAt >= oldest
2977
+ );
2978
+ return gone.length ? [...found, ...gone].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)).slice(0, limit) : found;
2833
2979
  },
2834
2980
  async recentFolders() {
2835
2981
  const seen = /* @__PURE__ */ new Map();
@@ -2850,7 +2996,7 @@ async function createCodingSessionsEngine(options = {}) {
2850
2996
  );
2851
2997
  },
2852
2998
  async listSessions() {
2853
- return index.sessions();
2999
+ return index.sessions().map(withHintTitle);
2854
3000
  },
2855
3001
  async listRuns(sessionId) {
2856
3002
  const session = index.session(sessionId);
@@ -3025,7 +3171,9 @@ async function createCodingSessionsEngine(options = {}) {
3025
3171
  return all;
3026
3172
  },
3027
3173
  async searchChats(query, opts) {
3028
- return searchLocalChats(String(query ?? ""), { homes, cursor: opts?.cursor, limit: opts?.limit });
3174
+ const result = await searchLocalChats(String(query ?? ""), { homes, cursor: opts?.cursor, limit: opts?.limit });
3175
+ const hints = hintsNow();
3176
+ return { ...result, hits: result.hits.map((hit) => ({ ...hit, title: hints?.get(titleHintKey(hit.provider, hit.providerSessionId))?.title ?? hit.title })) };
3029
3177
  },
3030
3178
  async openFolder(raw, target) {
3031
3179
  const folder = await assertFolder(String(raw ?? ""));
@@ -3055,6 +3203,7 @@ async function createCodingSessionsEngine(options = {}) {
3055
3203
  return agents.handleMcp(secret, body);
3056
3204
  },
3057
3205
  async dispose() {
3206
+ unsubscribeHints?.();
3058
3207
  for (const liveRun of live.values()) {
3059
3208
  liveRun.stopRequested = true;
3060
3209
  if (liveRun.pgid !== null) killGroup(liveRun);