@ai-matrx/coding-sessions 0.2.0 → 0.3.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/engine/index.cjs +54 -10
  3. package/dist/engine/index.cjs.map +1 -1
  4. package/dist/engine/index.d.cts +1 -1
  5. package/dist/engine/index.d.ts +1 -1
  6. package/dist/engine/index.js +54 -10
  7. package/dist/engine/index.js.map +1 -1
  8. package/dist/{index-29t7n472.d.cts → index-Bbe11HoB.d.cts} +30 -3
  9. package/dist/{index-29t7n472.d.ts → index-Bbe11HoB.d.ts} +30 -3
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +12 -3
  12. package/dist/index.d.ts +12 -3
  13. package/dist/index.js.map +1 -1
  14. package/dist/react/index.cjs +34 -5
  15. package/dist/react/index.cjs.map +1 -1
  16. package/dist/react/index.d.cts +15 -1
  17. package/dist/react/index.d.ts +15 -1
  18. package/dist/react/index.js +34 -5
  19. package/dist/react/index.js.map +1 -1
  20. package/dist/server/index.cjs +44 -9
  21. package/dist/server/index.cjs.map +1 -1
  22. package/dist/server/index.d.cts +2 -2
  23. package/dist/server/index.d.ts +2 -2
  24. package/dist/server/index.js +44 -9
  25. package/dist/server/index.js.map +1 -1
  26. package/dist/sessions/index.cjs +26 -1
  27. package/dist/sessions/index.cjs.map +1 -1
  28. package/dist/sessions/index.d.cts +13 -5
  29. package/dist/sessions/index.d.ts +13 -5
  30. package/dist/sessions/index.js +26 -1
  31. package/dist/sessions/index.js.map +1 -1
  32. package/dist/{titles-eCnA5sY7.d.cts → titles-DjxUb0ji.d.cts} +8 -1
  33. package/dist/{titles-eCnA5sY7.d.ts → titles-DjxUb0ji.d.ts} +8 -1
  34. package/dist/ui/assets/index-BqzTnRFe.css +2 -0
  35. package/dist/ui/assets/{index-SuRr0-D2.js → index-U98AY8r-.js} +17 -17
  36. package/dist/ui/index.html +2 -2
  37. package/package.json +3 -3
  38. package/dist/ui/assets/index-BqpVx_My.css +0 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @ai-matrx/coding-sessions
2
2
 
3
+ ## 0.3.0
4
+
5
+ - With an owner of the apps' index (`titleHints`), this package reads none of the apps' own files:
6
+ Codex thread names and title search come from the owner (`appTitles`); the standalone host still
7
+ reads `session_index.jsonl` itself.
8
+ - `titleStatus()` / `retryTitles()` (engine + bridge): a failing owner shows "app titles unavailable —
9
+ Retry" beside Your other chats, never a silent fallback. `TitleHints.problem()` / `retry()`.
10
+ - `displayTitle()` (`./sessions`): the list's trim — leading list markers and quotes dropped, cut to
11
+ `DISPLAY_MAX` (100); the stored title stays exact and is the hover text. Vectors in the one file.
12
+ - A held chat whose own file is gone reads its history from the holder's copy
13
+ (`homes.claudeVault`, default `~/.claude/claude-app-vault/<id>/transcript.jsonl`).
14
+ - `engine.setHomes()`: a host that learns the homes later (Matrx 2: Codex homes from its core).
15
+ - `TitleHint.source` / `DiscoveredSession.titleSource` may be `"unknown"` (an owner that did not say).
16
+ - **Consumer action:** none for hosts without an owner; Matrx 2 passes `titleHints` with `problem`/`retry`.
17
+
3
18
  ## 0.2.0
4
19
 
5
20
  - **`./sessions` (new, pure):** the one title rule for a coding chat — `resolveSessionTitle` (override >
@@ -1663,6 +1663,7 @@ function codexHome(dir) {
1663
1663
  function defaultProviderHomes(home = import_node_os3.default.homedir()) {
1664
1664
  return {
1665
1665
  claudeProjects: import_node_path5.default.join(home, ".claude", "projects"),
1666
+ claudeVault: import_node_path5.default.join(home, ".claude", "claude-app-vault"),
1666
1667
  codex: [codexHome(import_node_path5.default.join(home, ".codex"))]
1667
1668
  };
1668
1669
  }
@@ -1834,6 +1835,12 @@ async function describeCodex(candidate, names) {
1834
1835
  activeElsewhere: null
1835
1836
  };
1836
1837
  }
1838
+ async function codexNames(homes, appTitles) {
1839
+ if (!appTitles) return readCodexIndex(homes.codex.map((h) => h.index));
1840
+ const out = /* @__PURE__ */ new Map();
1841
+ for (const [key, title] of appTitles) if (key.startsWith("codex:")) out.set(key.slice(6), title);
1842
+ return out;
1843
+ }
1837
1844
  async function readTail(file, size) {
1838
1845
  const start = Math.max(0, size - TAIL_BYTES);
1839
1846
  return parseJsonl(completeLines(await readSlice(file, start, Math.min(size, TAIL_BYTES)), start > 0));
@@ -1874,7 +1881,7 @@ async function discoverLocalSessions(options = {}) {
1874
1881
  const [claude, codex, names] = await Promise.all([
1875
1882
  claudeCandidates(homes.claudeProjects),
1876
1883
  codexCandidates(homes.codex),
1877
- readCodexIndex(homes.codex.map((h) => h.index))
1884
+ codexNames(homes, options.appTitles)
1878
1885
  ]);
1879
1886
  const candidates = [...claude, ...codex].sort((a, b) => b.mtimeMs - a.mtimeMs);
1880
1887
  const processes = await listProcesses();
@@ -1896,7 +1903,12 @@ async function findSessionFile(provider, providerSessionId, homes = defaultProvi
1896
1903
  if (!/^[0-9a-f-]{36}$/i.test(providerSessionId)) return null;
1897
1904
  const candidates = provider === "claude" ? await claudeCandidates(homes.claudeProjects) : await codexCandidates(homes.codex);
1898
1905
  const hits = candidates.filter((c) => c.id === providerSessionId).sort((a, b) => b.mtimeMs - a.mtimeMs);
1899
- return hits[0]?.file ?? null;
1906
+ if (hits[0]) return hits[0].file;
1907
+ if (provider === "claude" && homes.claudeVault) {
1908
+ const copy = import_node_path5.default.join(homes.claudeVault, providerSessionId, "transcript.jsonl");
1909
+ if ((await import_promises4.default.stat(copy).catch(() => null))?.isFile()) return copy;
1910
+ }
1911
+ return null;
1900
1912
  }
1901
1913
  var HISTORY_BUDGET = 24 * 1024 * 1024;
1902
1914
  var MAX_HISTORY_BUDGET = 256 * 1024 * 1024;
@@ -1950,8 +1962,8 @@ async function listSessionFiles(homes = defaultProviderHomes()) {
1950
1962
  ]);
1951
1963
  return [...claude, ...codex].sort((a, b) => b.mtimeMs - a.mtimeMs);
1952
1964
  }
1953
- async function describeSessionFile(candidate, codexNames) {
1954
- return (candidate.provider === "claude" ? describeClaude(candidate) : describeCodex(candidate, codexNames)).catch(
1965
+ async function describeSessionFile(candidate, codexNames2) {
1966
+ return (candidate.provider === "claude" ? describeClaude(candidate) : describeCodex(candidate, codexNames2)).catch(
1955
1967
  () => null
1956
1968
  );
1957
1969
  }
@@ -2146,12 +2158,12 @@ async function searchLocalChats(query, options = {}) {
2146
2158
  const raw = JSON.stringify(needle).slice(1, -1);
2147
2159
  const limit = Math.max(1, Math.min(options.limit ?? 30, 100));
2148
2160
  const deadline = Date.now() + (options.budgetMs ?? 3500);
2149
- const names = await readCodexIndex(homes.codex.map((h) => h.index));
2161
+ const names = await codexNames(homes, options.appTitles);
2150
2162
  const hits = [];
2151
2163
  let i = cursor;
2152
2164
  for (; i < files.length && hits.length < limit && Date.now() < deadline; i += 1) {
2153
2165
  const candidate = files[i];
2154
- const named = candidate.provider === "codex" ? names.get(candidate.id) : void 0;
2166
+ const named = options.appTitles?.get(`${candidate.provider}:${candidate.id}`) ?? (candidate.provider === "codex" ? names.get(candidate.id) : void 0);
2155
2167
  const nameHit = named !== void 0 && named.toLowerCase().includes(needle);
2156
2168
  const scanned = nameHit ? null : await scanFile(candidate.provider, candidate.file, needle, raw, deadline);
2157
2169
  if (scanned === TIMED_OUT) {
@@ -2187,19 +2199,35 @@ function titleHintKey(provider, sessionId) {
2187
2199
  }
2188
2200
  function createTitleHintsStore() {
2189
2201
  let hints = null;
2202
+ let problem = null;
2190
2203
  const listeners = /* @__PURE__ */ new Set();
2204
+ const tell = () => {
2205
+ for (const l of listeners) l();
2206
+ };
2191
2207
  return {
2192
2208
  current: () => hints,
2209
+ problem: () => problem,
2193
2210
  subscribe(listener) {
2194
2211
  listeners.add(listener);
2195
2212
  return () => listeners.delete(listener);
2196
2213
  },
2197
2214
  set(next) {
2198
2215
  hints = next;
2199
- for (const l of listeners) l();
2216
+ problem = null;
2217
+ tell();
2218
+ },
2219
+ setProblem(next) {
2220
+ if (next === problem) return;
2221
+ problem = next;
2222
+ tell();
2200
2223
  }
2201
2224
  };
2202
2225
  }
2226
+ function appTitlesOf(hints) {
2227
+ const out = /* @__PURE__ */ new Map();
2228
+ for (const [key, hint] of hints) out.set(key, hint.title);
2229
+ return out;
2230
+ }
2203
2231
 
2204
2232
  // src/engine/title-hints.ts
2205
2233
  function overlayHint(chat, hints, expected) {
@@ -2510,7 +2538,7 @@ function describeAuthStatus(provider, probe) {
2510
2538
  async function createCodingSessionsEngine(options = {}) {
2511
2539
  const dataDir = options.dataDir ?? import_node_path9.default.join(import_node_os5.default.homedir(), ".matrx", "coding-sessions");
2512
2540
  const stateDir = import_node_path9.default.join(dataDir, "paperclip-state");
2513
- const homes = options.homes ?? defaultProviderHomes();
2541
+ let homes = options.homes ?? defaultProviderHomes();
2514
2542
  const index = new RunIndex(dataDir);
2515
2543
  await index.load();
2516
2544
  const preferences = new PreferenceStore(dataDir);
@@ -2592,6 +2620,11 @@ async function createCodingSessionsEngine(options = {}) {
2592
2620
  }
2593
2621
  const titleHints = options.titleHints;
2594
2622
  const hintsNow = () => titleHints?.current() ?? null;
2623
+ const appTitles = () => {
2624
+ if (!titleHints) return void 0;
2625
+ const hints = hintsNow();
2626
+ return hints ? appTitlesOf(hints) : /* @__PURE__ */ new Map();
2627
+ };
2595
2628
  const unsubscribeHints = titleHints?.subscribe(() => emit({ type: "titles" })) ?? null;
2596
2629
  const withHintTitle = (session) => {
2597
2630
  const hint = session.providerSessionId ? hintsNow()?.get(titleHintKey(session.provider, session.providerSessionId)) : void 0;
@@ -3066,7 +3099,7 @@ async function createCodingSessionsEngine(options = {}) {
3066
3099
  );
3067
3100
  },
3068
3101
  async discoverSessions(opts) {
3069
- const read = await discoverLocalSessions({ limit: opts?.limit, homes, excludePids: ourPids() });
3102
+ const read = await discoverLocalSessions({ limit: opts?.limit, homes, excludePids: ourPids(), appTitles: appTitles() });
3070
3103
  const hints = hintsNow();
3071
3104
  const found = read.map((chat) => overlayHint(chat, hints, titleHints !== void 0));
3072
3105
  if (backups) {
@@ -3104,6 +3137,17 @@ async function createCodingSessionsEngine(options = {}) {
3104
3137
  }))
3105
3138
  );
3106
3139
  },
3140
+ async titleStatus() {
3141
+ if (!titleHints) return { owner: false, pending: false, problem: null };
3142
+ const problem = titleHints.problem?.() ?? null;
3143
+ return { owner: true, pending: hintsNow() === null && problem === null, problem };
3144
+ },
3145
+ async retryTitles() {
3146
+ titleHints?.retry?.();
3147
+ },
3148
+ setHomes(next) {
3149
+ homes = next;
3150
+ },
3107
3151
  async listSessions() {
3108
3152
  return index.sessions().map(withHintTitle);
3109
3153
  },
@@ -3280,7 +3324,7 @@ async function createCodingSessionsEngine(options = {}) {
3280
3324
  return all;
3281
3325
  },
3282
3326
  async searchChats(query, opts) {
3283
- const result = await searchLocalChats(String(query ?? ""), { homes, cursor: opts?.cursor, limit: opts?.limit });
3327
+ const result = await searchLocalChats(String(query ?? ""), { homes, cursor: opts?.cursor, limit: opts?.limit, appTitles: appTitles() });
3284
3328
  const hints = hintsNow();
3285
3329
  return { ...result, hits: result.hits.map((hit) => ({ ...hit, title: hints?.get(titleHintKey(hit.provider, hit.providerSessionId))?.title ?? hit.title })) };
3286
3330
  },