@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.
- package/dist/session-index/first-prompt.d.ts +2 -1
- package/dist/session-index/first-prompt.d.ts.map +1 -1
- package/dist/session-index/first-prompt.js +2 -1
- package/dist/session-index/list-subscriptions.d.ts +11 -0
- package/dist/session-index/list-subscriptions.d.ts.map +1 -1
- package/dist/session-index/list-subscriptions.js +25 -0
- package/dist/session-index/service.d.ts +4 -3
- package/dist/session-index/service.d.ts.map +1 -1
- package/dist/session-index/service.js +8 -3
- package/dist/session-index/session-index.d.ts +42 -18
- package/dist/session-index/session-index.d.ts.map +1 -1
- package/dist/session-index/session-index.js +137 -42
- package/dist/session-index/transcript-scan.d.ts +10 -5
- package/dist/session-index/transcript-scan.d.ts.map +1 -1
- package/dist/session-index/transcript-scan.js +11 -9
- package/package.json +1 -1
|
@@ -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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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":"AAAA
|
|
1
|
+
{"version":3,"file":"list-subscriptions.d.ts","sourceRoot":"","sources":["../../src/session-index/list-subscriptions.ts"],"names":[],"mappings":"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"}
|
|
@@ -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
|
|
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":"AAAA;;;;;;;;GAQG;AAKH,OAAO,EACL,YAAY,EACZ,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
*
|
|
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
|
|
7
|
-
*
|
|
8
|
-
* `
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
-
|
|
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
|
|
187
|
-
* `summary` of `getSessionInfo`)
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
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":"AAAA
|
|
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
|
-
*
|
|
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
|
|
7
|
-
*
|
|
8
|
-
* `
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
-
|
|
223
|
-
|
|
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
|
-
:
|
|
350
|
+
: rootNames.map((dirName) => ({ dirName }));
|
|
300
351
|
// All directories' names first, then one bounded run of stats.
|
|
301
|
-
const
|
|
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
|
|
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
|
-
|
|
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
|
|
523
|
-
index +=
|
|
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
|
|
596
|
-
remaining = remaining.slice(
|
|
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
|
|
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
|
|
700
|
-
* `summary` of `getSessionInfo`)
|
|
701
|
-
*
|
|
702
|
-
*
|
|
703
|
-
*
|
|
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
|
|
706
|
-
const
|
|
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:
|
|
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}
|
|
117
|
-
*
|
|
118
|
-
*
|
|
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":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAcvC,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,YAAY,CAAC;AAElD,MAAM,MAAM,eAAe,GAAG;IAC5B,wEAAwE;IACxE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;0BACsB;IACtB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,gDAAgD;IAChD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;8EAC0E;IAC1E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;qDACiD;IACjD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;kEAC8D;IAC9D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;6DACyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;0DACsD;IACtD,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;
|
|
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
|
-
|
|
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}
|
|
432
|
-
*
|
|
433
|
-
*
|
|
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