@agentclientprotocol/claude-agent-acp 0.88.1-preview.7 → 0.89.1-preview.1

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,7 +1,8 @@
1
1
  /**
2
2
  * The first prompt of a transcript, extracted the way the SDK does for a
3
3
  * session title (its `rf`, `ti` and `jP` in `@anthropic-ai/claude-agent-sdk`).
4
- * Used when the SDK `getSessionInfo` read another copy of the session.
4
+ * Part of the SDK's title rule that the session index applies itself (see
5
+ * `sdkTitles`).
5
6
  *
6
7
  * - Only main-chain user records count: no tool result, meta or compact
7
8
  * summary record.
@@ -1 +1 @@
1
- {"version":3,"file":"first-prompt.d.ts","sourceRoot":"","sources":["../../src/session-index/first-prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAQH,KAAK,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AA+CrC;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE;IAAE,eAAe,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,GAAG,SAAS,CAgC7F;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAelD;AAMD,2EAA2E;AAC3E,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoBhD;AAED,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAWhD"}
1
+ {"version":3,"file":"first-prompt.d.ts","sourceRoot":"","sources":["../../src/session-index/first-prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAQH,KAAK,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AA+CrC;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE;IAAE,eAAe,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,GAAG,SAAS,CAgC7F;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAelD;AAMD,2EAA2E;AAC3E,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoBhD;AAED,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAWhD"}
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * The first prompt of a transcript, extracted the way the SDK does for a
3
3
  * session title (its `rf`, `ti` and `jP` in `@anthropic-ai/claude-agent-sdk`).
4
- * Used when the SDK `getSessionInfo` read another copy of the session.
4
+ * Part of the SDK's title rule that the session index applies itself (see
5
+ * `sdkTitles`).
5
6
  *
6
7
  * - Only main-chain user records count: no tool result, meta or compact
7
8
  * summary record.
@@ -20,6 +20,9 @@
20
20
  * - A registry event names the record: only that record is read, and only
21
21
  * the sessions whose record changed get their `state` recomputed.
22
22
  * - A session this connection runs reports its SDK state at once.
23
+ * - A session whose transcripts this connection wrote (a rename, archive,
24
+ * unarchive or delete) is stat'ed and read again at once, without waiting
25
+ * for its transcript event.
23
26
  *
24
27
  * Events are coalesced: 150 ms after the last one, at most 1 s after the
25
28
  * first. Each subscription compares a recomputed row with the last row it
@@ -111,6 +114,14 @@ export declare class ListSubscriptions {
111
114
  unsubscribe(subscriptionId: string): void;
112
115
  /** A session this connection runs changed its state, turn end or cost. */
113
116
  ownSessionChanged(id: string): void;
117
+ /**
118
+ * This connection wrote the transcripts of a session: a rename, archive,
119
+ * unarchive or delete. Its transcripts are stat'ed and its row read again
120
+ * at once, as a transcript event would after its quiet period; the event
121
+ * that follows finds the row unchanged and sends nothing more. A copy the
122
+ * watch does not know yet is left to its event.
123
+ */
124
+ sessionWritten(id: string): void;
114
125
  dispose(): void;
115
126
  /** The open watchers and armed timers, for tests and diagnostics. */
116
127
  openHandles(): {
@@ -1 +1 @@
1
- {"version":3,"file":"list-subscriptions.d.ts","sourceRoot":"","sources":["../../src/session-index/list-subscriptions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"list-subscriptions.d.ts","sourceRoot":"","sources":["../../src/session-index/list-subscriptions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAMH,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC1E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAIL,KAAK,mBAAmB,EAEzB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAiB,KAAK,YAAY,EAA4B,MAAM,oBAAoB,CAAC;AAGhG,eAAO,MAAM,qBAAqB,4BAA4B,CAAC;AAC/D,eAAO,MAAM,uBAAuB,8BAA8B,CAAC;AACnE,eAAO,MAAM,mBAAmB,0BAA0B,CAAC;AAE3D,oCAAoC;AACpC,eAAO,MAAM,iBAAiB,MAAM,CAAC;AASrC,MAAM,MAAM,oBAAoB,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AACnD,MAAM,MAAM,qBAAqB,GAAG;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,CAAC;AAC/D,MAAM,MAAM,sBAAsB,GAAG;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,CAAC;AAChE,MAAM,MAAM,WAAW,GAAG;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,WAAW,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAQjG,iEAAiE;AACjE,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,oBAAoB,CAM9E;AAED,4BAA4B;AAC5B,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,sBAAsB,CAMlF;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,KAAK,EAAE,IAAI,CACT,YAAY,EACV,aAAa,GACb,aAAa,GACb,gBAAgB,GAChB,QAAQ,GACR,cAAc,GACd,aAAa,GACb,QAAQ,CACX,CAAC;IACF,QAAQ,EAAE,IAAI,CAAC,mBAAmB,EAAE,WAAW,CAAC,CAAC;IACjD,uDAAuD;IACvD,GAAG,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,eAAe,GAAG,SAAS,CAAC;IACxD,MAAM,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACpD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;AA6HF,qBAAa,iBAAiB;IA+BhB,OAAO,CAAC,QAAQ,CAAC,IAAI;IA9BjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAmC;IACjE;wEACoE;IACpE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IACzD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;IAC1D,2EAA2E;IAC3E,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAyC;IACxE;+CAC2C;IAC3C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmC;IAC7D,OAAO,CAAC,WAAW,CAAC,CAAiC;IACrD,8CAA8C;IAC9C,OAAO,CAAC,SAAS,CAAiC;IAClD,OAAO,CAAC,IAAI,CAA2B;IACvC,OAAO,CAAC,cAAc,CAAC,CAAgB;IACvC,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,gBAAgB,CAAC,CAAgC;IACzD,OAAO,CAAC,eAAe,CAAC,CAAgC;IACxD,OAAO,CAAC,eAAe,CAAC,CAAgB;IACxC,OAAO,CAAC,QAAQ,CAAS;IACzB,6EAA6E;IAC7E,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;gBAEV,IAAI,EAAE,oBAAoB;IAQvD;6DACyD;IACnD,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAuC5D,6CAA6C;IAC7C,WAAW,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI;IAWzC,0EAA0E;IAC1E,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAanC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAchC,OAAO,IAAI,IAAI;IAMf,qEAAqE;IACrE,WAAW,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;IAepE,OAAO,CAAC,WAAW;IA+DnB,OAAO,CAAC,UAAU;IAwBlB;kBACc;IACd,OAAO,CAAC,YAAY;IAkDpB;;iBAEa;IACb,OAAO,CAAC,cAAc;IA2BtB,OAAO,CAAC,UAAU;IAkBlB,OAAO,CAAC,SAAS;IAcjB,OAAO,CAAC,iBAAiB;IAazB,qEAAqE;IACrE,OAAO,CAAC,mBAAmB;IAgB3B,OAAO,CAAC,eAAe;IASvB;;;;;OAKG;IACH,OAAO,CAAC,QAAQ;IAoBhB,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,gBAAgB;YAoBV,YAAY;IAuB1B;oDACgD;YAClC,YAAY;IAqC1B,OAAO,CAAC,aAAa;IAsBrB,mEAAmE;YACrD,WAAW;IAiDzB,OAAO,CAAC,QAAQ;IAOhB,2EAA2E;IAC3E,OAAO,CAAC,OAAO;IAaf,OAAO,CAAC,QAAQ;IAOhB,OAAO,CAAC,OAAO;IASf;yEACqE;YACvD,IAAI;YA8BJ,IAAI;IAsOlB;;yBAEqB;IACrB,OAAO,CAAC,eAAe;IAOvB;;;;;OAKG;YACW,SAAS;IAsCvB,oDAAoD;IACpD,OAAO,CAAC,OAAO;IASf;;;;;;;OAOG;YACW,WAAW;IAgBzB;;yCAEqC;IACrC,OAAO,CAAC,OAAO;IAkDf,wEAAwE;IACxE,OAAO,CAAC,cAAc;CAMvB"}
@@ -20,6 +20,9 @@
20
20
  * - A registry event names the record: only that record is read, and only
21
21
  * the sessions whose record changed get their `state` recomputed.
22
22
  * - A session this connection runs reports its SDK state at once.
23
+ * - A session whose transcripts this connection wrote (a rename, archive,
24
+ * unarchive or delete) is stat'ed and read again at once, without waiting
25
+ * for its transcript event.
23
26
  *
24
27
  * Events are coalesced: 150 ms after the last one, at most 1 s after the
25
28
  * first. Each subscription compares a recomputed row with the last row it
@@ -219,6 +222,28 @@ export class ListSubscriptions {
219
222
  }
220
223
  }
221
224
  }
225
+ /**
226
+ * This connection wrote the transcripts of a session: a rename, archive,
227
+ * unarchive or delete. Its transcripts are stat'ed and its row read again
228
+ * at once, as a transcript event would after its quiet period; the event
229
+ * that follows finds the row unchanged and sends nothing more. A copy the
230
+ * watch does not know yet is left to its event.
231
+ */
232
+ sessionWritten(id) {
233
+ for (const watch of this.watches.values()) {
234
+ if (!watch.ready) {
235
+ // Read when the watch is ready, as the events of its start.
236
+ watch.dirtyRows.add(id);
237
+ continue;
238
+ }
239
+ const files = watch.filesById.get(id);
240
+ if (!files)
241
+ continue;
242
+ for (const filePath of files)
243
+ watch.dirtyFiles.add(filePath);
244
+ this.schedule(watch, "now");
245
+ }
246
+ }
222
247
  dispose() {
223
248
  this.disposed = true;
224
249
  for (const id of [...this.subscriptions.keys()])
@@ -9,9 +9,9 @@
9
9
  */
10
10
  import { RequestError, type ListSessionsRequest, type ListSessionsResponse } from "@agentclientprotocol/sdk";
11
11
  import type { OwnSessionState } from "./activity.js";
12
- import { type ListChanges, type ListSubscribeResponse } from "./list-subscriptions.js";
12
+ import { type ListChanges, type ListSubscribeResponse, type ListSubscriptionDeps } from "./list-subscriptions.js";
13
13
  import { LiveSessionRegistry } from "./live-registry.js";
14
- import { SessionIndex, type ArchivedFilter, type GetSessionInfo, type ListCursor } from "./session-index.js";
14
+ import { SessionIndex, type ArchivedFilter, type ListCursor } from "./session-index.js";
15
15
  export declare const SESSION_RENAME_METHOD = "_session/rename";
16
16
  export declare const SESSION_ARCHIVE_METHOD = "_session/archive";
17
17
  export declare const SESSION_UNARCHIVE_METHOD = "_session/unarchive";
@@ -85,7 +85,6 @@ export type RetitleOptions = {
85
85
  sidecar: "always" | "existing";
86
86
  };
87
87
  export type SessionIndexDeps = {
88
- getSessionInfo?: GetSessionInfo;
89
88
  deleteSession?: (sessionId: string) => Promise<void>;
90
89
  registry?: LiveSessionRegistry;
91
90
  now?: () => number;
@@ -93,6 +92,8 @@ export type SessionIndexDeps = {
93
92
  ownSessionState?: (sessionId: string) => OwnSessionState | undefined;
94
93
  /** Sends `_session/list/changes`. */
95
94
  notifyListChanges?: (changes: ListChanges) => Promise<void>;
95
+ /** The timing of the list subscriptions, for tests. */
96
+ listSubscriptionTiming?: Pick<ListSubscriptionDeps, "debounceMs" | "maxWaitMs" | "rescanMs" | "minSessionIntervalMs">;
96
97
  logError: (message: string, error: unknown) => void;
97
98
  };
98
99
  export declare class SessionIndexService {
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/session-index/service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/session-index/service.ts"],"names":[],"mappings":"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"}
@@ -11,7 +11,7 @@ import { randomBytes } from "node:crypto";
11
11
  import * as fs from "node:fs/promises";
12
12
  import * as path from "node:path";
13
13
  import { RequestError, } from "@agentclientprotocol/sdk";
14
- import { deleteSession as sdkDeleteSession, getSessionInfo as sdkGetSessionInfo, } from "@anthropic-ai/claude-agent-sdk";
14
+ import { deleteSession as sdkDeleteSession } from "@anthropic-ai/claude-agent-sdk";
15
15
  import { airExtensionMeta } from "../air-extension.js";
16
16
  import { sanitizeTitle } from "../session-titles.js";
17
17
  import { effectiveTitle, isArchivedTitle, storedTitle, titleRecords, visibleTitle, } from "./archive-title.js";
@@ -203,7 +203,7 @@ async function ensureAgentName(filePath, sessionId, title) {
203
203
  async function copyTitle(filePath, sessionId) {
204
204
  const { size } = await fs.stat(filePath);
205
205
  const headTail = await readHeadTail(filePath, size);
206
- const sidecar = tailCustomTitle(headTail.tail) === undefined
206
+ const sidecar = tailCustomTitle(headTail) === undefined
207
207
  ? await readSidecarTitle(filePath, sessionId)
208
208
  : undefined;
209
209
  const { title, archived } = effectiveTitle(transcriptAgentName(headTail), sdkTitles(headTail, sidecar));
@@ -303,7 +303,7 @@ export class SessionIndexService {
303
303
  deleteSession;
304
304
  constructor(deps) {
305
305
  this.deps = deps;
306
- this.index = new SessionIndex(deps.getSessionInfo ?? sdkGetSessionInfo);
306
+ this.index = new SessionIndex();
307
307
  this.registry = deps.registry ?? new LiveSessionRegistry();
308
308
  this.now = deps.now ?? Date.now;
309
309
  this.deleteSession = deps.deleteSession ?? ((id) => sdkDeleteSession(id));
@@ -434,6 +434,9 @@ export class SessionIndexService {
434
434
  stored = await this.titleCopies(sessionId, transcripts, change, options.sidecar);
435
435
  }
436
436
  return stored;
437
+ }).finally(() => {
438
+ // Whatever was written, also by a change that failed part way.
439
+ this.subscriptions?.sessionWritten(sessionId);
437
440
  });
438
441
  }
439
442
  /** Appends the title records `change` gives each copy, then its sidecar.
@@ -512,6 +515,7 @@ export class SessionIndexService {
512
515
  }
513
516
  finally {
514
517
  this.index.invalidate(all);
518
+ this.subscriptions?.sessionWritten(sessionId);
515
519
  }
516
520
  });
517
521
  }
@@ -527,6 +531,7 @@ export class SessionIndexService {
527
531
  own: (sessionId) => this.deps.ownSessionState?.(sessionId),
528
532
  notify: (changes) => this.deps.notifyListChanges?.(changes) ?? Promise.resolve(),
529
533
  logError: this.deps.logError,
534
+ ...this.deps.listSubscriptionTiming,
530
535
  });
531
536
  return this.subscriptions.subscribe(cwd);
532
537
  }
@@ -1,13 +1,14 @@
1
1
  /**
2
2
  * The session list of a `sessionIndex` client.
3
3
  *
4
- * Each list enumerates the transcripts of the requested cwd and of every
4
+ * A list enumerates the transcripts of the requested cwd and of every
5
5
  * worktree of its repository: one `readdir` per project directory and one
6
- * `stat` per transcript, no long-lived cache. The metadata of a transcript is
7
- * cached by `(path, mtime, size)` in an LRU and read on a miss with the SDK
8
- * `getSessionInfo` (titles, sidecar) plus one head and tail read for
9
- * what the SDK does not report (see {@link scanTranscript}). Misses are read in
10
- * parallel batches.
6
+ * `stat` per transcript. The pages after a cursor reuse that enumeration for
7
+ * a short while (see {@link SessionIndex.reusableListing}). The metadata of a transcript is
8
+ * cached by `(path, mtime, size)` in an LRU and read on a miss from one head
9
+ * and tail read (the titles by the SDK's rule, see {@link sdkTitles}; the
10
+ * facts, see {@link scanTranscriptFile}) and the title sidecar. Misses are
11
+ * read in parallel batches.
11
12
  *
12
13
  * Order is the last user activity descending, then session id: the time of
13
14
  * the last real user prompt (`lastPromptAt`), else `updatedAt`. `updatedAt`
@@ -17,7 +18,6 @@
17
18
  * candidate's order key is at most its mtime, so the scan stops as soon as
18
19
  * the page is full and the next mtime is older than the key of the last row.
19
20
  */
20
- import type { SDKSessionInfo } from "@anthropic-ai/claude-agent-sdk";
21
21
  import { type TranscriptFacts } from "./transcript-scan.js";
22
22
  export declare const DEFAULT_LIST_LIMIT = 50;
23
23
  export declare const MAX_LIST_LIMIT = 200;
@@ -66,13 +66,9 @@ export type ListQuery = {
66
66
  archived: ArchivedFilter;
67
67
  after?: ListCursor;
68
68
  };
69
- export type GetSessionInfo = (sessionId: string, options: {
70
- dir?: string;
71
- }) => Promise<SDKSessionInfo | undefined>;
72
69
  /** The title of the CLI's `custom-title.json` sidecar of a transcript. */
73
70
  export declare function readSidecarTitle(filePath: string, sessionId: string): Promise<string | undefined>;
74
71
  export declare class SessionIndex {
75
- private readonly getSessionInfo;
76
72
  private readonly listings;
77
73
  private readonly metadata;
78
74
  /** The metadata reads in flight, by path: a list and a subscription that
@@ -86,7 +82,12 @@ export declare class SessionIndex {
86
82
  /** Directories whose transcripts gave no cwd, by the directory mtime and
87
83
  * when that was learned. */
88
84
  private readonly dirsWithoutCwd;
89
- constructor(getSessionInfo: GetSessionInfo);
85
+ /** The enumeration of each list with a next page, by {@link pageKey}. */
86
+ private readonly nextPages;
87
+ /** Transcripts read so far (metadata cache misses). */
88
+ private reads;
89
+ /** How many transcripts the index has read, for tests and diagnostics. */
90
+ get transcriptsRead(): number;
90
91
  /** The paths whose sessions a list of `cwd` shows: `cwd`, and with
91
92
  * `includeWorktrees` the same subdirectory of every other existing
92
93
  * worktree of its repository (the worktree roots for a cwd at the root). */
@@ -107,6 +108,8 @@ export declare class SessionIndex {
107
108
  * every copy of a session: a list shows the newest one that has a row
108
109
  * (see {@link collectRows}), as the SDK does. */
109
110
  enumerateFiles(paths?: readonly string[]): Promise<TranscriptCandidate[]>;
111
+ /** {@link enumerateFiles}, with the directory listings it was made from. */
112
+ private enumerate;
110
113
  /**
111
114
  * Every transcript (with its size) and every `<sessionId>/` directory
112
115
  * (sidecar, subagent transcripts) of the session, in any project
@@ -140,6 +143,19 @@ export declare class SessionIndex {
140
143
  rows: IndexRow[];
141
144
  hasMore: boolean;
142
145
  }>;
146
+ /** Enumerates the scope of `query`, newest mtime first. */
147
+ private freshListing;
148
+ /**
149
+ * The enumeration of the earlier page of `query` that ended at `cursor`,
150
+ * while it is recent and no directory of it has changed its entries: a
151
+ * transcript was neither added nor removed (see {@link DirListings}, which
152
+ * returns the same names until then). Which transcripts the page reads is
153
+ * stat'ed again (see {@link collectRows}), so a page made from it is as
154
+ * fresh as one from a new enumeration: a transcript that grew since moves
155
+ * up, out of the pages after the cursor, or keeps its place. A page of
156
+ * thousands of transcripts then stats some hundreds, not all of them.
157
+ */
158
+ private reusableListing;
143
159
  /**
144
160
  * The rows of `candidates`, one per session (its newest transcript that has
145
161
  * a row), any archive state, as a list of `paths` shows them: a candidate
@@ -167,6 +183,12 @@ export declare class SessionIndex {
167
183
  * row, as the SDK list does. With a finite `wanted`, stops once `wanted`
168
184
  * rows are certain to come first (a candidate's order key is at most its
169
185
  * mtime, so `candidates` must be in mtime order then).
186
+ *
187
+ * With `restat`, `candidates` are an earlier enumeration: each is stat'ed
188
+ * again just before it is read, together with the other copies of its
189
+ * session, any of which may be its newest one now. Their mtimes still
190
+ * bound the order keys: a transcript that changed since either kept its
191
+ * order key (no new prompt) or got one newer than any listed before.
170
192
  */
171
193
  private collectRows;
172
194
  /** Whether the cached metadata of `candidate`, still current, places it
@@ -175,7 +197,7 @@ export declare class SessionIndex {
175
197
  /** Reads the unread transcripts of `dirNames` until each directory has a
176
198
  * known cwd, at most {@link MAX_CWD_PROBES_PER_DIR} per directory. A
177
199
  * directory that gives none is not read again for a while, unless it
178
- * changes. */
200
+ * changes. `restat` as in {@link collectRows}. */
179
201
  private learnDirCwds;
180
202
  /** The requested path of the directory, else a sibling's cwd. */
181
203
  private fallbackCwd;
@@ -183,11 +205,13 @@ export declare class SessionIndex {
183
205
  private readAndCache;
184
206
  private readMetadata;
185
207
  /**
186
- * The titles the SDK reports for the listed transcript (`customTitle` and
187
- * `summary` of `getSessionInfo`). The SDK reads the first copy of the
188
- * session that its search finds; its answer is used only when that copy is
189
- * the listed file (same size and mtime), else the same titles are taken
190
- * from the listed file itself (see {@link sdkTitles}).
208
+ * The titles the SDK gives the listed transcript (`customTitle` and
209
+ * `summary` of its `getSessionInfo`), by the SDK's rule from the head and
210
+ * tail read here and the sidecar (see {@link sdkTitles}). Not through the
211
+ * SDK itself: its lookup of the transcript reads the whole project
212
+ * directory on every call, which made each page of a large project cost a
213
+ * listing of every transcript per row, and it may read another copy of the
214
+ * session than the listed one.
191
215
  */
192
216
  private titlesOf;
193
217
  /** Whether a successor of a continued transcript holds history: the SDK
@@ -1 +1 @@
1
- {"version":3,"file":"session-index.d.ts","sourceRoot":"","sources":["../../src/session-index/session-index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session-index.d.ts","sourceRoot":"","sources":["../../src/session-index/session-index.ts"],"names":[],"mappings":"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"}
@@ -1,13 +1,14 @@
1
1
  /**
2
2
  * The session list of a `sessionIndex` client.
3
3
  *
4
- * Each list enumerates the transcripts of the requested cwd and of every
4
+ * A list enumerates the transcripts of the requested cwd and of every
5
5
  * worktree of its repository: one `readdir` per project directory and one
6
- * `stat` per transcript, no long-lived cache. The metadata of a transcript is
7
- * cached by `(path, mtime, size)` in an LRU and read on a miss with the SDK
8
- * `getSessionInfo` (titles, sidecar) plus one head and tail read for
9
- * what the SDK does not report (see {@link scanTranscript}). Misses are read in
10
- * parallel batches.
6
+ * `stat` per transcript. The pages after a cursor reuse that enumeration for
7
+ * a short while (see {@link SessionIndex.reusableListing}). The metadata of a transcript is
8
+ * cached by `(path, mtime, size)` in an LRU and read on a miss from one head
9
+ * and tail read (the titles by the SDK's rule, see {@link sdkTitles}; the
10
+ * facts, see {@link scanTranscriptFile}) and the title sidecar. Misses are
11
+ * read in parallel batches.
11
12
  *
12
13
  * Order is the last user activity descending, then session id: the time of
13
14
  * the last real user prompt (`lastPromptAt`), else `updatedAt`. `updatedAt`
@@ -36,11 +37,52 @@ const READ_BATCH_SIZE = 16;
36
37
  const MAX_CWD_PROBES_PER_DIR = 64;
37
38
  /** How long a directory that gave no cwd is not probed again. */
38
39
  const NO_CWD_RETRY_MS = 60_000;
40
+ /** How long the pages after a list reuse its enumeration. */
41
+ const LISTING_REUSE_MS = 30_000;
42
+ /** Enumerations kept for the next page of their list. */
43
+ const MAX_REUSABLE_LISTINGS = 8;
39
44
  export const ARCHIVED_FILTERS = ["unarchived", "archived", "all"];
40
45
  /** Whether `filter` keeps a session of that archive state. */
41
46
  export function archivedFilterKeeps(filter, archived) {
42
47
  return filter === "all" || (filter === "archived") === archived;
43
48
  }
49
+ /** The key of the page of `query` that follows `cursor`. */
50
+ function pageKey(query, cursor) {
51
+ return JSON.stringify([
52
+ query.cwd ?? null,
53
+ query.includeWorktrees ?? false,
54
+ cursor.orderAtMs,
55
+ cursor.sessionId,
56
+ ]);
57
+ }
58
+ /** The candidates of each session. */
59
+ function copiesBySession(candidates) {
60
+ const copies = new Map();
61
+ for (const candidate of candidates) {
62
+ const known = copies.get(candidate.sessionId);
63
+ if (known)
64
+ known.push(candidate);
65
+ else
66
+ copies.set(candidate.sessionId, [candidate]);
67
+ }
68
+ return copies;
69
+ }
70
+ /** `candidates` as they are on disk now; one that is gone or empty is left
71
+ * out. */
72
+ async function restat(candidates) {
73
+ const stats = await statFiles(candidates.map(({ filePath }) => filePath));
74
+ return candidates.flatMap((candidate, i) => {
75
+ const now = stats[i];
76
+ if (!now?.isFile() || now.size === 0)
77
+ return [];
78
+ if (now.mtimeMs === candidate.mtimeMs &&
79
+ now.size === candidate.size &&
80
+ now.ino === candidate.ino) {
81
+ return [candidate];
82
+ }
83
+ return [{ ...candidate, mtimeMs: now.mtimeMs, size: now.size, ino: now.ino }];
84
+ });
85
+ }
44
86
  /** A small LRU: a `Map` keeps insertion order, and a hit is re-inserted. */
45
87
  class Lru {
46
88
  capacity;
@@ -205,7 +247,6 @@ function scopeOf(paths) {
205
247
  };
206
248
  }
207
249
  export class SessionIndex {
208
- getSessionInfo;
209
250
  listings = new DirListings();
210
251
  metadata = new Lru(METADATA_CACHE_SIZE);
211
252
  /** The metadata reads in flight, by path: a list and a subscription that
@@ -219,8 +260,13 @@ export class SessionIndex {
219
260
  /** Directories whose transcripts gave no cwd, by the directory mtime and
220
261
  * when that was learned. */
221
262
  dirsWithoutCwd = new Map();
222
- constructor(getSessionInfo) {
223
- this.getSessionInfo = getSessionInfo;
263
+ /** The enumeration of each list with a next page, by {@link pageKey}. */
264
+ nextPages = new Lru(MAX_REUSABLE_LISTINGS);
265
+ /** Transcripts read so far (metadata cache misses). */
266
+ reads = 0;
267
+ /** How many transcripts the index has read, for tests and diagnostics. */
268
+ get transcriptsRead() {
269
+ return this.reads;
224
270
  }
225
271
  /** The paths whose sessions a list of `cwd` shows: `cwd`, and with
226
272
  * `includeWorktrees` the same subdirectory of every other existing
@@ -293,12 +339,18 @@ export class SessionIndex {
293
339
  * every copy of a session: a list shows the newest one that has a row
294
340
  * (see {@link collectRows}), as the SDK does. */
295
341
  async enumerateFiles(paths) {
342
+ return (await this.enumerate(paths)).candidates;
343
+ }
344
+ /** {@link enumerateFiles}, with the directory listings it was made from. */
345
+ async enumerate(paths) {
296
346
  const root = projectsRoot();
347
+ const rootNames = await this.listings.names(root);
297
348
  const dirs = paths
298
349
  ? await this.projectDirs(paths)
299
- : (await this.listings.names(root)).map((dirName) => ({ dirName }));
350
+ : rootNames.map((dirName) => ({ dirName }));
300
351
  // All directories' names first, then one bounded run of stats.
301
- const listed = await this.listings.namesOfAll(dirs.map(({ dirName }) => path.join(root, dirName)));
352
+ const dirPaths = dirs.map(({ dirName }) => path.join(root, dirName));
353
+ const listed = await this.listings.namesOfAll(dirPaths);
302
354
  const files = dirs.flatMap(({ dirName, projectPath }, i) => listed[i].filter((name) => name.endsWith(".jsonl") && isSessionId(name.slice(0, -6))).map((name) => ({ name, filePath: path.join(root, dirName, name), dirName, projectPath })));
303
355
  const stats = await statFiles(files.map(({ filePath }) => filePath));
304
356
  const all = [];
@@ -316,7 +368,7 @@ export class SessionIndex {
316
368
  ino: fileStats.ino,
317
369
  });
318
370
  });
319
- return all;
371
+ return { dirs: [root, ...dirPaths], names: [rootNames, ...listed], candidates: all };
320
372
  }
321
373
  /**
322
374
  * Every transcript (with its size) and every `<sessionId>/` directory
@@ -378,11 +430,10 @@ export class SessionIndex {
378
430
  }
379
431
  /** One page of rows, plus whether more rows follow the page. */
380
432
  async list(query) {
381
- const paths = query.cwd
382
- ? await this.listedPaths(query.cwd, query.includeWorktrees ?? false)
383
- : undefined;
384
433
  const after = query.after;
385
- const enumerated = (await this.enumerateFiles(paths)).sort(byMtimeDescending);
434
+ const reused = after ? await this.reusableListing(query, after) : undefined;
435
+ const listing = reused ?? (await this.freshListing(query));
436
+ const { paths, candidates: enumerated } = listing;
386
437
  // A page after a cursor skips, without reading them, the sessions whose
387
438
  // newest transcript has a cached order key before the cursor. The
388
439
  // archive state comes from the title, so the archive filter applies once
@@ -404,9 +455,45 @@ export class SessionIndex {
404
455
  archived: query.archived,
405
456
  after,
406
457
  wanted: query.limit + 1,
407
- unread: (read) => enumerated.filter((candidate) => !read.has(candidate)),
458
+ unread: (read) => enumerated.filter((candidate) => !read.has(candidate.filePath)),
459
+ restat: reused !== undefined,
408
460
  });
409
- return { rows: rows.slice(0, query.limit), hasMore: rows.length > query.limit };
461
+ const page = rows.slice(0, query.limit);
462
+ const hasMore = rows.length > query.limit;
463
+ const last = page.at(-1);
464
+ if (hasMore && last)
465
+ this.nextPages.set(pageKey(query, last), listing);
466
+ return { rows: page, hasMore };
467
+ }
468
+ /** Enumerates the scope of `query`, newest mtime first. */
469
+ async freshListing(query) {
470
+ const at = Date.now();
471
+ const paths = query.cwd
472
+ ? await this.listedPaths(query.cwd, query.includeWorktrees ?? false)
473
+ : undefined;
474
+ const { dirs, names, candidates } = await this.enumerate(paths);
475
+ return { paths, dirs, names, candidates: candidates.sort(byMtimeDescending), at };
476
+ }
477
+ /**
478
+ * The enumeration of the earlier page of `query` that ended at `cursor`,
479
+ * while it is recent and no directory of it has changed its entries: a
480
+ * transcript was neither added nor removed (see {@link DirListings}, which
481
+ * returns the same names until then). Which transcripts the page reads is
482
+ * stat'ed again (see {@link collectRows}), so a page made from it is as
483
+ * fresh as one from a new enumeration: a transcript that grew since moves
484
+ * up, out of the pages after the cursor, or keeps its place. A page of
485
+ * thousands of transcripts then stats some hundreds, not all of them.
486
+ */
487
+ async reusableListing(query, cursor) {
488
+ const key = pageKey(query, cursor);
489
+ const listing = this.nextPages.peek(key);
490
+ if (!listing)
491
+ return undefined;
492
+ this.nextPages.delete(key);
493
+ if (Date.now() - listing.at > LISTING_REUSE_MS)
494
+ return undefined;
495
+ const names = await this.listings.namesOfAll(listing.dirs);
496
+ return names.every((current, i) => current === listing.names[i]) ? listing : undefined;
410
497
  }
411
498
  /**
412
499
  * The rows of `candidates`, one per session (its newest transcript that has
@@ -422,7 +509,7 @@ export class SessionIndex {
422
509
  inScope: scopeOf(paths),
423
510
  archived: "all",
424
511
  wanted: Infinity,
425
- unread: (read) => siblings().filter((candidate) => !read.has(candidate)),
512
+ unread: (read) => siblings().filter((candidate) => !read.has(candidate.filePath)),
426
513
  });
427
514
  }
428
515
  /**
@@ -473,6 +560,12 @@ export class SessionIndex {
473
560
  * row, as the SDK list does. With a finite `wanted`, stops once `wanted`
474
561
  * rows are certain to come first (a candidate's order key is at most its
475
562
  * mtime, so `candidates` must be in mtime order then).
563
+ *
564
+ * With `restat`, `candidates` are an earlier enumeration: each is stat'ed
565
+ * again just before it is read, together with the other copies of its
566
+ * session, any of which may be its newest one now. Their mtimes still
567
+ * bound the order keys: a transcript that changed since either kept its
568
+ * order key (no new prompt) or got one newer than any listed before.
476
569
  */
477
570
  async collectRows(candidates, options) {
478
571
  const { inScope, archived, after, wanted } = options;
@@ -510,6 +603,8 @@ export class SessionIndex {
510
603
  pending = left;
511
604
  };
512
605
  const read = new Set();
606
+ /** With `restat`: the copies of each session. */
607
+ const copies = options.restat ? copiesBySession(candidates) : undefined;
513
608
  let index = 0;
514
609
  while (index < candidates.length) {
515
610
  if (bySession.size >= wanted) {
@@ -519,10 +614,13 @@ export class SessionIndex {
519
614
  break;
520
615
  }
521
616
  }
522
- const batch = candidates.slice(index, index + READ_BATCH_SIZE);
523
- index += batch.length;
617
+ const next = candidates.slice(index, index + READ_BATCH_SIZE);
618
+ index += next.length;
619
+ const batch = copies
620
+ ? await restat([...new Set(next.flatMap(({ sessionId }) => copies.get(sessionId)))].filter(({ filePath }) => !read.has(filePath)))
621
+ : next;
524
622
  const resolved = await Promise.all(batch.map(async (candidate) => {
525
- read.add(candidate);
623
+ read.add(candidate.filePath);
526
624
  const metadata = await this.metadataOf(candidate);
527
625
  if (!metadata || (await this.continuedElsewhere(candidate, metadata)))
528
626
  return undefined;
@@ -544,7 +642,7 @@ export class SessionIndex {
544
642
  // was read before the stop, and more rows only raise the bound. A
545
643
  // sibling that the archive filter or the cursor leaves out may still
546
644
  // supply the cwd.
547
- await this.learnDirCwds(new Set(pending.map(({ candidate }) => candidate.dirName)), options.unread(read));
645
+ await this.learnDirCwds(new Set(pending.map(({ candidate }) => candidate.dirName)), options.unread(read), options.restat ?? false);
548
646
  await settlePending();
549
647
  }
550
648
  return rows();
@@ -566,8 +664,8 @@ export class SessionIndex {
566
664
  /** Reads the unread transcripts of `dirNames` until each directory has a
567
665
  * known cwd, at most {@link MAX_CWD_PROBES_PER_DIR} per directory. A
568
666
  * directory that gives none is not read again for a while, unless it
569
- * changes. */
570
- async learnDirCwds(dirNames, unread) {
667
+ * changes. `restat` as in {@link collectRows}. */
668
+ async learnDirCwds(dirNames, unread, restatFirst) {
571
669
  const root = projectsRoot();
572
670
  const now = Date.now();
573
671
  const mtimes = new Map();
@@ -592,8 +690,9 @@ export class SessionIndex {
592
690
  });
593
691
  while (remaining.length > 0) {
594
692
  remaining = remaining.filter((candidate) => !this.dirCwds.has(candidate.dirName));
595
- const batch = remaining.slice(0, READ_BATCH_SIZE);
596
- remaining = remaining.slice(batch.length);
693
+ const next = remaining.slice(0, READ_BATCH_SIZE);
694
+ remaining = remaining.slice(next.length);
695
+ const batch = restatFirst ? await restat(next) : next;
597
696
  await Promise.all(batch.map((candidate) => this.metadataOf(candidate)));
598
697
  }
599
698
  for (const [dirName, mtimeMs] of mtimes) {
@@ -657,6 +756,7 @@ export class SessionIndex {
657
756
  return metadata;
658
757
  }
659
758
  async readMetadata(candidate, previous) {
759
+ this.reads++;
660
760
  const headTail = await readHeadTail(candidate.filePath, candidate.size);
661
761
  if (isSidechainTranscript(headTail.head))
662
762
  return null;
@@ -672,7 +772,7 @@ export class SessionIndex {
672
772
  ]);
673
773
  if (fileCwd)
674
774
  this.dirCwds.set(candidate.dirName, fileCwd);
675
- const sdk = await this.titlesOf(candidate, headTail, fileCwd);
775
+ const sdk = await this.titlesOf(candidate, headTail);
676
776
  // No title at all: the SDK does not list it either.
677
777
  if (!sdk.summary)
678
778
  return null;
@@ -696,21 +796,16 @@ export class SessionIndex {
696
796
  };
697
797
  }
698
798
  /**
699
- * The titles the SDK reports for the listed transcript (`customTitle` and
700
- * `summary` of `getSessionInfo`). The SDK reads the first copy of the
701
- * session that its search finds; its answer is used only when that copy is
702
- * the listed file (same size and mtime), else the same titles are taken
703
- * from the listed file itself (see {@link sdkTitles}).
799
+ * The titles the SDK gives the listed transcript (`customTitle` and
800
+ * `summary` of its `getSessionInfo`), by the SDK's rule from the head and
801
+ * tail read here and the sidecar (see {@link sdkTitles}). Not through the
802
+ * SDK itself: its lookup of the transcript reads the whole project
803
+ * directory on every call, which made each page of a large project cost a
804
+ * listing of every transcript per row, and it may read another copy of the
805
+ * session than the listed one.
704
806
  */
705
- async titlesOf(candidate, headTail, fileCwd) {
706
- const dir = [fileCwd, candidate.projectPath].find((cwd) => cwd !== undefined && isExactProjectDir(candidate.dirName, cwd));
707
- const info = await this.getSessionInfo(candidate.sessionId, dir ? { dir } : {}).catch(() => undefined);
708
- if (info &&
709
- info.fileSize === candidate.size &&
710
- info.lastModified === Math.trunc(candidate.mtimeMs)) {
711
- return { customTitle: info.customTitle, summary: info.summary };
712
- }
713
- const sidecar = tailCustomTitle(headTail.tail) === undefined
807
+ async titlesOf(candidate, headTail) {
808
+ const sidecar = tailCustomTitle(headTail) === undefined
714
809
  ? await readSidecarTitle(candidate.filePath, candidate.sessionId)
715
810
  : undefined;
716
811
  return sdkTitles(headTail, sidecar);
@@ -42,7 +42,12 @@ export type TranscriptFacts = {
42
42
  };
43
43
  export type HeadTail = {
44
44
  head: string;
45
+ /** The last 64 KB from their first whole line on; the whole file when it
46
+ * fits in the head. */
45
47
  tail: string;
48
+ /** The last 64 KB as the SDK searches them, with the cut line before
49
+ * `tail`; absent when that is `tail`. */
50
+ sdkTail?: string;
46
51
  };
47
52
  /** Facts that an earlier scan may have found before the tail window. */
48
53
  declare const INHERITED_FACTS: readonly ["model", "tailCwd", "lastTurnEndedAt"];
@@ -98,7 +103,7 @@ export declare function scanTranscript({ head, tail }: HeadTail, options?: {
98
103
  * SDK never lists. */
99
104
  export declare function isSidechainTranscript(head: string): boolean;
100
105
  /** The last `customTitle` of the tail, as the SDK reads it. */
101
- export declare function tailCustomTitle(tail: string): string | undefined;
106
+ export declare function tailCustomTitle({ tail, sdkTail }: HeadTail): string | undefined;
102
107
  /** The last agent name of a transcript, as AIR's native Claude provider
103
108
  * reads it: the top-level `agentName` of any record, from the tail, else
104
109
  * from the head. */
@@ -113,11 +118,11 @@ export type SdkTitles = {
113
118
  /**
114
119
  * The titles the SDK gives a transcript, from its own head and tail, in the
115
120
  * SDK's order: custom title (tail, the sidecar, head), AI title, then
116
- * {@link generatedTitle}. For a transcript that the SDK `getSessionInfo`
117
- * does not read (another copy of the session comes first in its search).
118
- * The sidecar title counts only when the tail has no custom title.
121
+ * {@link generatedTitle}: what the SDK `getSessionInfo` reports for this
122
+ * file, without its lookup of the file. The sidecar title counts only when
123
+ * the tail has no custom title.
119
124
  */
120
- export declare function sdkTitles({ head, tail }: HeadTail, sidecarTitle?: string): SdkTitles;
125
+ export declare function sdkTitles({ head, tail: wholeLines, sdkTail }: HeadTail, sidecarTitle?: string): SdkTitles;
121
126
  /** The title of a transcript without a custom title, in the SDK's order: AI
122
127
  * title, last prompt, summary, first prompt. */
123
128
  export declare function generatedTitle({ head, tail }: HeadTail): string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"transcript-scan.d.ts","sourceRoot":"","sources":["../../src/session-index/transcript-scan.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"transcript-scan.d.ts","sourceRoot":"","sources":["../../src/session-index/transcript-scan.ts"],"names":[],"mappings":"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"}
@@ -29,9 +29,9 @@ async function readTail(handle, size, window) {
29
29
  const { bytesRead } = await handle.read(buffer, 0, length, size - length);
30
30
  const raw = buffer.toString("utf8", 0, bytesRead);
31
31
  if (length === size)
32
- return raw;
32
+ return { raw, tail: raw };
33
33
  const newline = raw.indexOf("\n");
34
- return newline >= 0 ? raw.slice(newline + 1) : "";
34
+ return { raw, tail: newline >= 0 ? raw.slice(newline + 1) : "" };
35
35
  }
36
36
  /** The first and the last 64 KB of the first `size` bytes of `filePath`:
37
37
  * what was appended after the file was stat'ed is not read, so the result
@@ -53,7 +53,8 @@ export async function readHeadTailOf(handle, size) {
53
53
  const head = buffer.toString("utf8", 0, first.bytesRead);
54
54
  if (size <= CHUNK_SIZE)
55
55
  return { head, tail: head };
56
- return { head, tail: await readTail(handle, size, CHUNK_SIZE) };
56
+ const { raw, tail } = await readTail(handle, size, CHUNK_SIZE);
57
+ return raw === tail ? { head, tail } : { head, tail, sdkTail: raw };
57
58
  }
58
59
  /**
59
60
  * The facts of `filePath`, from its head and tail. When the transcript has
@@ -400,8 +401,8 @@ export function isSidechainTranscript(head) {
400
401
  return first.includes('"isSidechain":true') || first.includes('"isSidechain": true');
401
402
  }
402
403
  /** The last `customTitle` of the tail, as the SDK reads it. */
403
- export function tailCustomTitle(tail) {
404
- return lastField(tail, "customTitle");
404
+ export function tailCustomTitle({ tail, sdkTail }) {
405
+ return lastField(sdkTail ?? tail, "customTitle");
405
406
  }
406
407
  /** The last non-blank top-level `agentName` of the records of `text`, the
407
408
  * name `/rename`, a `rename_session` and AIR write with the custom title. */
@@ -428,11 +429,12 @@ export function transcriptAgentName({ head, tail }) {
428
429
  /**
429
430
  * The titles the SDK gives a transcript, from its own head and tail, in the
430
431
  * SDK's order: custom title (tail, the sidecar, head), AI title, then
431
- * {@link generatedTitle}. For a transcript that the SDK `getSessionInfo`
432
- * does not read (another copy of the session comes first in its search).
433
- * The sidecar title counts only when the tail has no custom title.
432
+ * {@link generatedTitle}: what the SDK `getSessionInfo` reports for this
433
+ * file, without its lookup of the file. The sidecar title counts only when
434
+ * the tail has no custom title.
434
435
  */
435
- export function sdkTitles({ head, tail }, sidecarTitle) {
436
+ export function sdkTitles({ head, tail: wholeLines, sdkTail }, sidecarTitle) {
437
+ const tail = sdkTail ?? wholeLines;
436
438
  const customTitle = lastField(tail, "customTitle") ??
437
439
  sidecarTitle ??
438
440
  lastField(head, "customTitle") ??
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.88.1-preview.7",
6
+ "version": "0.89.1-preview.1",
7
7
  "description": "An ACP-compatible coding agent powered by the Claude Agent SDK (TypeScript)",
8
8
  "main": "dist/lib.js",
9
9
  "types": "dist/lib.d.ts",