@gkorepanov/ccodex 0.5.0-rc.2 → 0.5.0-rc.21
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/README.md +156 -157
- package/claude/chat-formatting.md +25 -0
- package/config/compatibility.json +1 -1
- package/dist/claude/commandActions.d.ts +2 -0
- package/dist/claude/commandActions.js +43 -24
- package/dist/claude/commandActions.js.map +1 -1
- package/dist/claude/delegation.d.ts +10 -0
- package/dist/claude/delegation.js +41 -0
- package/dist/claude/delegation.js.map +1 -0
- package/dist/claude/native/catalog.d.ts +14 -0
- package/dist/claude/native/catalog.js +52 -17
- package/dist/claude/native/catalog.js.map +1 -1
- package/dist/claude/native/history.d.ts +8 -1
- package/dist/claude/native/history.js +43 -19
- package/dist/claude/native/history.js.map +1 -1
- package/dist/claude/native/pages.d.ts +54 -0
- package/dist/claude/native/pages.js +271 -0
- package/dist/claude/native/pages.js.map +1 -0
- package/dist/claude/native/projector.d.ts +18 -2
- package/dist/claude/native/projector.js +104 -46
- package/dist/claude/native/projector.js.map +1 -1
- package/dist/claude/native/records.d.ts +2 -0
- package/dist/claude/native/records.js +35 -13
- package/dist/claude/native/records.js.map +1 -1
- package/dist/claude/native/search.d.ts +7 -0
- package/dist/claude/native/search.js +79 -0
- package/dist/claude/native/search.js.map +1 -0
- package/dist/claude/native/summary.d.ts +6 -1
- package/dist/claude/native/summary.js +24 -6
- package/dist/claude/native/summary.js.map +1 -1
- package/dist/claude/peers.d.ts +4 -0
- package/dist/claude/peers.js +19 -5
- package/dist/claude/peers.js.map +1 -1
- package/dist/claude/processes.d.ts +2 -0
- package/dist/claude/processes.js +15 -0
- package/dist/claude/processes.js.map +1 -1
- package/dist/claude/sdk.d.ts +8 -2
- package/dist/claude/sdk.js +23 -6
- package/dist/claude/sdk.js.map +1 -1
- package/dist/claude/session.d.ts +34 -7
- package/dist/claude/session.js +184 -72
- package/dist/claude/session.js.map +1 -1
- package/dist/claude/threads.d.ts +61 -3
- package/dist/claude/threads.js +408 -100
- package/dist/claude/threads.js.map +1 -1
- package/dist/claude/toolMapper.d.ts +23 -3
- package/dist/claude/toolMapper.js +50 -1
- package/dist/claude/toolMapper.js.map +1 -1
- package/dist/cli/args.js +2 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +7 -3
- package/dist/config.js.map +1 -1
- package/dist/daemon/daemon.d.ts +3 -0
- package/dist/daemon/daemon.js +6 -3
- package/dist/daemon/daemon.js.map +1 -1
- package/dist/daemon/supervisor.d.ts +8 -0
- package/dist/daemon/supervisor.js +15 -7
- package/dist/daemon/supervisor.js.map +1 -1
- package/dist/desktop/stdioFrontend.js +1 -1
- package/dist/desktop/stdioFrontend.js.map +1 -1
- package/dist/gateway/catalog.js +35 -20
- package/dist/gateway/catalog.js.map +1 -1
- package/dist/gateway/connection.js +2 -2
- package/dist/gateway/connection.js.map +1 -1
- package/dist/gateway/lineage.d.ts +29 -1
- package/dist/gateway/lineage.js +260 -38
- package/dist/gateway/lineage.js.map +1 -1
- package/dist/gateway/remote.js +3 -8
- package/dist/gateway/remote.js.map +1 -1
- package/dist/gateway/server.d.ts +2 -1
- package/dist/gateway/server.js +26 -13
- package/dist/gateway/server.js.map +1 -1
- package/dist/gateway/status.d.ts +15 -5
- package/dist/gateway/status.js +142 -47
- package/dist/gateway/status.js.map +1 -1
- package/dist/gateway/stock.js +17 -2
- package/dist/gateway/stock.js.map +1 -1
- package/dist/management/commands.d.ts +2 -0
- package/dist/management/commands.js +66 -3
- package/dist/management/commands.js.map +1 -1
- package/dist/meta.d.ts +39 -11
- package/dist/meta.js +59 -28
- package/dist/meta.js.map +1 -1
- package/dist/protocol/codex.d.ts +1 -3
- package/dist/protocol/codex.js +1 -4
- package/dist/protocol/codex.js.map +1 -1
- package/dist/protocol/turnPagination.d.ts +35 -2
- package/dist/protocol/turnPagination.js +53 -28
- package/dist/protocol/turnPagination.js.map +1 -1
- package/examples/config.toml +2 -2
- package/package.json +5 -4
- package/scripts/migrate-0.4-to-0.5.mjs +31 -20
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owns paged reads of a Claude transcript. History is projected from windows read back from the end of the file, as
|
|
3
|
+
* far as a page needs, never the whole file: Desktop pages a thread (`thread/turns/list`, `thread/items/list`) and
|
|
4
|
+
* a transcript grows to hundreds of megabytes.
|
|
5
|
+
*/
|
|
6
|
+
import { open, stat } from "node:fs/promises";
|
|
7
|
+
import { backgroundEnds, projectTranscript } from "./projector.js";
|
|
8
|
+
import { parseTranscriptLine } from "./records.js";
|
|
9
|
+
const CHUNK_BYTES = 1 << 20;
|
|
10
|
+
/** Turns the live window keeps beyond what was asked: the next page of a scroll back needs no read. */
|
|
11
|
+
const KEPT_TURNS = 12;
|
|
12
|
+
/** Windows kept while the file stays as it is: Desktop asks each turn of a page for its items right after the page. */
|
|
13
|
+
const RECENT_WINDOWS = 4;
|
|
14
|
+
function emptyWindow(end) {
|
|
15
|
+
return { start: end, end, records: [], positions: [], offsets: new Map() };
|
|
16
|
+
}
|
|
17
|
+
async function readRange(path, from, to) {
|
|
18
|
+
const handle = await open(path, "r");
|
|
19
|
+
try {
|
|
20
|
+
const bytes = Buffer.alloc(to - from);
|
|
21
|
+
const { bytesRead } = await handle.read(bytes, 0, bytes.length, from);
|
|
22
|
+
return bytes.subarray(0, bytesRead);
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
await handle.close();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Complete lines of `bytes` (read at `at`) from `first` up to the last newline: records with their line offsets. */
|
|
29
|
+
function parseLines(bytes, at, first) {
|
|
30
|
+
const records = [];
|
|
31
|
+
const positions = [];
|
|
32
|
+
let line = first;
|
|
33
|
+
for (let newline = bytes.indexOf(0x0a, line); newline !== -1; newline = bytes.indexOf(0x0a, line)) {
|
|
34
|
+
const record = parseTranscriptLine(bytes.subarray(line, newline));
|
|
35
|
+
if (record) {
|
|
36
|
+
records.push(record);
|
|
37
|
+
positions.push(at + line);
|
|
38
|
+
}
|
|
39
|
+
line = newline + 1;
|
|
40
|
+
}
|
|
41
|
+
return { records, positions, end: at + line };
|
|
42
|
+
}
|
|
43
|
+
function uuidOf(record) {
|
|
44
|
+
return "uuid" in record && typeof record.uuid === "string" ? record.uuid : undefined;
|
|
45
|
+
}
|
|
46
|
+
/** Extends the window back by up to `chunk` bytes; false when no whole line fits in them (a longer chunk will). */
|
|
47
|
+
async function readBack(path, window, chunk, ends) {
|
|
48
|
+
const from = Math.max(0, window.start - chunk);
|
|
49
|
+
const bytes = await readRange(path, from, window.start);
|
|
50
|
+
const first = from === 0 ? 0 : bytes.indexOf(0x0a) + 1;
|
|
51
|
+
if (first === 0 && from > 0)
|
|
52
|
+
return false;
|
|
53
|
+
const parsed = parseLines(bytes, from, first);
|
|
54
|
+
// A window opened at the end of the file ends at its last whole line (Claude may be writing the next one).
|
|
55
|
+
if (window.start === window.end)
|
|
56
|
+
window.end = parsed.end;
|
|
57
|
+
else if (parsed.end !== window.start)
|
|
58
|
+
throw new Error(`${path}: a line runs past a window's start`);
|
|
59
|
+
window.start = from + first;
|
|
60
|
+
backgroundEnds(parsed.records, ends);
|
|
61
|
+
window.records.unshift(...parsed.records);
|
|
62
|
+
window.positions.unshift(...parsed.positions);
|
|
63
|
+
parsed.records.forEach((record, index) => {
|
|
64
|
+
const uuid = uuidOf(record);
|
|
65
|
+
if (uuid && !window.offsets.has(uuid))
|
|
66
|
+
window.offsets.set(uuid, parsed.positions[index]);
|
|
67
|
+
});
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
/** Appends what Claude wrote since the window's end. */
|
|
71
|
+
async function readForward(path, window, size, ends) {
|
|
72
|
+
if (size <= window.end || !window.records.length)
|
|
73
|
+
return;
|
|
74
|
+
const parsed = parseLines(await readRange(path, window.end, size), window.end, 0);
|
|
75
|
+
backgroundEnds(parsed.records, ends);
|
|
76
|
+
window.end = parsed.end;
|
|
77
|
+
window.records.push(...parsed.records);
|
|
78
|
+
window.positions.push(...parsed.positions);
|
|
79
|
+
parsed.records.forEach((record, index) => {
|
|
80
|
+
const uuid = uuidOf(record);
|
|
81
|
+
if (uuid)
|
|
82
|
+
window.offsets.set(uuid, parsed.positions[index]);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/** Where the line after `uuid`'s starts. */
|
|
86
|
+
function after(window, uuid) {
|
|
87
|
+
const offset = window.offsets.get(uuid);
|
|
88
|
+
let low = 0;
|
|
89
|
+
let high = window.positions.length;
|
|
90
|
+
while (low < high) {
|
|
91
|
+
const middle = (low + high) >> 1;
|
|
92
|
+
if (window.positions[middle] <= offset)
|
|
93
|
+
low = middle + 1;
|
|
94
|
+
else
|
|
95
|
+
high = middle;
|
|
96
|
+
}
|
|
97
|
+
return window.positions[low] ?? window.end;
|
|
98
|
+
}
|
|
99
|
+
/** Drops the window's records before `offset`. */
|
|
100
|
+
function trim(window, offset) {
|
|
101
|
+
const index = window.positions.findIndex((position) => position >= offset);
|
|
102
|
+
if (index <= 0)
|
|
103
|
+
return;
|
|
104
|
+
window.records.splice(0, index);
|
|
105
|
+
window.positions.splice(0, index);
|
|
106
|
+
window.start = window.positions[0];
|
|
107
|
+
for (const [uuid, position] of window.offsets)
|
|
108
|
+
if (position < window.start)
|
|
109
|
+
window.offsets.delete(uuid);
|
|
110
|
+
}
|
|
111
|
+
export class TranscriptPages {
|
|
112
|
+
chunkBytes;
|
|
113
|
+
/** The newest part of the file; follows Claude's writes. */
|
|
114
|
+
live;
|
|
115
|
+
places = new Map();
|
|
116
|
+
/**
|
|
117
|
+
* How background commands ended, from every part of the file read. A turn is read after the turns that follow it
|
|
118
|
+
* (pages go back from the end), so its commands' ends are known by then.
|
|
119
|
+
*/
|
|
120
|
+
ends = new Map();
|
|
121
|
+
/** Windows projected lately, newest first, for the file as it was (`key`). */
|
|
122
|
+
recent = { key: "", windows: [] };
|
|
123
|
+
queue = Promise.resolve();
|
|
124
|
+
constructor(chunkBytes = CHUNK_BYTES) {
|
|
125
|
+
this.chunkBytes = chunkBytes;
|
|
126
|
+
}
|
|
127
|
+
/** The newest turns: at least `count` of them unless history is shorter. */
|
|
128
|
+
newest(source, count) {
|
|
129
|
+
return this.serial(() => this.liveWindow(source, (turns) => turns.length >= count, count));
|
|
130
|
+
}
|
|
131
|
+
/** The window of `turnId` (the last of its turns unless it is the newest) with at least `before` turns before it. */
|
|
132
|
+
around(source, turnId, before) {
|
|
133
|
+
return this.serial(async () => {
|
|
134
|
+
const reaches = (turns) => turns.findIndex((turn) => turn.id === turnId) >= before;
|
|
135
|
+
let place = this.places.get(turnId);
|
|
136
|
+
if (!place) {
|
|
137
|
+
// Not paged to yet (a cursor from before a restart): back from the end until it shows.
|
|
138
|
+
const found = await this.liveWindow(source, (turns) => turns.some((turn) => turn.id === turnId), 1);
|
|
139
|
+
if (!found.turns.some((turn) => turn.id === turnId))
|
|
140
|
+
throw new Error(`turn not found: ${turnId}`);
|
|
141
|
+
place = this.places.get(turnId);
|
|
142
|
+
}
|
|
143
|
+
if (place.end === undefined)
|
|
144
|
+
return this.liveWindow(source, reaches, 1);
|
|
145
|
+
const { physical, end, lastUuid } = place;
|
|
146
|
+
const { ino, size } = await stat(source.path);
|
|
147
|
+
// A turn alone (its items) reads the same in any window holding it whole; a page's turns before it do not: where
|
|
148
|
+
// history starts depends on where the window was read from (a compaction).
|
|
149
|
+
const known = before === 0 && this.recentWindows(source, ino, size).find((window) => reaches(window.turns));
|
|
150
|
+
if (known)
|
|
151
|
+
return known;
|
|
152
|
+
return this.remember(await this.fill(source, emptyWindow(end), lastUuid, true, reaches, physical));
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/** The newest turns back to the one holding the record at `offset` (bytes into the file). */
|
|
156
|
+
reaching(source, offset) {
|
|
157
|
+
return this.serial(() => this.liveWindow(source, (_turns, start) => start !== undefined && start <= offset, 1));
|
|
158
|
+
}
|
|
159
|
+
/** The newest turns back to `turnId`. */
|
|
160
|
+
since(source, turnId) {
|
|
161
|
+
return this.serial(() => this.liveWindow(source, (turns) => turns.some((turn) => turn.id === turnId), 1));
|
|
162
|
+
}
|
|
163
|
+
/** The last record of the turn before `turnId` (of `turnId` itself when `inclusive`); null before the first turn. */
|
|
164
|
+
async boundary(source, turnId, inclusive) {
|
|
165
|
+
if (!this.places.has(turnId))
|
|
166
|
+
await this.around(source, turnId, 0);
|
|
167
|
+
const place = this.places.get(turnId);
|
|
168
|
+
return inclusive ? place.boundary : place.previousBoundary;
|
|
169
|
+
}
|
|
170
|
+
/** The windows projected lately, while the file is as it was then. */
|
|
171
|
+
recentWindows(source, ino, size) {
|
|
172
|
+
const key = `${ino}:${size}:${source.leafUuid ?? ""}:${source.peersVersion}`;
|
|
173
|
+
if (this.recent.key !== key)
|
|
174
|
+
this.recent = { key, windows: [] };
|
|
175
|
+
return this.recent.windows;
|
|
176
|
+
}
|
|
177
|
+
remember(window) {
|
|
178
|
+
this.recent.windows = [window, ...this.recent.windows.filter((entry) => entry !== window)].slice(0, RECENT_WINDOWS);
|
|
179
|
+
return window;
|
|
180
|
+
}
|
|
181
|
+
serial(work) {
|
|
182
|
+
const run = this.queue.then(work, work);
|
|
183
|
+
this.queue = run.catch(() => undefined);
|
|
184
|
+
return run;
|
|
185
|
+
}
|
|
186
|
+
async liveWindow(source, enough, keep) {
|
|
187
|
+
const { ino, size } = await stat(source.path);
|
|
188
|
+
if (!this.live || this.live.ino !== ino || size < this.live.end) {
|
|
189
|
+
this.live = { ...emptyWindow(size), ino };
|
|
190
|
+
this.places.clear();
|
|
191
|
+
this.ends.clear();
|
|
192
|
+
}
|
|
193
|
+
const live = this.live;
|
|
194
|
+
this.recentWindows(source, ino, size);
|
|
195
|
+
await readForward(source.path, live, size, this.ends);
|
|
196
|
+
const key = `${live.start}:${live.end}:${source.leafUuid ?? ""}:${source.peersVersion}`;
|
|
197
|
+
if (live.cache?.key === key && (!live.cache.window.older || enough(live.cache.window.turns)))
|
|
198
|
+
return this.remember(live.cache.window);
|
|
199
|
+
const window = this.remember(await this.fill(source, live, source.leafUuid, false, enough));
|
|
200
|
+
// The window stays a few pages long: it starts again at the turn before the kept ones (their context).
|
|
201
|
+
const kept = Math.max(keep, KEPT_TURNS);
|
|
202
|
+
const cut = window.projection.turnBoundaries.at(-kept - 1)?.firstUuid;
|
|
203
|
+
if (window.turns.length > 2 * kept && cut && enough(window.turns.slice(-kept)))
|
|
204
|
+
trim(live, live.offsets.get(cut));
|
|
205
|
+
else
|
|
206
|
+
live.cache = { key, window };
|
|
207
|
+
return window;
|
|
208
|
+
}
|
|
209
|
+
/** Reads the window back until its turns are `enough` or history starts in it. */
|
|
210
|
+
async fill(source, window, leafUuid, continues, enough, physical = false) {
|
|
211
|
+
for (let chunk = this.chunkBytes;; chunk *= 2) {
|
|
212
|
+
if (window.records.length && (!leafUuid || window.offsets.has(leafUuid))) {
|
|
213
|
+
const projection = await projectTranscript({
|
|
214
|
+
sessionId: source.sessionId, path: source.path, records: window.records, header: source.header,
|
|
215
|
+
peers: source.peers, continues, physical, backgroundEnds: this.ends, ...(leafUuid ? { leafUuid } : {}),
|
|
216
|
+
});
|
|
217
|
+
// Cut off from its start, the first turn lacks what came before it (a steer's queue, the prompt of the answer it
|
|
218
|
+
// goes on after): only the turns after it are whole.
|
|
219
|
+
const older = window.start > 0 && (projection.truncated || projection.selectedLeafUuid === null);
|
|
220
|
+
// A turn Claude goes on from mid-message: the window of the one before it holds the message's later blocks, the
|
|
221
|
+
// start of this turn only (past the leaf).
|
|
222
|
+
const leafAt = projection.selectedLeafUuid ? window.offsets.get(projection.selectedLeafUuid) : window.end;
|
|
223
|
+
const partial = projection.turnBoundaries.findIndex((bound) => window.offsets.get(bound.firstUuid) > leafAt);
|
|
224
|
+
const whole = partial < 0 ? projection.turns.length : partial;
|
|
225
|
+
const turns = projection.turns.slice(older ? 1 : 0, whole);
|
|
226
|
+
const first = projection.turnBoundaries[older ? 1 : 0];
|
|
227
|
+
if (!older || enough(turns, first && window.offsets.get(first.firstUuid)) && !projection.partialCompaction) {
|
|
228
|
+
this.index(window, projection, older, continues, physical, whole);
|
|
229
|
+
return { turns, older, projection };
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
else if (window.start === 0) {
|
|
233
|
+
if (!leafUuid)
|
|
234
|
+
return { turns: [], older: false, projection: await projectTranscript({ sessionId: source.sessionId, path: source.path, records: [], header: source.header }) };
|
|
235
|
+
throw new Error(`${source.path}: record ${leafUuid} not found`);
|
|
236
|
+
}
|
|
237
|
+
await readBack(source.path, window, chunk, this.ends);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
index(window, projection, older, continues, physical, whole) {
|
|
241
|
+
const bounds = projection.turnBoundaries.slice(0, whole);
|
|
242
|
+
const order = new Map([...projection.selectedRecordUuids].map((uuid, index) => [uuid, index]));
|
|
243
|
+
const physicalFrom = projection.physicalFrom ? order.get(projection.physicalFrom) : -1;
|
|
244
|
+
// Where each record's API message ends: a turn Claude goes on from mid-message needs the message's later blocks.
|
|
245
|
+
const messageOf = new Map();
|
|
246
|
+
const messageEnds = new Map();
|
|
247
|
+
window.records.forEach((record, index) => {
|
|
248
|
+
if (record.type !== "assistant" || !record.message.id)
|
|
249
|
+
return;
|
|
250
|
+
messageOf.set(record.uuid, record.message.id);
|
|
251
|
+
messageEnds.set(record.message.id, window.positions[index + 1] ?? window.end);
|
|
252
|
+
});
|
|
253
|
+
bounds.forEach((bound, index) => {
|
|
254
|
+
if (older && index === 0)
|
|
255
|
+
return;
|
|
256
|
+
const next = bounds[index + 1];
|
|
257
|
+
this.places.set(bound.turnId, {
|
|
258
|
+
start: window.offsets.get(bound.firstUuid),
|
|
259
|
+
// Its records end where the next turn starts, unless Claude grouped a later one into it (one API message).
|
|
260
|
+
end: next
|
|
261
|
+
? Math.max(window.offsets.get(next.firstUuid), after(window, bound.lastUuid), messageEnds.get(messageOf.get(bound.lastUuid)) ?? 0)
|
|
262
|
+
: continues ? window.end : undefined,
|
|
263
|
+
lastUuid: bound.lastUuid,
|
|
264
|
+
boundary: bound.messageUuid,
|
|
265
|
+
previousBoundary: bounds[index - 1]?.messageUuid ?? null,
|
|
266
|
+
physical: physical || order.get(bound.lastUuid) < physicalFrom,
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
//# sourceMappingURL=pages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pages.js","sourceRoot":"","sources":["../../../src/claude/native/pages.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Thread, ThreadItem, TokenUsageBreakdown, Turn } from "../../protocol/codex.js";
|
|
2
2
|
import { type PeerDirectory } from "../peers.js";
|
|
3
|
-
import { type ActiveTool } from "../toolMapper.js";
|
|
3
|
+
import { type ActiveTool, type BackgroundEnd } from "../toolMapper.js";
|
|
4
4
|
import { type SelectedHistory } from "./history.js";
|
|
5
5
|
import { type ToolResultBlock, type TranscriptRecord, type UserRecord } from "./records.js";
|
|
6
6
|
import { type TranscriptHeader } from "./summary.js";
|
|
@@ -19,11 +19,22 @@ export interface ProjectTranscriptInput {
|
|
|
19
19
|
readonly nickname: string;
|
|
20
20
|
readonly depth: number;
|
|
21
21
|
};
|
|
22
|
+
/** The records end where a later turn starts (a page of history): their last turn is not the thread's last. */
|
|
23
|
+
readonly continues?: boolean;
|
|
24
|
+
/** The leaf lies past a later compaction: see `selectHistory`. */
|
|
25
|
+
readonly physical?: boolean;
|
|
26
|
+
/** How background commands ended further on in the transcript than `records` reach (a page of history). */
|
|
27
|
+
readonly backgroundEnds?: ReadonlyMap<string, BackgroundEnd>;
|
|
22
28
|
}
|
|
23
|
-
/**
|
|
29
|
+
/** How background commands ended, by task id: Claude's task notifications and its stops of them (TaskStop). */
|
|
30
|
+
export declare function backgroundEnds(records: readonly TranscriptRecord[], ends?: Map<string, BackgroundEnd>): Map<string, BackgroundEnd>;
|
|
31
|
+
/** Chain records bounding a projected turn: `messageUuid` is the native rollback anchor. */
|
|
24
32
|
export interface TurnProviderBoundary {
|
|
25
33
|
readonly turnId: string;
|
|
26
34
|
readonly messageUuid: string;
|
|
35
|
+
readonly firstUuid: string;
|
|
36
|
+
/** The turn's last record: a trailing compaction included, unless the `/compact` turn next takes it. */
|
|
37
|
+
readonly lastUuid: string;
|
|
27
38
|
}
|
|
28
39
|
export interface TranscriptProjection {
|
|
29
40
|
readonly thread: Thread;
|
|
@@ -38,6 +49,11 @@ export interface TranscriptProjection {
|
|
|
38
49
|
readonly turnBoundaries: readonly TurnProviderBoundary[];
|
|
39
50
|
readonly selectedLeafUuid: string | null;
|
|
40
51
|
readonly selectedRecordUuids: ReadonlySet<string>;
|
|
52
|
+
/** Projected from a window of the transcript whose history goes on before it. */
|
|
53
|
+
readonly truncated: boolean;
|
|
54
|
+
/** A compaction in it keeps messages from before the window. */
|
|
55
|
+
readonly partialCompaction: boolean;
|
|
56
|
+
readonly physicalFrom: string | null;
|
|
41
57
|
}
|
|
42
58
|
export interface ToolCompletion {
|
|
43
59
|
readonly record: Pick<UserRecord, "toolUseResult" | "toolDenialKind">;
|
|
@@ -1,13 +1,37 @@
|
|
|
1
1
|
/** Owns pure projection of selected Claude transcript history into Codex protocol objects. */
|
|
2
2
|
import { isAbsolute, resolve } from "node:path";
|
|
3
|
+
import { parseCommands } from "../commandActions.js";
|
|
3
4
|
import { CODEX_MCP_TOOLS, codexMcpItems } from "../codexRollout.js";
|
|
4
5
|
import { normalizeClaudeModelIdentifier } from "../modelSelection.js";
|
|
5
|
-
import { NO_PEERS, peerMessageItem, peerOrigin, sentMessageItem } from "../peers.js";
|
|
6
|
-
import { projectToolCompletion, startTool, } from "../toolMapper.js";
|
|
6
|
+
import { NO_PEERS, peerMessageItem, peerOrigin, sentMessageItem, subagentFiles } from "../peers.js";
|
|
7
|
+
import { endedBackground, killedCommand, projectToolCompletion, taskOutputFile, startTool, stoppedCommand, taskNotification, } from "../toolMapper.js";
|
|
7
8
|
import { selectHistory } from "./history.js";
|
|
8
9
|
import { ANSWER_CHARS, assistantBlockItemId, continuationTurnId } from "./ids.js";
|
|
9
10
|
import { isCompactBoundary, readTranscriptRecords, } from "./records.js";
|
|
10
11
|
import { slashCommand, startsTurn, summarizeTranscript, timestampSeconds, userText, } from "./summary.js";
|
|
12
|
+
/** How background commands ended, by task id: Claude's task notifications and its stops of them (TaskStop). */
|
|
13
|
+
export function backgroundEnds(records, ends = new Map()) {
|
|
14
|
+
for (const record of records) {
|
|
15
|
+
// A command Stop killed: Claude queues its notification but never sends it (a sent one, later, tells more).
|
|
16
|
+
const killed = record.type === "queue-operation" && record.operation === "enqueue"
|
|
17
|
+
? taskNotification(record.content ?? "", Date.parse(record.timestamp ?? "")) : undefined;
|
|
18
|
+
if (killed) {
|
|
19
|
+
ends.set(killed.taskId, killed.end);
|
|
20
|
+
if (killed.toolUseId)
|
|
21
|
+
ends.set(killed.toolUseId, { ...killed.end, taskId: killed.taskId });
|
|
22
|
+
}
|
|
23
|
+
if (record.type !== "user")
|
|
24
|
+
continue;
|
|
25
|
+
const atMs = Date.parse(record.timestamp);
|
|
26
|
+
const notified = record.origin?.kind === "task-notification" ? taskNotification(userText(record), atMs) : undefined;
|
|
27
|
+
if (notified)
|
|
28
|
+
ends.set(notified.taskId, notified.end);
|
|
29
|
+
const stopped = stoppedCommand(record.toolUseResult);
|
|
30
|
+
if (stopped)
|
|
31
|
+
ends.set(stopped, { status: "stopped", summary: "", atMs });
|
|
32
|
+
}
|
|
33
|
+
return ends;
|
|
34
|
+
}
|
|
11
35
|
/** Claude's token use over a history, and of its last request (Desktop's context meter). */
|
|
12
36
|
function projectedUsage(records) {
|
|
13
37
|
const total = {
|
|
@@ -86,6 +110,13 @@ function assistantBlocks(record) {
|
|
|
86
110
|
return [];
|
|
87
111
|
return record.message.content.filter((block) => block !== null && typeof block === "object");
|
|
88
112
|
}
|
|
113
|
+
/** Claude Code's filler reply to a turn cut off mid-way, written when the session resumes; its own UI hides it. */
|
|
114
|
+
function resumeFiller(record) {
|
|
115
|
+
if (record.type !== "assistant" || record.isApiErrorMessage === true || record.message.model !== "<synthetic>")
|
|
116
|
+
return false;
|
|
117
|
+
const blocks = assistantBlocks(record);
|
|
118
|
+
return blocks.length === 1 && blocks[0].text === "No response requested.";
|
|
119
|
+
}
|
|
89
120
|
function outputText(value) {
|
|
90
121
|
if (typeof value === "string")
|
|
91
122
|
return value;
|
|
@@ -167,14 +198,30 @@ function activeTool(index, block, cwd, threadId, timestamp) {
|
|
|
167
198
|
}
|
|
168
199
|
return startTool(index, block, cwd, threadId);
|
|
169
200
|
}
|
|
170
|
-
function projectTool(block, blockIndex, record, cwd, threadId, completions, peers) {
|
|
201
|
+
function projectTool(block, blockIndex, record, cwd, threadId, completions, peers, ends) {
|
|
171
202
|
const started = activeTool(blockIndex, block, cwd, threadId, record.timestamp);
|
|
172
203
|
const completion = completions.get(block.id);
|
|
173
204
|
const result = completion?.record.toolUseResult;
|
|
174
205
|
const item = sentMessageItem(started.item, started.state.input, result, peers);
|
|
175
206
|
if (!completion)
|
|
176
207
|
return item;
|
|
177
|
-
|
|
208
|
+
// An MCP tool's result is its content (text or blocks), not an object of fields.
|
|
209
|
+
const fields = typeof result === "object" && !Array.isArray(result) ? result : undefined;
|
|
210
|
+
const startedAtMs = Date.parse(record.timestamp);
|
|
211
|
+
const completed = completedToolItem({ state: { ...started.state, startedAtMs }, item }, { ...completion, record: { ...completion.record, toolUseResult: { ...fields, duration_ms: typeof fields?.duration_ms === "number" ? fields.duration_ms : 0 } } }, cwd);
|
|
212
|
+
// A background command runs on past its turn: it ends as its task notification (or a stop) tells. With neither, its
|
|
213
|
+
// Claude process ended first (a running one shows live).
|
|
214
|
+
const taskId = typeof fields?.backgroundTaskId === "string" ? fields.backgroundTaskId : undefined;
|
|
215
|
+
const output = (end, task) => end.outputFile ?? taskOutputFile(cwd, record.sessionId, task);
|
|
216
|
+
const killed = taskId ? undefined : ends.get(block.id);
|
|
217
|
+
if (killed?.taskId)
|
|
218
|
+
return killedCommand(completed, output(killed, killed.taskId));
|
|
219
|
+
if (!taskId || completed.type !== "commandExecution")
|
|
220
|
+
return completed;
|
|
221
|
+
const background = { ...completed, processId: taskId };
|
|
222
|
+
const end = ends.get(taskId);
|
|
223
|
+
return end ? endedBackground(background, { ...end, outputFile: output(end, taskId) }, startedAtMs)
|
|
224
|
+
: { ...background, status: "failed", exitCode: null, aggregatedOutput: null };
|
|
178
225
|
}
|
|
179
226
|
/** A started tool item completed by its tool_result; shared by history projection and the live stream. */
|
|
180
227
|
export function completedToolItem(started, completion, cwd) {
|
|
@@ -219,26 +266,27 @@ function responseHasTools(records) {
|
|
|
219
266
|
}
|
|
220
267
|
return result;
|
|
221
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* A block's item id: its API message and block index. A record without the index (older Claude Code; 0.4's restored
|
|
271
|
+
* turns, whose messages all share one id) is named by its own uuid, a synthetic reply by its message id as live names it.
|
|
272
|
+
*/
|
|
273
|
+
function blockItemId(record, position) {
|
|
274
|
+
if (record.apiBlockIndex !== undefined)
|
|
275
|
+
return assistantBlockItemId(record.message.id, record.apiBlockIndex + position);
|
|
276
|
+
return assistantBlockItemId(record.message.model === "<synthetic>" ? record.message.id : record.uuid, position);
|
|
277
|
+
}
|
|
222
278
|
function responseBlocks(records) {
|
|
223
279
|
const ordered = records
|
|
224
280
|
.map((record, order) => ({ record, order }))
|
|
225
281
|
.sort((left, right) => (left.record.apiBlockIndex ?? left.order) - (right.record.apiBlockIndex ?? right.order));
|
|
226
|
-
|
|
227
|
-
return ordered.flatMap(({ record }) => assistantBlocks(record).map((block, blockIndex) => {
|
|
228
|
-
const apiBlockIndex = record.apiBlockIndex === undefined
|
|
229
|
-
? fallbackIndex
|
|
230
|
-
: record.apiBlockIndex + blockIndex;
|
|
231
|
-
fallbackIndex = Math.max(fallbackIndex, apiBlockIndex + 1);
|
|
232
|
-
return { record, block, apiBlockIndex };
|
|
233
|
-
}));
|
|
282
|
+
return ordered.flatMap(({ record }) => assistantBlocks(record).map((block, position) => ({ record, block, index: (record.apiBlockIndex ?? 0) + position, id: blockItemId(record, position) })));
|
|
234
283
|
}
|
|
235
|
-
function assistantItems(records, cwd, threadId, completions, toolResponses, codexCalls, peers) {
|
|
284
|
+
function assistantItems(records, cwd, threadId, completions, toolResponses, codexCalls, peers, ends) {
|
|
236
285
|
let reasoning;
|
|
237
|
-
return responseBlocks(records).flatMap(({ record, block,
|
|
238
|
-
const messageId = record.message.id;
|
|
286
|
+
return responseBlocks(records).flatMap(({ record, block, index, id }) => {
|
|
239
287
|
if (block.type === "text" && typeof block.text === "string")
|
|
240
288
|
return [{
|
|
241
|
-
type: "agentMessage", id
|
|
289
|
+
type: "agentMessage", id, text: block.text,
|
|
242
290
|
phase: record.message.id && toolResponses.has(record.message.id) ? "commentary" : "final_answer",
|
|
243
291
|
memoryCitation: null, delivery: null, questions: null,
|
|
244
292
|
}];
|
|
@@ -248,17 +296,21 @@ function assistantItems(records, cwd, threadId, completions, toolResponses, code
|
|
|
248
296
|
return [];
|
|
249
297
|
}
|
|
250
298
|
const item = {
|
|
251
|
-
type: "reasoning", id
|
|
299
|
+
type: "reasoning", id, summary: [block.thinking], content: [],
|
|
252
300
|
};
|
|
253
301
|
reasoning = item;
|
|
254
302
|
return [item];
|
|
255
303
|
}
|
|
256
304
|
if (["tool_use", "server_tool_use", "mcp_tool_use"].includes(String(block.type))
|
|
257
305
|
&& typeof block.id === "string" && typeof block.name === "string") {
|
|
258
|
-
const
|
|
306
|
+
const completion = completions.get(block.id);
|
|
307
|
+
// Stopping a background command shows on the command (stock's Ctrl-C), not as a call of its own.
|
|
308
|
+
if (stoppedCommand(completion?.record.toolUseResult))
|
|
309
|
+
return [];
|
|
310
|
+
const item = projectTool(block, index, record, cwd, threadId, completions, peers, ends);
|
|
259
311
|
if (!CODEX_MCP_TOOLS.has(block.name))
|
|
260
312
|
return [item];
|
|
261
|
-
const result =
|
|
313
|
+
const result = completion?.block.content;
|
|
262
314
|
return [item, ...codexMcpItems(block.id, object(block.input) ?? {}, result === undefined ? undefined : outputText(result), codexCalls)];
|
|
263
315
|
}
|
|
264
316
|
return [];
|
|
@@ -267,7 +319,7 @@ function assistantItems(records, cwd, threadId, completions, toolResponses, code
|
|
|
267
319
|
function turnStatus(records, hasFollowingTurn) {
|
|
268
320
|
const failed = records.some((record) => record.type === "system" && record.subtype === "api_error"
|
|
269
321
|
|| record.type === "assistant" && (record.isApiErrorMessage === true || Boolean(record.error)));
|
|
270
|
-
const interrupted = records.some((record) => record.type === "user" && (record.interruptedByShutdown === true
|
|
322
|
+
const interrupted = records.some((record) => resumeFiller(record) || record.type === "user" && (record.interruptedByShutdown === true
|
|
271
323
|
|| record.toolUseResult?.interrupted === true || /^\[Request interrupted by user(?: for tool use)?\]$/u.test(userText(record))));
|
|
272
324
|
if (interrupted)
|
|
273
325
|
return "interrupted";
|
|
@@ -348,7 +400,7 @@ function turnStarts(records, subagentPromptUuid, steered) {
|
|
|
348
400
|
assistantBlocks(record).forEach((block, position) => {
|
|
349
401
|
if (answer)
|
|
350
402
|
goOn(index);
|
|
351
|
-
lastBlock =
|
|
403
|
+
lastBlock = blockItemId(record, position);
|
|
352
404
|
answer = block.type === "text" && typeof block.text === "string" && block.text.length >= ANSWER_CHARS;
|
|
353
405
|
});
|
|
354
406
|
}
|
|
@@ -373,22 +425,18 @@ function turnStarts(records, subagentPromptUuid, steered) {
|
|
|
373
425
|
});
|
|
374
426
|
return starts;
|
|
375
427
|
}
|
|
376
|
-
|
|
377
|
-
function subagentIds(completions) {
|
|
378
|
-
return new Set([...completions.values()].flatMap(({ record }) => string(record.toolUseResult?.agentId) ?? []));
|
|
379
|
-
}
|
|
380
|
-
function projectTurns(records, cwd, threadId, subagentPromptUuid, steered, directory) {
|
|
428
|
+
function projectTurns(records, cwd, threadId, subagentPromptUuid, steered, directory, continues, path, ends) {
|
|
381
429
|
const starts = turnStarts(records, subagentPromptUuid, steered);
|
|
382
430
|
const completions = toolCompletions(records);
|
|
383
|
-
|
|
431
|
+
// As live: a window of history lacks the calls that started earlier sub-agents.
|
|
432
|
+
const peers = { directory, children: subagentFiles(path) };
|
|
384
433
|
const toolResponses = responseHasTools(records);
|
|
385
434
|
const codexCalls = new Map();
|
|
386
435
|
const turns = starts.map(({ index: start, id, prompt }, turnIndex) => {
|
|
387
436
|
const end = starts[turnIndex + 1]?.index ?? records.length;
|
|
388
437
|
const turnRecords = records.slice(start, end);
|
|
389
438
|
const input = prompt ? userInputs(prompt) : [];
|
|
390
|
-
const hiddenCommand = input.length === 1 && input[0]?.type === "text"
|
|
391
|
-
&& /^\/(?:compact(?:\s|$)|goal clear$)/u.test(input[0].text);
|
|
439
|
+
const hiddenCommand = input.length === 1 && input[0]?.type === "text" && /^\/compact(?:\s|$)/u.test(input[0].text);
|
|
392
440
|
const peer = peerOrigin(prompt?.origin);
|
|
393
441
|
const items = peer ? [peerMessageItem(prompt.uuid, peer, userText(prompt), peers)]
|
|
394
442
|
: hiddenCommand || !prompt ? []
|
|
@@ -404,22 +452,26 @@ function projectTurns(records, cwd, threadId, subagentPromptUuid, steered, direc
|
|
|
404
452
|
}
|
|
405
453
|
const projectedResponses = new Set();
|
|
406
454
|
for (const record of prompt ? turnRecords.slice(1) : turnRecords) {
|
|
455
|
+
if (resumeFiller(record))
|
|
456
|
+
continue;
|
|
407
457
|
if (record.type === "assistant") {
|
|
408
458
|
const messageId = record.message.id;
|
|
409
459
|
if (projectedResponses.has(messageId))
|
|
410
460
|
continue;
|
|
411
461
|
projectedResponses.add(messageId);
|
|
412
|
-
items.push(...assistantItems(responses.get(messageId), cwd, threadId, completions, toolResponses, codexCalls, peers));
|
|
462
|
+
items.push(...assistantItems(responses.get(messageId), cwd, threadId, completions, toolResponses, codexCalls, peers, ends));
|
|
413
463
|
}
|
|
414
464
|
else if (record.type === "user" && peerOrigin(record.origin)) {
|
|
415
465
|
items.push(peerMessageItem(record.uuid, peerOrigin(record.origin), userText(record), peers));
|
|
416
466
|
}
|
|
467
|
+
// A steer's id is the client's own (Desktop shows a message of a turn after its first only with it).
|
|
417
468
|
else if (record.type === "user" && steered.has(record.uuid)) {
|
|
418
|
-
items.push({ type: "userMessage", id: record.uuid, clientId:
|
|
469
|
+
items.push({ type: "userMessage", id: record.uuid, clientId: record.uuid, content: userInputs(record) });
|
|
419
470
|
}
|
|
420
471
|
else if (record.type === "system" && record.subtype === "local_command" && typeof record.content === "string") {
|
|
421
472
|
const text = record.content.replace(/<\/?local-command-std(?:out|err)>/gu, "").trim();
|
|
422
|
-
|
|
473
|
+
// Claude's own word on `/goal` ("Goal set: …"): Codex clients show goals themselves.
|
|
474
|
+
if (text && record.commandRun?.command !== "goal")
|
|
423
475
|
items.push({ type: "agentMessage", id: record.uuid, text, phase: "commentary", memoryCitation: null });
|
|
424
476
|
}
|
|
425
477
|
else if (isCompactBoundary(record))
|
|
@@ -431,19 +483,17 @@ function projectTurns(records, cwd, threadId, subagentPromptUuid, steered, direc
|
|
|
431
483
|
if (attachment?.type === "queued_command" && peer) {
|
|
432
484
|
items.push(peerMessageItem(string(attachment.source_uuid) ?? record.uuid, peer, string(attachment.prompt) ?? "", peers));
|
|
433
485
|
}
|
|
434
|
-
else if (attachment?.type === "queued_command" && attachment.commandMode === "prompt" && typeof attachment.prompt === "string") {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
content: [{ type: "text", text: attachment.prompt, text_elements: [] }],
|
|
438
|
-
});
|
|
486
|
+
else if (attachment?.type === "queued_command" && attachment.commandMode === "prompt" && (typeof attachment.prompt === "string" || Array.isArray(attachment.prompt))) {
|
|
487
|
+
const id = string(attachment.source_uuid) ?? record.uuid;
|
|
488
|
+
items.push({ type: "userMessage", id, clientId: id, content: userInputs({ message: { content: attachment.prompt } }) });
|
|
439
489
|
}
|
|
440
490
|
}
|
|
441
491
|
}
|
|
442
|
-
const status = turnStatus(turnRecords, turnIndex + 1 < starts.length);
|
|
492
|
+
const status = turnStatus(turnRecords, continues || turnIndex + 1 < starts.length);
|
|
443
493
|
const startedAt = timestampSeconds(turnRecords[0].timestamp);
|
|
444
|
-
// A later local command (a model switch) trails the turn in the transcript without extending it.
|
|
445
|
-
const last = turnRecords.findLast((record) => record.type !== "user"
|
|
446
|
-
|| record.isMeta !== true && !/^<(?:command-name|local-command-)/u.test(userText(record)));
|
|
494
|
+
// A later local command (a model switch) or compaction trails the turn in the transcript without extending it.
|
|
495
|
+
const last = turnRecords.findLast((record) => record === turnRecords[0] || !isCompactBoundary(record) && (record.type !== "user"
|
|
496
|
+
|| record.isMeta !== true && record.isCompactSummary !== true && !/^<(?:command-name|local-command-)/u.test(userText(record))));
|
|
447
497
|
const completedAt = status === "inProgress" ? null : timestampSeconds(last?.timestamp);
|
|
448
498
|
return {
|
|
449
499
|
id,
|
|
@@ -467,14 +517,16 @@ function projectTurns(records, cwd, threadId, subagentPromptUuid, steered, direc
|
|
|
467
517
|
});
|
|
468
518
|
return turns;
|
|
469
519
|
}
|
|
470
|
-
function projectTurnBoundaries(records, subagentPromptUuid, steered) {
|
|
520
|
+
function projectTurnBoundaries(records, subagentPromptUuid, steered, turns) {
|
|
471
521
|
const starts = turnStarts(records, subagentPromptUuid, steered);
|
|
472
522
|
return starts.map(({ index: start, id }, turnIndex) => {
|
|
473
523
|
const range = records.slice(start + 1, starts[turnIndex + 1]?.index ?? records.length);
|
|
474
524
|
// A compaction that ends the turn (the next turn is the `/compact`) is not part of it: forks stay uncompacted.
|
|
475
525
|
const compaction = range.findLastIndex((record) => isCompactBoundary(record));
|
|
476
526
|
const kept = compaction >= 0 && !range.slice(compaction).some((record) => record.type === "assistant") ? range.slice(0, compaction) : range;
|
|
477
|
-
|
|
527
|
+
// A `/compact` turn next took the compaction (see `projectTurns`): this turn's records end before it.
|
|
528
|
+
const last = compaction >= 0 && turns[turnIndex + 1]?.items[0]?.id === range[compaction].uuid ? kept : range;
|
|
529
|
+
return { turnId: id, messageUuid: (kept.at(-1) ?? records[start]).uuid, firstUuid: records[start].uuid, lastUuid: (last.at(-1) ?? records[start]).uuid };
|
|
478
530
|
});
|
|
479
531
|
}
|
|
480
532
|
/** The Codex thread for a native session header (list rows, reads, live responses). */
|
|
@@ -534,11 +586,14 @@ export async function projectTranscript(input) {
|
|
|
534
586
|
skippedLines = reader.skippedLines;
|
|
535
587
|
rawRecords = loaded;
|
|
536
588
|
}
|
|
537
|
-
const history = input.history ?? selectHistory(rawRecords, input.leafUuid);
|
|
589
|
+
const history = input.history ?? selectHistory(rawRecords, input.leafUuid, input.physical);
|
|
538
590
|
const selected = history.records;
|
|
591
|
+
await parseCommands(selected.flatMap((record) => record.type === "assistant"
|
|
592
|
+
? assistantBlocks(record).flatMap((block) => block.type === "tool_use" && block.name === "Bash" ? string(object(block.input)?.command) ?? [] : [])
|
|
593
|
+
: []));
|
|
539
594
|
const header = input.header ?? summarizeTranscript(rawRecords, input.subagent?.promptRecordUuid);
|
|
540
595
|
const steered = steeredPrompts(rawRecords);
|
|
541
|
-
const turns = projectTurns(selected, header.cwd, input.sessionId, input.subagent?.promptRecordUuid, steered, input.peers ?? NO_PEERS.directory);
|
|
596
|
+
const turns = projectTurns(selected, header.cwd, input.sessionId, input.subagent?.promptRecordUuid, steered, input.peers ?? NO_PEERS.directory, input.continues ?? false, input.path, backgroundEnds(rawRecords, new Map(input.backgroundEnds)));
|
|
542
597
|
const nickname = input.subagent?.nickname ?? null;
|
|
543
598
|
const parentThreadId = input.parentThreadId ?? null;
|
|
544
599
|
const status = turns.at(-1)?.status === "inProgress"
|
|
@@ -557,9 +612,12 @@ export async function projectTranscript(input) {
|
|
|
557
612
|
tokenUsage: projectedUsage(selected),
|
|
558
613
|
skippedLines,
|
|
559
614
|
compactionBoundaries: history.compactionBoundaries,
|
|
560
|
-
turnBoundaries: projectTurnBoundaries(selected, input.subagent?.promptRecordUuid, steered),
|
|
615
|
+
turnBoundaries: projectTurnBoundaries(selected, input.subagent?.promptRecordUuid, steered, turns),
|
|
561
616
|
selectedLeafUuid: history.leafUuid,
|
|
562
617
|
selectedRecordUuids: new Set(selected.map((record) => record.uuid)),
|
|
618
|
+
truncated: history.truncated,
|
|
619
|
+
partialCompaction: history.partialCompaction,
|
|
620
|
+
physicalFrom: history.physicalFrom,
|
|
563
621
|
};
|
|
564
622
|
}
|
|
565
623
|
//# sourceMappingURL=projector.js.map
|