feat(quick-pane): promote active runtime
This commit is contained in:
@@ -194,6 +194,56 @@ describe("workspace reducer", () => {
|
||||
]);
|
||||
expect(state.runtimesById.a.modelError).toContain("RPC timed out");
|
||||
});
|
||||
it("preserves reasoning model metadata from runtime snapshots", () => {
|
||||
let state = reconcileWorkspace(initialWorkspaceState, workspace);
|
||||
state = workspaceReducer(state, {
|
||||
type: "snapshotRequested",
|
||||
runtimeId: "a",
|
||||
generation: 1,
|
||||
});
|
||||
state = workspaceReducer(state, {
|
||||
type: "snapshotLoaded",
|
||||
runtimeId: "a",
|
||||
generation: 1,
|
||||
snapshot: {
|
||||
bridgeInstanceId: "epoch",
|
||||
latestSeq: 1,
|
||||
runtime: runtime("a"),
|
||||
state: {
|
||||
data: {
|
||||
model: {
|
||||
provider: "provider",
|
||||
id: "reasoning",
|
||||
reasoning: true,
|
||||
thinkingLevelMap: { high: null, xhigh: "xhigh" },
|
||||
},
|
||||
},
|
||||
},
|
||||
models: {
|
||||
data: {
|
||||
models: [
|
||||
{
|
||||
provider: "provider",
|
||||
id: "reasoning",
|
||||
reasoning: true,
|
||||
thinkingLevelMap: { high: null, xhigh: "xhigh" },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
extensions: [],
|
||||
},
|
||||
});
|
||||
expect(state.runtimesById.a.state.model).toEqual({
|
||||
provider: "provider",
|
||||
id: "reasoning",
|
||||
reasoning: true,
|
||||
thinkingLevelMap: { high: null, xhigh: "xhigh" },
|
||||
});
|
||||
expect(state.runtimesById.a.models[0]).toEqual(
|
||||
state.runtimesById.a.state.model,
|
||||
);
|
||||
});
|
||||
it("removes the optimistic submission when Pi returns its structured user message", () => {
|
||||
let state = reconcileWorkspace(initialWorkspaceState, workspace);
|
||||
state = workspaceReducer(state, {
|
||||
|
||||
@@ -60,19 +60,23 @@ export type Message = {
|
||||
| string
|
||||
| Array<{ type?: string; text?: string; thinking?: string; name?: string }>;
|
||||
};
|
||||
type ThinkingLevel =
|
||||
| "off"
|
||||
| "minimal"
|
||||
| "low"
|
||||
| "medium"
|
||||
| "high"
|
||||
| "xhigh"
|
||||
| "max";
|
||||
|
||||
export type AgentState = {
|
||||
isStreaming?: boolean;
|
||||
thinkingLevel?: string;
|
||||
thinkingLevel?: ThinkingLevel;
|
||||
sessionName?: string;
|
||||
sessionId?: string;
|
||||
messageCount?: number;
|
||||
pendingMessageCount?: number;
|
||||
model?: {
|
||||
provider?: string;
|
||||
id?: string;
|
||||
name?: string;
|
||||
contextWindow?: number;
|
||||
};
|
||||
model?: Model & { contextWindow?: number };
|
||||
};
|
||||
export type SessionStats = {
|
||||
tokens?: {
|
||||
@@ -93,7 +97,8 @@ export type Model = {
|
||||
provider?: string;
|
||||
id?: string;
|
||||
name?: string;
|
||||
thinkingLevelMap?: Record<string, string>;
|
||||
reasoning?: boolean;
|
||||
thinkingLevelMap?: Partial<Record<ThinkingLevel, string | null>>;
|
||||
};
|
||||
export type Extension = {
|
||||
id?: string;
|
||||
|
||||
@@ -28,6 +28,9 @@ export function useWorkspace() {
|
||||
const legacyMigrationPending = useRef(false);
|
||||
const disposed = useRef(true);
|
||||
const refreshGeneration = useRef(0);
|
||||
const workspaceReady = useRef(false);
|
||||
const pendingPromotedRuntime = useRef<string | undefined>(undefined);
|
||||
const consumingPromotion = useRef(false);
|
||||
|
||||
const loadSnapshot = useCallback(
|
||||
async (runtimeId: string, options: { throwOnError?: boolean } = {}) => {
|
||||
@@ -150,10 +153,44 @@ export function useWorkspace() {
|
||||
[loadSnapshot],
|
||||
);
|
||||
|
||||
const consumePromotedRuntime = useCallback(() => {
|
||||
if (!workspaceReady.current || consumingPromotion.current) return;
|
||||
consumingPromotion.current = true;
|
||||
void (async () => {
|
||||
while (pendingPromotedRuntime.current && !disposed.current) {
|
||||
const runtimeId = pendingPromotedRuntime.current;
|
||||
pendingPromotedRuntime.current = undefined;
|
||||
await refresh();
|
||||
if (disposed.current) return;
|
||||
dispatch({ type: "selectRuntime", runtimeId });
|
||||
await loadSnapshot(runtimeId);
|
||||
if (disposed.current) return;
|
||||
await invoke("consume_pending_promoted_runtime", { runtimeId });
|
||||
}
|
||||
})()
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
consumingPromotion.current = false;
|
||||
if (pendingPromotedRuntime.current) consumePromotedRuntime();
|
||||
});
|
||||
}, [loadSnapshot, refresh]);
|
||||
|
||||
useEffect(() => {
|
||||
disposed.current = false;
|
||||
workspaceReady.current = false;
|
||||
let stop: (() => void) | undefined;
|
||||
let stopNewRuntime: (() => void) | undefined;
|
||||
let stopPromotedRuntime: (() => void) | undefined;
|
||||
const promotedListenerReady = listen<{ runtimeId: string }>(
|
||||
"workspace-promoted-runtime",
|
||||
(message) => {
|
||||
pendingPromotedRuntime.current = message.payload.runtimeId;
|
||||
consumePromotedRuntime();
|
||||
},
|
||||
).then((unlisten) => {
|
||||
if (disposed.current) unlisten();
|
||||
else stopPromotedRuntime = unlisten;
|
||||
});
|
||||
void listen<{
|
||||
phase: "starting" | "ready" | "error";
|
||||
runtimeId?: string;
|
||||
@@ -197,6 +234,7 @@ export function useWorkspace() {
|
||||
"runtime_recovered",
|
||||
"runtime_renewed",
|
||||
"runtime_restore_failed",
|
||||
"runtime_promoted",
|
||||
].includes(eventType ?? "")
|
||||
) {
|
||||
void refresh();
|
||||
@@ -215,10 +253,17 @@ export function useWorkspace() {
|
||||
});
|
||||
void (async () => {
|
||||
try {
|
||||
await promotedListenerReady;
|
||||
const loaded = await loadPresentation();
|
||||
if (disposed.current) return;
|
||||
await refresh(loaded);
|
||||
if (disposed.current) return;
|
||||
workspaceReady.current = true;
|
||||
const runtimeId = await invoke<string | null>(
|
||||
"get_pending_promoted_runtime",
|
||||
).catch(() => null);
|
||||
if (runtimeId) pendingPromotedRuntime.current = runtimeId;
|
||||
consumePromotedRuntime();
|
||||
initialized.current = true;
|
||||
} catch (error) {
|
||||
if (!disposed.current)
|
||||
@@ -243,9 +288,11 @@ export function useWorkspace() {
|
||||
document.addEventListener("visibilitychange", visibility);
|
||||
return () => {
|
||||
disposed.current = true;
|
||||
workspaceReady.current = false;
|
||||
refreshGeneration.current += 1;
|
||||
stop?.();
|
||||
stopNewRuntime?.();
|
||||
stopPromotedRuntime?.();
|
||||
void invoke("unsubscribe_workspace").catch(() => {});
|
||||
window.removeEventListener("focus", visibility);
|
||||
window.removeEventListener("blur", visibility);
|
||||
@@ -253,7 +300,7 @@ export function useWorkspace() {
|
||||
Object.values(timers.current).forEach(clearTimeout);
|
||||
void appWindow;
|
||||
};
|
||||
}, [refresh, scheduleSnapshot]);
|
||||
}, [consumePromotedRuntime, refresh, scheduleSnapshot]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!initialized.current) return;
|
||||
|
||||
Reference in New Issue
Block a user