Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
9aacce9
fix(ui): space numbers from units in the zh turn duration
Astro-Han Sep 15, 2026
8107844
fix(storage): serve running Turns through a per-event transcript wate…
Astro-Han Sep 15, 2026
424a491
fix(runtime): project any committed event prefix without an active mode
Astro-Han Sep 15, 2026
e08e94b
refactor(runtime-host): push per-event transcript progress and drop t…
Astro-Han Sep 15, 2026
3338f65
refactor(desktop): read running Turns from durable transcript pages
Astro-Han Sep 15, 2026
228b2a9
fix(ui): keep a live Turn's start at its first event
Astro-Han Sep 15, 2026
2922b42
fix(desktop): drop the retired transcript release recovery branch
Astro-Han Sep 15, 2026
3c2a750
test(ui): pin the running Turn's clock and its settled duration copy
Astro-Han Sep 16, 2026
2276f07
fix(desktop): merge an admitted Side Chat message from the transcript
Astro-Han Sep 16, 2026
5d8d862
fix(cli): keep the streamed answer when a transcript read lags it
Astro-Han Sep 16, 2026
bb239ec
fix(cli): let the later answer win, not the later observation
Astro-Han Sep 16, 2026
7b9160b
fix(runtime-host): keep a transcript bootstrap failure observable
Astro-Han Sep 16, 2026
58d6ef9
refactor(runtime-host): drop the transcript bootstrap's duplicate wat…
Astro-Han Sep 16, 2026
ba47413
test(ui): cover the recorded start taking over the live stand-in
Astro-Han Sep 16, 2026
089ab59
fix(desktop): keep a running Turn's subscription when the viewer leaves
Astro-Han Sep 16, 2026
c27cdd8
fix(runtime-host): give each subscriber one delivery order
Astro-Han Sep 16, 2026
4671432
fix(cli): let the stream's order decide the final answer, not a clock
Astro-Han Sep 16, 2026
6a35920
fix(storage): page the transcript by ordinal runs, not overlap compon…
Astro-Han Sep 16, 2026
24889e5
fix(runtime-host): start Session frames where the subscriber says it …
Astro-Han Sep 16, 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
2 changes: 1 addition & 1 deletion apps/desktop/renderer-architecture.json
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@
"@maka/ui": 1
},
"importSpecifiers": 7,
"nonTriviaTokens": 2725
"nonTriviaTokens": 2687
},
"src/renderer/app-shell-session-start-actions.ts": {
"importDeclarations": 2,
Expand Down
188 changes: 38 additions & 150 deletions apps/desktop/src/main/__tests__/desktop-transcript-range-store.test.ts

Large diffs are not rendered by default.

14 changes: 5 additions & 9 deletions apps/desktop/src/main/__tests__/runtime-host-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,7 @@ function subscription(
hostEpoch: 'host-1',
subscriptionId: `subscription-${sessionId}`,
activeAssistantStreams: [],
transcriptBootstrap: {
throughSequence: null,
overlayMessageCount: 0,
durable: emptyTranscriptPage(sessionId, 'durable'),
overlay: emptyTranscriptPage(sessionId, 'overlay'),
},
transcriptBootstrap: { durable: emptyTranscriptPage(sessionId) },
snapshot: {
schemaVersion: SESSION_CONTINUITY_SCHEMA_VERSION,
session: {
Expand All @@ -176,25 +171,26 @@ function subscription(
lifecycle.push(`${sessionId}:transcript`);
return [] as T[];
},
loadTranscriptOverlay: async <T>(_decodeMessage: (value: unknown) => T) => [] as T[],
decodeTranscriptPage: async () => {
throw new Error('Fake subscription does not expose transcript pages');
},
loadTranscriptPage: async () => {
throw new Error('Fake subscription does not expose transcript pages');
},
ready: async () => {
lifecycle.push(`${sessionId}:ready`);
},
close: async () => {
lifecycle.push(`${sessionId}:close`);
},
[Symbol.asyncIterator]: async function* (): AsyncIterator<SubscriptionFrame> {},
};
}

function emptyTranscriptPage(sessionId: string, source: 'durable' | 'overlay') {
function emptyTranscriptPage(sessionId: string) {
return {
kind: 'page' as const,
sessionId,
source,
direction: 'older' as const,
throughSequence: null,
rawBytes: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1442,12 +1442,23 @@ function connectionHarness(
if (options.subscriptionError) throw options.subscriptionError;
const subscriptionFrames = new AsyncFrameQueue();
activeSubscriptionFrames = subscriptionFrames;
const closeSubscription = () => { subscriptionFrames.end(); ptyListeners.clear(); };
// The Host holds a subscription's frames until the subscriber calls
// ready(), so handing them over earlier would let an ordering bug pass.
let releaseFrames = (): void => undefined;
const readyGate = new Promise<void>((resolve) => {
releaseFrames = resolve;
});
let closed = false;
const closeSubscription = () => {
closed = true;
subscriptionFrames.end();
ptyListeners.clear();
releaseFrames();
};
closeSubscriptions.add(closeSubscription);
const emptyPage = {
kind: 'page' as const,
sessionId,
source: 'durable' as const,
direction: 'older' as const,
throughSequence: null,
rawBytes: 0,
Expand All @@ -1468,15 +1479,17 @@ function connectionHarness(
activeAssistantStreams: options.activeAssistantStreams ?? [],
transcriptBootstrap: {
throughSequence: null,
overlayMessageCount: 0,
durable: emptyPage,
overlay: { ...emptyPage, source: 'overlay' },
},
loadTranscript: async () => [],
loadTranscriptOverlay: async () => [],
decodeTranscriptPage: async () => ({ messages: [], nextCursor: null }),
loadTranscriptPage: async () => emptyPage,
[Symbol.asyncIterator]: () => subscriptionFrames[Symbol.asyncIterator](),
[Symbol.asyncIterator]: async function* () {
await readyGate;
if (closed) return;
yield* subscriptionFrames;
},
ready: async () => releaseFrames(),
close: async () => closeSubscription(),
};
},
Expand Down
Loading