Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
975b9de
feat(runtime): give a turn's system notes a RuntimeEvent of their own
Astro-Han Sep 5, 2026
a1b5896
feat(runtime): convert every legacy transcript row instead of droppin…
Astro-Han Sep 5, 2026
64fa312
feat(runtime): make the transcript conversion whole and resumable
Astro-Han Sep 5, 2026
fa058d9
refactor(runtime): read a running turn from its own ledger
Astro-Han Sep 5, 2026
88228a9
refactor(runtime): move runtime notes onto the ledger and retire the …
Astro-Han Sep 5, 2026
08cbf54
refactor(runtime): make the RuntimeEvent ledger the only transcript a…
Astro-Han Sep 5, 2026
aadef6e
fix(runtime): derive an admitted prompt's id once, and never seal a r…
Astro-Han Sep 6, 2026
7b0451c
fix(runtime): owe a run's prompt from before its invocation opens
Astro-Han Sep 6, 2026
3541457
fix(runtime): resume transcript migration safely after interruption
M4n5ter Sep 6, 2026
a5544ad
fix(storage): seek transcript pages before projecting ledger messages
M4n5ter Sep 6, 2026
f92f76a
fix(runtime): resume a conversion a released build left part-written
Astro-Han Sep 6, 2026
8c29d2d
fix(runtime): convert a legacy transcript a page at a time
Astro-Han Sep 6, 2026
8041671
refactor(runtime): make the read model the only transcript definition
Astro-Han Sep 7, 2026
a32e71a
fix(runtime): keep the compaction notes on the ledger after the rebase
Astro-Han Sep 7, 2026
69e110e
fix(runtime): resume a released conversion by the rows it already con…
Astro-Han Sep 7, 2026
09f0bf2
test(runtime): guard the absence this cutover is named for
Astro-Han Sep 7, 2026
1491fee
test(desktop): read a Turn's retained-output flag from its recorded s…
Astro-Han Sep 7, 2026
fe4e366
fix(storage): reassemble chunked records in the paged transcript scan
Astro-Han Sep 7, 2026
d45be86
refactor(runtime-host): drop the durable-coverage claim from transcri…
Astro-Han Sep 7, 2026
ad351d2
fix(desktop): ask for one older row instead of assuming a transcript …
Astro-Han Sep 7, 2026
0d886e9
test(desktop): scroll until the transcript range moves
Astro-Han Sep 7, 2026
4cddf6f
fix(runtime-host): read the WorkHub Coordination transcript from its …
Astro-Han Sep 7, 2026
86f1d76
fix(runtime): bound a transcript Turn before reading it and keep the …
Astro-Han Sep 7, 2026
41ae92c
fix(storage): keep a replayed projection from moving the catalog prev…
Astro-Han Sep 7, 2026
1b79c6d
Merge branch 'main' into refactor/4791-single-transcript-authority
Astro-Han Sep 7, 2026
957c269
fix(runtime): mint a converted Session's ordinals in the order its tu…
Astro-Han Sep 7, 2026
16f70e8
perf(storage): page a transcript off the ordinal index
Astro-Han Sep 7, 2026
a1db59a
Merge origin/main into refactor/4791-single-transcript-authority
Astro-Han Sep 7, 2026
94c24b0
perf(storage): materialize the ordinal renumber instead of recomputin…
Astro-Han Sep 7, 2026
3e8aad4
perf(storage): page the transcript forward off the ordinal index
Astro-Han Sep 7, 2026
8e20e6d
refactor(runtime): drop the retained-output fact nothing reads
Astro-Han Sep 7, 2026
3a4371a
fix(core): keep retired keys readable where the shape is exact
Astro-Han Sep 7, 2026
e2f304f
fix(runtime-host): page overlapping Turns in sequence order
Astro-Han Sep 7, 2026
f09c677
Merge origin/main into refactor/4791-single-transcript-authority
Astro-Han Sep 7, 2026
4e670b7
fix(storage): keep an undecodable payload from wedging the ledger schema
Astro-Han Sep 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions apps/desktop/e2e/transcript-scroll-cost.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,10 +252,14 @@ test('paging back through the whole history keeps the mounted range bounded', as
const firstBefore = await turns.first().getAttribute('data-turn-id');
if (firstBefore === 'turn-prompt-rail-1') break;
// The product asks for history on an upward wheel near the start, so the
// gesture that pages is the gesture a reader makes.
await wheel(page, cdp, { ticks: 12, deltaY: -120 });
// gesture that pages is the gesture a reader makes. How many gestures it
// takes is how tall the resident range happens to be, which is not what
// this test is about — keep scrolling until the range moves.
await expect
.poll(async () => turns.first().getAttribute('data-turn-id'))
.poll(async () => {
await wheel(page, cdp, { ticks: 12, deltaY: -120 });
return turns.first().getAttribute('data-turn-id');
})
.not.toBe(firstBefore);
pages += 1;
mountedMax = Math.max(mountedMax, await turns.count());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@ test('projects typed linked Session ids without rewriting opaque tool data', ()
turnId: 'turn-1',
status: 'completed',
parentSessionId: 'child-session',
partialOutputRetained: false,
}).parentSessionId,
linkedSessionId,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,6 @@ test('bounds the default active transcript range by Turn identities', async () =
events: { async *[Symbol.asyncIterator]() {} },
transcriptBootstrap: {
throughSequence: messages.length - 1,
durableCoverage: 'complete',
overlayMessageCount: 0,
durable: bootstrapPage,
overlay: { ...transcriptPage('older', null, messages.length - 1), source: 'overlay' },
Expand Down Expand Up @@ -298,7 +297,6 @@ test('bounds the default active transcript range by presentation bytes', async (
events: { async *[Symbol.asyncIterator]() {} },
transcriptBootstrap: {
throughSequence: messages.length - 1,
durableCoverage: 'complete',
overlayMessageCount: 0,
durable: bootstrapPage,
overlay: { ...transcriptPage('older', null, messages.length - 1), source: 'overlay' },
Expand Down Expand Up @@ -337,7 +335,6 @@ test('keeps an oversized latest Turn visible after bootstrap eviction', async ()
events: { async *[Symbol.asyncIterator]() {} },
transcriptBootstrap: {
throughSequence: latest.identity,
durableCoverage: 'complete',
overlayMessageCount: 0,
durable: bootstrapPage,
overlay: { ...transcriptPage('older', null, latest.identity), source: 'overlay' },
Expand Down Expand Up @@ -378,7 +375,6 @@ test('keeps an oversized latest Turn visible before a trailing session note', as
events: { async *[Symbol.asyncIterator]() {} },
transcriptBootstrap: {
throughSequence: trailingNote.identity,
durableCoverage: 'complete',
overlayMessageCount: 0,
durable: bootstrapPage,
overlay: {
Expand Down Expand Up @@ -440,7 +436,6 @@ test('keeps an oversized latest Turn when returning from history to a trailing s
events: { async *[Symbol.asyncIterator]() {} },
transcriptBootstrap: {
throughSequence: trailingNote.identity,
durableCoverage: 'complete',
overlayMessageCount: 0,
durable: bootstrapPage,
overlay: {
Expand Down Expand Up @@ -500,7 +495,6 @@ test('keeps a bounded contiguous window while moving between history and the tai
events: { async *[Symbol.asyncIterator]() {} },
transcriptBootstrap: {
throughSequence: 4,
durableCoverage: 'complete',
overlayMessageCount: 0,
durable: bootstrapPage,
overlay: { ...page(null), source: 'overlay' },
Expand Down Expand Up @@ -550,7 +544,6 @@ test('retains the reading anchor while an older page replaces the far edges', as
events: { async *[Symbol.asyncIterator]() {} },
transcriptBootstrap: {
throughSequence: 7,
durableCoverage: 'complete',
overlayMessageCount: 0,
durable: bootstrapPage,
overlay: { ...transcriptPage('older', null, 7), source: 'overlay' },
Expand All @@ -576,11 +569,10 @@ test('retains the reading anchor while an older page replaces the far edges', as
assert.equal(snapshot.hasNewer, true);
});

for (const { coverage, textBytes } of (['complete', 'projected'] as const).flatMap((coverage) =>
[0, 300 * 1024, 600 * 1024].map((textBytes) => ({ coverage, textBytes })),
for (const { stride, textBytes } of [1, 3].flatMap((stride) =>
[0, 300 * 1024, 600 * 1024].map((textBytes) => ({ stride, textBytes })),
)) {
test(`scrolls both ways through bounded ${coverage} history with ${textBytes}-byte Turns`, async () => {
const stride = coverage === 'projected' ? 3 : 1;
test(`scrolls both ways through bounded stride-${stride} history with ${textBytes}-byte Turns`, async () => {
const messages = Array.from({ length: 40 }, (_, index) => ({
identity: index * stride,
message: { ...assistantMessage('x'.repeat(textBytes), `assistant-${index}`), turnId: `turn-${index}` },
Expand Down Expand Up @@ -610,7 +602,6 @@ for (const { coverage, textBytes } of (['complete', 'projected'] as const).flatM
async close() {},
transcriptBootstrap: {
throughSequence: through,
durableCoverage: coverage,
overlayMessageCount: 0,
durable: makePage('older', null),
overlay: { ...transcriptPage('older', null, through), source: 'overlay' },
Expand Down Expand Up @@ -708,7 +699,6 @@ test('delivers a mid-session tail append even while a history window is resident
events: { async *[Symbol.asyncIterator]() {} },
transcriptBootstrap: {
throughSequence: 4,
durableCoverage: 'complete',
overlayMessageCount: 0,
durable: bootstrapPage,
overlay: { ...page(null), source: 'overlay' },
Expand Down Expand Up @@ -759,7 +749,6 @@ test('advances a projected transcript across hidden durable records', async () =
events: { async *[Symbol.asyncIterator]() {} },
transcriptBootstrap: {
throughSequence: 1,
durableCoverage: 'projected',
overlayMessageCount: 0,
durable: bootstrapPage,
overlay: { ...transcriptPage('older', null, 1), source: 'overlay' },
Expand Down Expand Up @@ -816,7 +805,6 @@ test('keeps an oversized streaming Turn visible when its overlay settles', async
events: { async *[Symbol.asyncIterator]() {} },
transcriptBootstrap: {
throughSequence: older.identity,
durableCoverage: 'complete',
overlayMessageCount: 1,
durable: bootstrapPage,
overlay: { ...transcriptPage('older', null, older.identity), source: 'overlay' },
Expand Down Expand Up @@ -868,7 +856,6 @@ test('keeps an oversized settled Turn visible before a trailing session note', a
events: { async *[Symbol.asyncIterator]() {} },
transcriptBootstrap: {
throughSequence: older.identity,
durableCoverage: 'complete',
overlayMessageCount: 1,
durable: bootstrapPage,
overlay: { ...bootstrapPage, source: 'overlay' },
Expand Down Expand Up @@ -933,7 +920,6 @@ test('does not resurrect a discarded replica when a tail re-anchor is in flight'
events: { async *[Symbol.asyncIterator]() {} },
transcriptBootstrap: {
throughSequence: 4,
durableCoverage: 'complete',
overlayMessageCount: 0,
durable: bootstrapPage,
overlay: { ...page(null), source: 'overlay' },
Expand Down Expand Up @@ -1021,7 +1007,6 @@ for (const direction of ['older', 'newer'] as const) {
events: { async *[Symbol.asyncIterator]() {} },
transcriptBootstrap: {
throughSequence: 4,
durableCoverage: 'complete',
overlayMessageCount: 0,
durable: bootstrapPage,
overlay: { ...page(null), source: 'overlay' },
Expand Down Expand Up @@ -1099,7 +1084,6 @@ test('does not drive a discarded replica terminal when a contiguous catch-up is
events: { async *[Symbol.asyncIterator]() {} },
transcriptBootstrap: {
throughSequence: 4,
durableCoverage: 'complete',
overlayMessageCount: 0,
durable: bootstrapPage,
overlay: { ...page(null, 4), source: 'overlay' },
Expand Down Expand Up @@ -1147,14 +1131,13 @@ test('loads a history target with newer messages available below it', async () =
}));
const bootstrapPage = transcriptPage('older', null, 4);
const aroundPage = transcriptPage('newer', 'newer', 4);
let aroundInput: { direction: string; anchorSequence: number | null } | undefined;
const inputs: Array<{ direction: string; anchorSequence: number | null }> = [];
const handle = runtimeHostSessionFixture({
snapshot: continuitySnapshot(),
transcript: Promise.resolve([]),
events: { async *[Symbol.asyncIterator]() {} },
transcriptBootstrap: {
throughSequence: 4,
durableCoverage: 'complete',
overlayMessageCount: 0,
durable: bootstrapPage,
overlay: { ...transcriptPage('older', null, 4), source: 'overlay' },
Expand All @@ -1164,8 +1147,8 @@ test('loads a history target with newer messages available below it', async () =
? { messages: messages.slice(4), nextCursor: null }
: { messages: messages.slice(0, 3), nextCursor: 'newer' },
loadTranscriptPage: async (input) => {
aroundInput = input;
return aroundPage;
inputs.push(input);
return input.direction === 'older' ? olderProbePage(0, false) : aroundPage;
},
async close() {},
});
Expand All @@ -1175,8 +1158,14 @@ test('loads a history target with newer messages available below it', async () =

await replica.loadAround(0, 128 * 1024);

assert.equal(aroundInput?.direction, 'newer');
assert.equal(aroundInput?.anchorSequence, null);
assert.deepEqual(
inputs.map(({ direction, anchorSequence }) => ({ direction, anchorSequence })),
[
{ direction: 'newer', anchorSequence: null },
// Nothing older than the anchor exists, and only this read can say so.
{ direction: 'older', anchorSequence: 0 },
],
);
assert.deepEqual(replica.snapshot().durable.map(({ sequence }) => sequence), [0, 1, 2]);
assert.equal(replica.snapshot().hasOlder, false);
assert.equal(replica.snapshot().hasNewer, true);
Expand Down Expand Up @@ -1207,7 +1196,6 @@ test('keeps an oversized transcript sparse while moving between indexed prompts'
events: { async *[Symbol.asyncIterator]() {} },
transcriptBootstrap: {
throughSequence: 15,
durableCoverage: 'complete',
overlayMessageCount: 0,
durable: bootstrapPage,
overlay: { ...transcriptPage('older', null, 15), source: 'overlay' },
Expand All @@ -1224,7 +1212,10 @@ test('keeps an oversized transcript sparse while moving between indexed prompts'
anchorSequence: input.anchorSequence,
maxBytes: input.maxBytes,
});
if (input.direction === 'older') return latestPage;
if (input.direction === 'older') {
if (input.maxBytes > 1) return latestPage;
return olderProbePage(input.anchorSequence!, input.anchorSequence !== 0);
}
return input.anchorSequence === null ? historicalPage : intermediatePage;
},
async close() {},
Expand Down Expand Up @@ -1269,9 +1260,13 @@ test('keeps an oversized transcript sparse while moving between indexed prompts'
assert.equal(rendererStore.range().hasNewer, false);
assertRangeFitsBudget(rendererStore);

// Every jump that is not to the tail pays one extra single-byte read, the
// only thing that can say whether the anchor has anything older than it.
assert.deepEqual(requests, [
{ direction: 'newer', anchorSequence: null, maxBytes: DESKTOP_TRANSCRIPT_RANGE_MAX_BYTES },
{ direction: 'older', anchorSequence: 0, maxBytes: 1 },
{ direction: 'newer', anchorSequence: 5, maxBytes: DESKTOP_TRANSCRIPT_RANGE_MAX_BYTES },
{ direction: 'older', anchorSequence: 6, maxBytes: 1 },
{ direction: 'older', anchorSequence: 16, maxBytes: DESKTOP_TRANSCRIPT_RANGE_MAX_BYTES },
]);
replica.close();
Expand Down Expand Up @@ -1311,7 +1306,6 @@ test('keeps history resident when an active overlay uses its own cache budget',
events: { async *[Symbol.asyncIterator]() {} },
transcriptBootstrap: {
throughSequence: 1,
durableCoverage: 'complete',
overlayMessageCount: 1,
durable: bootstrapPage,
overlay: { ...transcriptPage('older', null, 1), source: 'overlay' },
Expand Down Expand Up @@ -1602,6 +1596,26 @@ function transcriptPage(
};
}

/** The one-byte read `loadAround` uses to ask whether `sequence` has anything
* older than it: only the presence of a fragment answers, not its content. */
function olderProbePage(sequence: number, exists: boolean) {
return {
...transcriptPage('older', null, sequence),
fragments: exists
? [
{
kind: 'durable' as const,
sequence,
byteOffset: 0,
totalBytes: 1,
payloadDigest: null,
data: '',
},
]
: [],
};
}

function syntheticLargeTranscript(): Array<{ identity: number; message: StoredMessage }> {
return Array.from({ length: 8 }, (_, index) => {
const number = index + 1;
Expand Down
1 change: 0 additions & 1 deletion apps/desktop/src/main/__tests__/interrupted-resume.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ describe('latest interrupted resume candidate', () => {
ts: 2,
status: 'failed',
errorClass: 'timeout',
partialOutputRetained: false,
},
{ type: 'tool_call', id: 'call-1', turnId: 'turn-1', ts: 3, toolName: 'Read', args: {} },
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ function session(id: string): SessionSummary {
}

function settledTurn(turnId: string): TurnRecord {
return { turnId, status: 'completed', partialOutputRetained: false };
return { turnId, status: 'completed' };
}

const sourceSession = session('side-chat-disposal-source');
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src/main/__tests__/quote-companion-retry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2088,11 +2088,11 @@ function choiceFor(
}

function settledTurn(turnId: string): TurnRecord {
return { turnId, status: 'completed', partialOutputRetained: false };
return { turnId, status: 'completed' };
}

function runningTurn(turnId: string): TurnRecord {
return { turnId, status: 'running', partialOutputRetained: false };
return { turnId, status: 'running' };
}

async function waitUntil(predicate: () => boolean, diagnostics?: () => string): Promise<void> {
Expand Down
13 changes: 0 additions & 13 deletions apps/desktop/src/main/__tests__/runtime-host-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,6 @@ test('derives turn records from bounded contribution pages', async () => {
firstSequence: 0,
latestState: null,
userPromptPreview: 'hello',
hasAssistantMessage: true,
hasAssistantOutput: true,
hasToolResult: false,
hasFailedToolResult: false,
hasAbortNote: false,
}],
nextPosition: 2,
};
Expand All @@ -101,15 +96,9 @@ test('derives turn records from bounded contribution pages', async () => {
turnId: 'turn-1',
ts: 3,
status: 'completed',
partialOutputRetained: false,
},
},
userPromptPreview: null,
hasAssistantMessage: false,
hasAssistantOutput: false,
hasToolResult: true,
hasFailedToolResult: false,
hasAbortNote: false,
}],
nextPosition: null,
};
Expand All @@ -124,7 +113,6 @@ test('derives turn records from bounded contribution pages', async () => {
userPromptPreview: 'hello',
status: 'completed',
statusSource: 'recorded',
partialOutputRetained: true,
}]);
assert.deepEqual(positions, [0, 2]);
await client.close();
Expand Down Expand Up @@ -164,7 +152,6 @@ function subscription(
activeAssistantStreams: [],
transcriptBootstrap: {
throughSequence: null,
durableCoverage: 'complete',
overlayMessageCount: 0,
durable: emptyTranscriptPage(sessionId, 'durable'),
overlay: emptyTranscriptPage(sessionId, 'overlay'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,6 @@ test('fences transcript range failures across same-source replica recovery', asy
events,
transcriptBootstrap: {
throughSequence: 1,
durableCoverage: 'complete',
overlayMessageCount: 0,
durable: bootstrap,
overlay: { ...bootstrap, source: 'overlay', nextCursor: null },
Expand Down Expand Up @@ -990,7 +989,6 @@ test('finishes transcript open and replays a stale range request after replaceme
events,
transcriptBootstrap: {
throughSequence: 0,
durableCoverage: 'complete',
overlayMessageCount: 0,
durable: {
kind: 'page',
Expand Down Expand Up @@ -2166,7 +2164,6 @@ test("finishes a watched predecessor after initial catch-up recovery", async ()
turnId: "turn-1",
ts: 20,
status: "completed" as const,
partialOutputRetained: true,
},
]),
events: secondEvents,
Expand Down Expand Up @@ -2322,7 +2319,6 @@ test("reconciles terminal, Goal, interaction, and sidecar state after subscripti
turnId: 'turn-1',
status: 'completed' as const,
statusSource: 'recorded' as const,
partialOutputRetained: true,
}],
openSession: async () => {
openCount += 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ export function runtimeHostSessionFixture(input: {
activeAssistantStreams: input.activeAssistantStreams ?? [],
transcriptBootstrap: input.transcriptBootstrap ?? {
throughSequence: null,
durableCoverage: 'complete',
overlayMessageCount: 0,
durable: emptyPage(sessionId, 'durable'),
overlay: emptyPage(sessionId, 'overlay'),
Expand Down
Loading
Loading