@ai-matrx/coding-sessions 0.1.166 → 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 +35 -0
  2. package/dist/engine/index.cjs +273 -59
  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 +269 -58
  7. package/dist/engine/index.js.map +1 -1
  8. package/dist/{index-CDeDKQHe.d.cts → index-Bbe11HoB.d.cts} +108 -3
  9. package/dist/{index-CDeDKQHe.d.ts → index-Bbe11HoB.d.ts} +108 -3
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +30 -1
  12. package/dist/index.d.ts +30 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/react/index.cjs +47 -6
  15. package/dist/react/index.cjs.map +1 -1
  16. package/dist/react/index.d.cts +37 -0
  17. package/dist/react/index.d.ts +37 -0
  18. package/dist/react/index.js +47 -6
  19. package/dist/react/index.js.map +1 -1
  20. package/dist/server/index.cjs +241 -57
  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 +241 -57
  25. package/dist/server/index.js.map +1 -1
  26. package/dist/sessions/index.cjs +360 -0
  27. package/dist/sessions/index.cjs.map +1 -0
  28. package/dist/sessions/index.d.cts +71 -0
  29. package/dist/sessions/index.d.ts +71 -0
  30. package/dist/sessions/index.js +337 -0
  31. package/dist/sessions/index.js.map +1 -0
  32. package/dist/titles-DjxUb0ji.d.cts +48 -0
  33. package/dist/titles-DjxUb0ji.d.ts +48 -0
  34. package/dist/ui/assets/index-BqzTnRFe.css +2 -0
  35. package/dist/ui/assets/{index-7eK8XQtL.js → index-U98AY8r-.js} +17 -17
  36. package/dist/ui/index.html +2 -2
  37. package/package.json +13 -3
  38. package/dist/ui/assets/index-BqpVx_My.css +0 -2
@@ -2,6 +2,10 @@ import { ReactNode } from 'react';
2
2
  import { JsonValue } from '@ai-matrx/agents/portable';
3
3
  import { ChatThreadStore, ChatMessage } from '@ai-matrx/chat';
4
4
 
5
+ /** Where a title came from, strongest first. */
6
+ declare const TITLE_PRECEDENCE: readonly ["override", "app", "app-remembered", "custom-title", "ai-title", "agent-name", "summary", "first-prompt", "last-prompt"];
7
+ type TitleSource = (typeof TITLE_PRECEDENCE)[number];
8
+
5
9
  /**
6
10
  * The public contract of `@ai-matrx/coding-sessions`, shared by the engine
7
11
  * (Node), the local host (Node) and the UI (browser). Pure types — nothing here
@@ -129,7 +133,22 @@ interface DiscoveredSession {
129
133
  provider: CodingProvider;
130
134
  providerSessionId: string;
131
135
  cwd: string | null;
136
+ /**
137
+ * The chat's own title by the one rule (`./sessions`): the coding app's title when the owner of
138
+ * the apps' index supplied it (title hints), else the transcript's. Never the person's rename —
139
+ * that is applied once, by the screen, from the preferences.
140
+ */
132
141
  title: string;
142
+ /** Where `title` came from (`TITLE_PRECEDENCE`). */
143
+ titleSource?: TitleSource | "unknown" | undefined;
144
+ /** The app's title is still on its way (hints not loaded yet); `title` is the transcript's for now. */
145
+ titlePending?: boolean | undefined;
146
+ /** Archived in the app (hidden by default, like Code › Chats). */
147
+ archived?: boolean | undefined;
148
+ /** A scheduled or automated run (hidden by default). */
149
+ scheduled?: boolean | undefined;
150
+ /** The app lists it but its conversation file is gone: why. Shown, cannot be taken over. */
151
+ unavailable?: string | null | undefined;
133
152
  updatedAt: string;
134
153
  /** Where it was started, when the provider records it ("cli", "vscode", "exec", …). */
135
154
  source: string | null;
@@ -244,6 +263,11 @@ type TranscriptItem = {
244
263
  id: string;
245
264
  text: string;
246
265
  };
266
+ interface TitleStatus {
267
+ owner: boolean;
268
+ pending: boolean;
269
+ problem: string | null;
270
+ }
247
271
  /** Everything the engine pushes to a listener. */
248
272
  type CodingEvent = {
249
273
  type: "session";
@@ -254,6 +278,10 @@ type CodingEvent = {
254
278
  } | {
255
279
  type: "preferences";
256
280
  preferences: Record<string, ChatPreference>;
281
+ }
282
+ /** The apps' titles changed (or finished loading): re-read the discovered chats. */
283
+ | {
284
+ type: "titles";
257
285
  } | {
258
286
  type: "item";
259
287
  sessionId: string;
@@ -409,6 +437,10 @@ interface CodingSessionsApi {
409
437
  discoverSessions(options?: {
410
438
  limit?: number;
411
439
  }): Promise<DiscoveredSession[]>;
440
+ /** Where the apps' own titles stand: an owner supplies them (`owner`), still loading, or failing (`problem`, shown with Retry). */
441
+ titleStatus(): Promise<TitleStatus>;
442
+ /** Ask the owner of the apps' titles again (the Retry). */
443
+ retryTitles(): Promise<void>;
412
444
  recentFolders(): Promise<RecentFolder[]>;
413
445
  listSessions(): Promise<CodingSession[]>;
414
446
  listRuns(sessionId: string): Promise<RunRecord[]>;
@@ -533,6 +565,8 @@ interface CodingSessionsState {
533
565
  preferences: Record<string, ChatPreference>;
534
566
  /** How many of "Your other chats" to list (grows with "Show more"). */
535
567
  discoveredLimit: number;
568
+ /** Where the apps' own titles stand (null until first asked; an engine without the method says nothing). */
569
+ titles: TitleStatus | null;
536
570
  }
537
571
  /** A run that just ended — for the tab badge and the desktop notification. */
538
572
  interface RunFinished {
@@ -595,6 +629,9 @@ declare class CodingSessionsClient {
595
629
  private load;
596
630
  refreshProviders: () => Promise<void>;
597
631
  refreshDiscovered: () => Promise<void>;
632
+ refreshTitleStatus: () => Promise<void>;
633
+ /** The Retry beside "app titles unavailable". */
634
+ retryTitles: () => Promise<void>;
598
635
  /** List more of "Your other chats". */
599
636
  showMoreDiscovered: () => Promise<void>;
600
637
  refreshPreferences: () => Promise<void>;
@@ -2,6 +2,10 @@ import { ReactNode } from 'react';
2
2
  import { JsonValue } from '@ai-matrx/agents/portable';
3
3
  import { ChatThreadStore, ChatMessage } from '@ai-matrx/chat';
4
4
 
5
+ /** Where a title came from, strongest first. */
6
+ declare const TITLE_PRECEDENCE: readonly ["override", "app", "app-remembered", "custom-title", "ai-title", "agent-name", "summary", "first-prompt", "last-prompt"];
7
+ type TitleSource = (typeof TITLE_PRECEDENCE)[number];
8
+
5
9
  /**
6
10
  * The public contract of `@ai-matrx/coding-sessions`, shared by the engine
7
11
  * (Node), the local host (Node) and the UI (browser). Pure types — nothing here
@@ -129,7 +133,22 @@ interface DiscoveredSession {
129
133
  provider: CodingProvider;
130
134
  providerSessionId: string;
131
135
  cwd: string | null;
136
+ /**
137
+ * The chat's own title by the one rule (`./sessions`): the coding app's title when the owner of
138
+ * the apps' index supplied it (title hints), else the transcript's. Never the person's rename —
139
+ * that is applied once, by the screen, from the preferences.
140
+ */
132
141
  title: string;
142
+ /** Where `title` came from (`TITLE_PRECEDENCE`). */
143
+ titleSource?: TitleSource | "unknown" | undefined;
144
+ /** The app's title is still on its way (hints not loaded yet); `title` is the transcript's for now. */
145
+ titlePending?: boolean | undefined;
146
+ /** Archived in the app (hidden by default, like Code › Chats). */
147
+ archived?: boolean | undefined;
148
+ /** A scheduled or automated run (hidden by default). */
149
+ scheduled?: boolean | undefined;
150
+ /** The app lists it but its conversation file is gone: why. Shown, cannot be taken over. */
151
+ unavailable?: string | null | undefined;
133
152
  updatedAt: string;
134
153
  /** Where it was started, when the provider records it ("cli", "vscode", "exec", …). */
135
154
  source: string | null;
@@ -244,6 +263,11 @@ type TranscriptItem = {
244
263
  id: string;
245
264
  text: string;
246
265
  };
266
+ interface TitleStatus {
267
+ owner: boolean;
268
+ pending: boolean;
269
+ problem: string | null;
270
+ }
247
271
  /** Everything the engine pushes to a listener. */
248
272
  type CodingEvent = {
249
273
  type: "session";
@@ -254,6 +278,10 @@ type CodingEvent = {
254
278
  } | {
255
279
  type: "preferences";
256
280
  preferences: Record<string, ChatPreference>;
281
+ }
282
+ /** The apps' titles changed (or finished loading): re-read the discovered chats. */
283
+ | {
284
+ type: "titles";
257
285
  } | {
258
286
  type: "item";
259
287
  sessionId: string;
@@ -409,6 +437,10 @@ interface CodingSessionsApi {
409
437
  discoverSessions(options?: {
410
438
  limit?: number;
411
439
  }): Promise<DiscoveredSession[]>;
440
+ /** Where the apps' own titles stand: an owner supplies them (`owner`), still loading, or failing (`problem`, shown with Retry). */
441
+ titleStatus(): Promise<TitleStatus>;
442
+ /** Ask the owner of the apps' titles again (the Retry). */
443
+ retryTitles(): Promise<void>;
412
444
  recentFolders(): Promise<RecentFolder[]>;
413
445
  listSessions(): Promise<CodingSession[]>;
414
446
  listRuns(sessionId: string): Promise<RunRecord[]>;
@@ -533,6 +565,8 @@ interface CodingSessionsState {
533
565
  preferences: Record<string, ChatPreference>;
534
566
  /** How many of "Your other chats" to list (grows with "Show more"). */
535
567
  discoveredLimit: number;
568
+ /** Where the apps' own titles stand (null until first asked; an engine without the method says nothing). */
569
+ titles: TitleStatus | null;
536
570
  }
537
571
  /** A run that just ended — for the tab badge and the desktop notification. */
538
572
  interface RunFinished {
@@ -595,6 +629,9 @@ declare class CodingSessionsClient {
595
629
  private load;
596
630
  refreshProviders: () => Promise<void>;
597
631
  refreshDiscovered: () => Promise<void>;
632
+ refreshTitleStatus: () => Promise<void>;
633
+ /** The Retry beside "app titles unavailable". */
634
+ retryTitles: () => Promise<void>;
598
635
  /** List more of "Your other chats". */
599
636
  showMoreDiscovered: () => Promise<void>;
600
637
  refreshPreferences: () => Promise<void>;
@@ -305,6 +305,7 @@ function sessionEntry(session, prefs) {
305
305
  updatedAt: session.updatedAt,
306
306
  pinned: pref.pinned === true,
307
307
  hidden: pref.hidden === true,
308
+ hiddenBy: pref.hidden === true ? "you" : null,
308
309
  session,
309
310
  discovered: null
310
311
  };
@@ -322,7 +323,9 @@ function discoveredEntry(d, prefs) {
322
323
  cwd: d.cwd,
323
324
  updatedAt: d.updatedAt,
324
325
  pinned: pref.pinned === true,
325
- hidden: pref.hidden === true,
326
+ // Archived chats and scheduled runs are hidden by default, as in Code › Chats; a pin shows them.
327
+ hidden: pref.hidden === true || pref.pinned !== true && (d.archived === true || d.scheduled === true),
328
+ hiddenBy: pref.hidden === true ? "you" : pref.pinned === true ? null : d.archived ? "archived" : d.scheduled ? "scheduled" : null,
326
329
  session: null,
327
330
  discovered: d
328
331
  };
@@ -1268,6 +1271,14 @@ function ChooseAgentDialog(props) {
1268
1271
  ] }) });
1269
1272
  }
1270
1273
 
1274
+ // src/sessions/titles.ts
1275
+ var DISPLAY_MAX = 100;
1276
+ var LEADING_NOISE = /^(?:\s*(?:\d{1,3}[.)]|[-*•]|#{1,6}|>)\s+|\s*["“'‘`]+)+/;
1277
+ function displayTitle(title, max = DISPLAY_MAX) {
1278
+ const stripped = title.replace(LEADING_NOISE, "").replace(/["”'’`]+$/, "").trim() || title.trim();
1279
+ return stripped.length > max ? `${stripped.slice(0, max - 1).trimEnd()}\u2026` : stripped;
1280
+ }
1281
+
1271
1282
  // src/react/sidebar.tsx
1272
1283
  import { useCallback, useEffect as useEffect3, useMemo as useMemo3, useRef as useRef2, useState as useState5 } from "react";
1273
1284
  import {
@@ -1381,7 +1392,7 @@ function Row(props) {
1381
1392
  /* @__PURE__ */ jsx6("span", { className: "pointer-events-none mt-0.5 shrink-0", children: props.live ? /* @__PURE__ */ jsx6(SpinnerIcon, { size: 16, className: "text-primary" }) : /* @__PURE__ */ jsx6(ProviderMark, { provider: entry.provider, size: 16 }) }),
1382
1393
  /* @__PURE__ */ jsxs6("span", { className: "cs-row__body", children: [
1383
1394
  renaming ? /* @__PURE__ */ jsx6("span", { className: "pointer-events-auto relative z-10 block", children: /* @__PURE__ */ jsx6(RenameInput, { initial: entry.title, onDone: rename }) }) : /* @__PURE__ */ jsxs6("span", { className: "flex items-center gap-1", children: [
1384
- /* @__PURE__ */ jsx6("span", { className: "min-w-0 truncate text-[13px] font-medium leading-5", children: entry.title }),
1395
+ /* @__PURE__ */ jsx6("span", { className: "min-w-0 truncate text-[13px] font-medium leading-5", children: displayTitle(entry.title) }),
1385
1396
  entry.pinned ? /* @__PURE__ */ jsx6(PinIcon, { size: 11, className: "shrink-0 text-muted-foreground", "aria-label": "Pinned" }) : null
1386
1397
  ] }),
1387
1398
  /* @__PURE__ */ jsx6("span", { className: "block truncate text-[11.5px] leading-4 text-muted-foreground", children: props.meta }),
@@ -1483,6 +1494,8 @@ function Sidebar(props) {
1483
1494
  const [search, searchMore] = useContentSearch(query);
1484
1495
  const [groupMode, setGroupMode] = useState5(() => readLocal("coding-sessions.group", ["date", "folder"], "date"));
1485
1496
  const [showHidden, setShowHidden] = useState5(false);
1497
+ const titlesProblem = state.titles?.problem ?? null;
1498
+ const titlesUpdating = !titlesProblem && state.discovered.data.some((d) => d.titlePending === true);
1486
1499
  const [takingOver, setTakingOver] = useState5(null);
1487
1500
  const [takeoverError, setTakeoverError] = useState5(null);
1488
1501
  const prefs = state.preferences;
@@ -1536,6 +1549,7 @@ function Sidebar(props) {
1536
1549
  "Running in ",
1537
1550
  PROVIDER_LABEL[entry.provider]
1538
1551
  ] });
1552
+ else if (d?.unavailable) meta = `${PROVIDER_LABEL[entry.provider]} \xB7 file missing \xB7 ${relativeTime(entry.updatedAt, now)}`;
1539
1553
  else {
1540
1554
  const status = run ? run.status === "failed" ? "Failed \xB7 " : run.status === "stopped" ? "Stopped \xB7 " : "" : "";
1541
1555
  const where = entry.cwd ? prettyPath2(entry.cwd) : "unknown folder";
@@ -1625,7 +1639,7 @@ function Sidebar(props) {
1625
1639
  ] }) : null,
1626
1640
  /* @__PURE__ */ jsx6(SectionLabel, { children: "Recent" }),
1627
1641
  state.sessions.loading && state.sessions.loadedAt === null ? /* @__PURE__ */ jsx6(SidebarSkeleton, {}) : state.sessions.error ? /* @__PURE__ */ jsx6("p", { className: "px-4 py-1 text-[12px] text-destructive", children: state.sessions.error }) : recent.length === 0 ? running.length === 0 ? /* @__PURE__ */ jsx6("p", { className: "px-4 py-1 text-[12px] leading-5 text-muted-foreground", children: "Sessions you start or take over here will appear here." }) : null : recent.map((e) => renderEntry(e)),
1628
- /* @__PURE__ */ jsx6(
1642
+ /* @__PURE__ */ jsxs6(
1629
1643
  SectionLabel,
1630
1644
  {
1631
1645
  action: /* @__PURE__ */ jsxs6("span", { className: "flex items-center gap-0.5", children: [
@@ -1653,7 +1667,15 @@ function Sidebar(props) {
1653
1667
  }
1654
1668
  )
1655
1669
  ] }),
1656
- children: "Your other chats"
1670
+ children: [
1671
+ "Your other chats",
1672
+ titlesUpdating ? /* @__PURE__ */ jsx6("span", { className: "ml-1 font-normal normal-case text-muted-foreground", "data-cs-titles": "updating", children: "\xB7 titles updating" }) : null,
1673
+ titlesProblem ? /* @__PURE__ */ jsxs6("span", { className: "ml-1 font-normal normal-case text-destructive", "data-cs-titles": "unavailable", title: titlesProblem, children: [
1674
+ "\xB7 app titles unavailable \u2014",
1675
+ " ",
1676
+ /* @__PURE__ */ jsx6("button", { type: "button", className: "cs-link", onClick: () => void client.retryTitles(), children: "Retry" })
1677
+ ] }) : null
1678
+ ]
1657
1679
  }
1658
1680
  ),
1659
1681
  state.discovered.loading && state.discovered.loadedAt === null ? /* @__PURE__ */ jsx6(SidebarSkeleton, {}) : state.discovered.error ? /* @__PURE__ */ jsx6("p", { className: "px-4 py-1 text-[12px] text-destructive", children: state.discovered.error }) : otherList.length === 0 ? /* @__PURE__ */ jsx6("p", { className: "px-4 py-1 text-[12px] leading-5 text-muted-foreground", children: others.length > 0 ? "Every chat found here is pinned or hidden." : "No other Claude Code or Codex chats were found on this computer." }) : /* @__PURE__ */ jsxs6(Fragment4, { children: [
@@ -2122,7 +2144,8 @@ var CodingSessionsClient = class {
2122
2144
  folders: loadable([]),
2123
2145
  runs: {},
2124
2146
  preferences: {},
2125
- discoveredLimit: DISCOVERED_PAGE
2147
+ discoveredLimit: DISCOVERED_PAGE,
2148
+ titles: null
2126
2149
  };
2127
2150
  transport.onStatus((connection) => {
2128
2151
  const wasOpen = this.state.connection.state === "open";
@@ -2171,6 +2194,10 @@ var CodingSessionsClient = class {
2171
2194
  } else if (event.type === "preferences") {
2172
2195
  this.set({ preferences: event.preferences });
2173
2196
  return;
2197
+ } else if (event.type === "titles") {
2198
+ void this.refreshDiscovered();
2199
+ void this.refreshSessions();
2200
+ return;
2174
2201
  }
2175
2202
  const sessionId = event.type === "session" ? event.session.id : event.type === "run" ? event.run.sessionId : event.sessionId;
2176
2203
  this.transcripts.get(sessionId)?.onEvent(event);
@@ -2187,7 +2214,21 @@ var CodingSessionsClient = class {
2187
2214
  }
2188
2215
  }
2189
2216
  refreshProviders = () => this.load("providers", () => this.transport.call("providers"));
2190
- refreshDiscovered = () => this.load("discovered", () => this.transport.call("discoverSessions", { limit: this.state.discoveredLimit }));
2217
+ refreshDiscovered = async () => {
2218
+ void this.refreshTitleStatus();
2219
+ await this.load("discovered", () => this.transport.call("discoverSessions", { limit: this.state.discoveredLimit }));
2220
+ };
2221
+ refreshTitleStatus = async () => {
2222
+ try {
2223
+ this.set({ titles: await this.transport.call("titleStatus") });
2224
+ } catch {
2225
+ }
2226
+ };
2227
+ /** The Retry beside "app titles unavailable". */
2228
+ retryTitles = async () => {
2229
+ await this.transport.call("retryTitles").catch(() => void 0);
2230
+ await this.refreshTitleStatus();
2231
+ };
2191
2232
  /** List more of "Your other chats". */
2192
2233
  showMoreDiscovered = async () => {
2193
2234
  this.set({ discoveredLimit: this.state.discoveredLimit + DISCOVERED_PAGE });