Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c15377f
feat(runtime): derive semantic request prefix continuity
Astro-Han Aug 31, 2026
956e08a
fix(runtime): preserve request prefix lineage across copies
Astro-Han Aug 31, 2026
1fa238e
feat(desktop): show semantic request prefix continuity
Astro-Han Aug 31, 2026
320ed8e
test(runtime): verify prefix continuity after restart
Astro-Han Aug 31, 2026
50630d1
fix(runtime): qualify semantic prefix request domains
Astro-Han Aug 31, 2026
d80ce03
fix(runtime): copy durable request prefix lineage
Astro-Han Aug 31, 2026
966f2c5
fix(runtime): normalize unavailable prefix diagnostics
Astro-Han Aug 31, 2026
ddfb433
fix(desktop): hide stale request prefix verdicts
Astro-Han Aug 31, 2026
b787308
feat(desktop): show provider cache usage
Astro-Han Aug 31, 2026
6d25152
style: format semantic prefix stack
Astro-Han Aug 31, 2026
fe3db69
fix(runtime): prioritize known prefix structure
Astro-Han Aug 31, 2026
cee8ba3
fix(desktop): avoid unsupported cache zeroes
Astro-Han Aug 31, 2026
e6b079d
fix(runtime): preserve historical root lineage copies
Astro-Han Aug 31, 2026
50842b7
style(desktop): align context refresh flow
Astro-Han Aug 31, 2026
3a35c7d
fix(runtime-host): restore semantic domain resolution
Astro-Han Aug 31, 2026
deec6d7
fix(runtime-host): advance semantic diagnostics epoch
Astro-Han Aug 31, 2026
9222c1f
fix(runtime-host): require prefix diagnostics verdict
Astro-Han Aug 31, 2026
08e353d
fix(runtime): resolve durable continuation prefix tips
Astro-Han Aug 31, 2026
6a39cca
fix(runtime-host): qualify complete request domains
Astro-Han Aug 31, 2026
b556e33
fix(desktop): invalidate inactive prefix diagnostics
Astro-Han Aug 31, 2026
d107fa5
docs(desktop): refresh Astryx surface inventory
Astro-Han Aug 31, 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
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ import assert from 'node:assert/strict';
import { test } from 'node:test';
import { createElement } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { InspectorCompositionSection } from '../../renderer/features/workbar/testing.js';
import {
InspectorCompositionSection,
InspectorRequestPrefixTag,
} from '../../renderer/features/workbar/testing.js';
import { getDesktopConversationCopy } from '../../renderer/locales/conversation-copy.js';

test('maps each request-composition category to the same colour in the chart and legend', () => {
Expand Down Expand Up @@ -60,3 +63,20 @@ test('maps each request-composition category to the same colour in the chart and
);
}
});

test('renders the Host request-prefix verdict as one compact Inspector badge', () => {
const markup = renderToStaticMarkup(
createElement(InspectorRequestPrefixTag, {
copy: getDesktopConversationCopy('en').inspector,
requestPrefix: {
status: 'diverged',
previousSegmentCount: 8,
preservedSegmentCount: 3,
firstDivergentSegment: { kind: 'message', index: 2, role: 'user' },
},
}),
);

assert.match(markup, /data-maka-contract="session-inspector-request-prefix"/);
assert.match(markup, /Request prefix 3\/8 · diverged at message 3/);
});
106 changes: 106 additions & 0 deletions apps/desktop/src/main/__tests__/session-inspector-panel-model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,112 @@ test('does not estimate a cache-hit ratio from partial usage', () => {
});

assert.equal(overview.cacheHitRate, undefined);
assert.equal(overview.providerCacheUsage, undefined);
});

test('does not present absent provider cache fields as reported zeroes', () => {
const overview = deriveInspectorOverviewModel(undefined, {
range: { from: 0, to: 1 },
totalRequests: 1,
totalCostUsd: 0,
totalTokens: {
input: 10,
output: 1,
cacheMiss: 10,
cacheRead: 0,
cacheWrite: 0,
reasoning: 0,
total: 11,
},
cacheHitRequests: 0,
cacheCreateRequests: 0,
errorRequests: 0,
provenance: {
coverage: {
attempts: 1,
pricedAttempts: 1,
unpricedAttempts: 0,
usageReportedAttempts: 1,
usagePartialAttempts: 0,
usageMissingAttempts: 0,
},
legacyRecords: 0,
unreadableRecords: 0,
pendingRepairs: 0,
},
});

assert.equal(overview.providerCacheUsage, undefined);
});

test('passes through the Host request-prefix verdict independently of cache usage', () => {
const requestPrefix = {
status: 'diverged' as const,
previousSegmentCount: 8,
preservedSegmentCount: 3,
firstDivergentSegment: { kind: 'message' as const, index: 2, role: 'user' },
};

const overview = deriveInspectorOverviewModel(
{
status: 'available',
providerId: 'anthropic',
modelId: 'claude',
completedAt: 10,
requestPrefix,
},
{
range: { from: 0, to: 10 },
totalRequests: 1,
totalCostUsd: 0,
totalTokens: {
input: 10,
output: 1,
cacheMiss: 5,
cacheRead: 5,
cacheWrite: 2,
reasoning: 0,
total: 11,
},
cacheHitRequests: 1,
cacheCreateRequests: 1,
errorRequests: 0,
provenance: {
coverage: {
attempts: 1,
pricedAttempts: 1,
unpricedAttempts: 0,
usageReportedAttempts: 1,
usagePartialAttempts: 0,
usageMissingAttempts: 0,
},
legacyRecords: 0,
unreadableRecords: 0,
pendingRepairs: 0,
},
},
);

assert.deepEqual(overview.requestPrefix, requestPrefix);
assert.equal(overview.cacheHitRate, 0.5);
assert.deepEqual(overview.providerCacheUsage, { read: 5, write: 2 });

const stale = deriveInspectorOverviewModel(
{
status: 'available',
providerId: 'anthropic',
modelId: 'claude',
completedAt: 10,
inputTokens: 10,
contextWindow: 100,
requestPrefix,
},
undefined,
{ contextCurrent: false },
);
assert.equal(stale.context, undefined);
assert.equal(stale.composition, undefined);
assert.equal(stale.requestPrefix, undefined);
});
test('derives per-turn cost only from priced model-call step totals', () => {
const cases: readonly {
Expand Down
112 changes: 112 additions & 0 deletions apps/desktop/src/main/__tests__/use-session-trace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
} from '@maka/core/session-trace';
import type { SessionEvent } from '@maka/core/events';
import type { Result } from '@maka/core/result';
import type { ContextDiagnosticsResult } from '@maka/runtime-host/protocol';
import { cleanupFakeDom, installReactRenderer } from './fake-dom.js';
import {
createFakeWorkbarServices,
Expand Down Expand Up @@ -120,6 +121,10 @@ function createTraceHarness(
sessionId: string,
readIndex: number,
) => Promise<Result<WorkbarSessionUsageSummary>>;
context?: (
sessionId: string,
readIndex: number,
) => Promise<Result<ContextDiagnosticsResult>>;
} = {},
): TraceHarness {
const handlers = new Set<(event: SessionEvent) => void>();
Expand Down Expand Up @@ -168,6 +173,7 @@ function createTraceHarness(
// TRACE is re-read, and an enrichment read must not move them.
context: async (sessionId: string) => {
harness.contextReads.push(sessionId);
if (options.context) return options.context(sessionId, harness.contextReads.length);
return {
ok: true as const,
data: {
Expand Down Expand Up @@ -682,4 +688,110 @@ describe('useSessionTrace', () => {
assert.equal(snapshot?.summary, undefined);
assert.equal(snapshot?.summaryError, true);
});

it('marks an old context snapshot non-current while its refresh is pending or failed', async () => {
const { root } = installReactRenderer();
let resolveRefresh:
| ((result: Result<ContextDiagnosticsResult>) => void)
| undefined;
const refresh = new Promise<Result<ContextDiagnosticsResult>>((resolve) => {
resolveRefresh = resolve;
});
const harness = createTraceHarness({
context: async (_sessionId, readIndex) =>
readIndex === 1
? {
ok: true,
data: {
status: 'available',
providerId: 'anthropic',
modelId: 'claude',
completedAt: 1,
requestPrefix: {
status: 'preserved',
previousSegmentCount: 8,
preservedSegmentCount: 8,
},
},
}
: refresh,
});
let snapshot: ReturnType<typeof useSessionTrace> | undefined;
await act(async () => {
root.render(
createElement(Probe, {
services: harness.services,
sessionId: 'session-1',
active: true,
onHookSnapshot: (value) => {
snapshot = value;
},
}),
);
});
assert.equal(snapshot?.context?.status, 'available');
assert.equal(snapshot?.contextLoading, false);

await act(async () => harness.emit(event('complete')));
await flushRefresh();

assert.equal(snapshot?.context?.status, 'available', 'the prior answer remains inspectable');
assert.equal(snapshot?.contextLoading, true, 'but it is no longer marked current');
assert.equal(snapshot?.contextError, undefined);

await act(async () => {
resolveRefresh?.({ ok: false, error: { code: 'FAILED', message: 'failed' } });
});
assert.equal(snapshot?.contextLoading, false);
assert.equal(snapshot?.contextError, true);
});

it('invalidates context before an inactive Inspector can be reactivated', async () => {
const { root } = installReactRenderer();
const pendingRefresh = new Promise<Result<ContextDiagnosticsResult>>(() => undefined);
const harness = createTraceHarness({
context: async (_sessionId, readIndex) =>
readIndex === 1
? {
ok: true,
data: {
status: 'available',
providerId: 'anthropic',
modelId: 'claude',
completedAt: 1,
requestPrefix: {
status: 'preserved',
previousSegmentCount: 8,
preservedSegmentCount: 8,
},
},
}
: pendingRefresh,
});
let snapshot: ReturnType<typeof useSessionTrace> | undefined;
const render = async (active: boolean) => {
await act(async () => {
root.render(
createElement(Probe, {
services: harness.services,
sessionId: 'session-1',
active,
onHookSnapshot: (value) => {
snapshot = value;
},
}),
);
});
};

await render(true);
assert.equal(snapshot?.context?.status, 'available');

await render(false);
assert.equal(snapshot?.context, undefined);

await render(true);
assert.equal(snapshot?.context, undefined);
assert.equal(snapshot?.contextLoading, true);
});
});
6 changes: 5 additions & 1 deletion apps/desktop/src/renderer/features/workbar/testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ export * from './model/workbar-tool-definitions.js';
export * from './tools/artifacts/artifact-list-keyboard.js';
export * from './tools/artifacts/artifact-visibility.js';
export * from './tools/inspector/session-inspector-panel-model.js';
export { compactNumberFormatter, InspectorCompositionSection } from './tools/inspector/session-inspector-panel.js';
export {
compactNumberFormatter,
InspectorCompositionSection,
InspectorRequestPrefixTag,
} from './tools/inspector/session-inspector-panel.js';
export * from './tools/inspector/session-inspector-overview-model.js';
export * from './tools/side-chat/quote-companion-panel-state.js';
export * from './tools/side-chat/quote-companion-core.js';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/

import type {
ContextDiagnosticsRequestPrefix,
ContextDiagnosticsResult,
ContextDiagnosticsSegment,
} from '@maka/runtime-host/protocol';
Expand Down Expand Up @@ -135,11 +136,19 @@ export interface InspectorOverviewModel {
* Absent when no input was metered at all — a rate over nothing is not
* zero, it is unknown.
*
* The only token figure the panel keeps. The raw totals it used to carry
* were priced by `totals.costUsd`, sized by the context bar and audited in
* the run ledger; three statements of the same tokens is two too many.
* Kept beside the provider's reported cache read/write counts, never used to
* infer the semantic request-prefix verdict.
*/
cacheHitRate?: number;
/** Positive provider cache token usage; never inferred from semantic continuity. */
providerCacheUsage?: { readonly read?: number; readonly write?: number };
/** Runtime/Host conclusion, passed through without local comparison. */
requestPrefix?: ContextDiagnosticsRequestPrefix;
}

export interface InspectorOverviewOptions {
/** False while the Host snapshot is refreshing or its refresh failed. */
contextCurrent?: boolean;
}

export function estimatedSessionCost(
Expand Down Expand Up @@ -173,19 +182,52 @@ export function hasUnavailableSessionUsage(
export function deriveInspectorOverviewModel(
diagnostics?: ContextDiagnosticsResult,
usage?: SessionUsageSummary,
options?: InspectorOverviewOptions,
): InspectorOverviewModel {
// Both halves of the context block come from the SAME snapshot. They used to
// be picked separately — the bar from the latest trace attempt that carried a
// window, the breakdown from the latest diagnostics — so a newest call
// without a window put one request's fullness above another request's
// contents. One source cannot disagree with itself (#2323).
const composition = compositionState(diagnostics);
const context = contextBudget(diagnostics);
const currentDiagnostics = options?.contextCurrent === false ? undefined : diagnostics;
const composition = compositionState(currentDiagnostics);
const context = contextBudget(currentDiagnostics);
const cacheHitRate = usageCacheHitRate(usage);
const providerCacheUsage = completeProviderCacheUsage(usage);
const requestPrefix =
currentDiagnostics?.status === 'available' ? currentDiagnostics.requestPrefix : undefined;
return {
...(context ? { context } : {}),
...(composition ? { composition } : {}),
...(cacheHitRate !== undefined ? { cacheHitRate } : {}),
...(providerCacheUsage ? { providerCacheUsage } : {}),
...(requestPrefix ? { requestPrefix } : {}),
};
}

function completeProviderCacheUsage(
usage: SessionUsageSummary | undefined,
): InspectorOverviewModel['providerCacheUsage'] {
const provenance = usage?.provenance;
if (!usage || !provenance) return undefined;
const coverage = provenance.coverage;
if (
coverage.attempts === 0 ||
coverage.usageReportedAttempts !== coverage.attempts ||
coverage.usagePartialAttempts > 0 ||
coverage.usageMissingAttempts > 0 ||
provenance.legacyRecords > 0 ||
provenance.unreadableRecords > 0 ||
provenance.pendingRepairs > 0
) {
return undefined;
}
const read = usage.totalTokens.cacheRead > 0 ? usage.totalTokens.cacheRead : undefined;
const write = usage.totalTokens.cacheWrite > 0 ? usage.totalTokens.cacheWrite : undefined;
if (read === undefined && write === undefined) return undefined;
return {
...(read !== undefined ? { read } : {}),
...(write !== undefined ? { write } : {}),
};
}

Expand Down
Loading
Loading