fix: keep optimistic status during agent bootstrap phase
This commit is contained in:
@@ -47,6 +47,19 @@ function createAgent(id: string): Agent {
|
||||
};
|
||||
}
|
||||
|
||||
function createAgentWithStatus({
|
||||
id,
|
||||
status,
|
||||
}: {
|
||||
id: string;
|
||||
status: Agent["status"];
|
||||
}): Agent {
|
||||
return {
|
||||
...createAgent(id),
|
||||
status,
|
||||
};
|
||||
}
|
||||
|
||||
function createBaseInput(): AgentScreenMachineInput {
|
||||
return {
|
||||
agent: null,
|
||||
@@ -357,6 +370,86 @@ describe("deriveAgentScreenViewState", () => {
|
||||
expect(handoffReady.agent.id).toBe("draft-agent");
|
||||
});
|
||||
|
||||
it("keeps optimistic running status while authoritative agent is still bootstrapping", () => {
|
||||
const memory = createBaseMemory();
|
||||
const input: AgentScreenMachineInput = {
|
||||
...createBaseInput(),
|
||||
agent: createAgentWithStatus({ id: "agent-1", status: "idle" }),
|
||||
placeholderAgent: createAgent("agent-1"),
|
||||
shouldUseOptimisticStream: true,
|
||||
};
|
||||
|
||||
const result = deriveAgentScreenViewState({ input, memory });
|
||||
const ready = expectReadyState(result.state);
|
||||
|
||||
expect(ready.source).toBe("optimistic");
|
||||
expect(ready.agent.status).toBe("running");
|
||||
});
|
||||
|
||||
it("keeps optimistic running status while authoritative agent is initializing", () => {
|
||||
const memory = createBaseMemory();
|
||||
const input: AgentScreenMachineInput = {
|
||||
...createBaseInput(),
|
||||
agent: createAgentWithStatus({ id: "agent-1", status: "initializing" }),
|
||||
placeholderAgent: createAgent("agent-1"),
|
||||
shouldUseOptimisticStream: true,
|
||||
};
|
||||
|
||||
const result = deriveAgentScreenViewState({ input, memory });
|
||||
const ready = expectReadyState(result.state);
|
||||
|
||||
expect(ready.source).toBe("optimistic");
|
||||
expect(ready.agent.status).toBe("running");
|
||||
});
|
||||
|
||||
it("hands off to authoritative once agent reaches running", () => {
|
||||
const memory = createBaseMemory();
|
||||
const input: AgentScreenMachineInput = {
|
||||
...createBaseInput(),
|
||||
agent: createAgentWithStatus({ id: "agent-1", status: "running" }),
|
||||
placeholderAgent: createAgent("agent-1"),
|
||||
shouldUseOptimisticStream: true,
|
||||
};
|
||||
|
||||
const result = deriveAgentScreenViewState({ input, memory });
|
||||
const ready = expectReadyState(result.state);
|
||||
|
||||
expect(ready.source).toBe("authoritative");
|
||||
expect(ready.agent.status).toBe("running");
|
||||
});
|
||||
|
||||
it("hands off to authoritative for terminal error states", () => {
|
||||
const memory = createBaseMemory();
|
||||
const input: AgentScreenMachineInput = {
|
||||
...createBaseInput(),
|
||||
agent: createAgentWithStatus({ id: "agent-1", status: "error" }),
|
||||
placeholderAgent: createAgent("agent-1"),
|
||||
shouldUseOptimisticStream: true,
|
||||
};
|
||||
|
||||
const result = deriveAgentScreenViewState({ input, memory });
|
||||
const ready = expectReadyState(result.state);
|
||||
|
||||
expect(ready.source).toBe("authoritative");
|
||||
expect(ready.agent.status).toBe("error");
|
||||
});
|
||||
|
||||
it("hands off to authoritative for terminal closed states", () => {
|
||||
const memory = createBaseMemory();
|
||||
const input: AgentScreenMachineInput = {
|
||||
...createBaseInput(),
|
||||
agent: createAgentWithStatus({ id: "agent-1", status: "closed" }),
|
||||
placeholderAgent: createAgent("agent-1"),
|
||||
shouldUseOptimisticStream: true,
|
||||
};
|
||||
|
||||
const result = deriveAgentScreenViewState({ input, memory });
|
||||
const ready = expectReadyState(result.state);
|
||||
|
||||
expect(ready.source).toBe("authoritative");
|
||||
expect(ready.agent.status).toBe("closed");
|
||||
});
|
||||
|
||||
it("emits history refresh toast only on transition into toast catch-up state", () => {
|
||||
const memory = createBaseMemory({
|
||||
hasRenderedReady: true,
|
||||
|
||||
@@ -93,7 +93,17 @@ export function deriveAgentScreenViewState({
|
||||
nextMemory.hadInitialSyncFailure = true;
|
||||
}
|
||||
|
||||
const candidateAgent = input.agent ?? input.placeholderAgent;
|
||||
const useOptimisticCreateFlowAgent =
|
||||
input.shouldUseOptimisticStream &&
|
||||
Boolean(input.placeholderAgent) &&
|
||||
(!input.agent ||
|
||||
input.agent.status === "initializing" ||
|
||||
input.agent.status === "idle");
|
||||
|
||||
const candidateAgent =
|
||||
input.agent && useOptimisticCreateFlowAgent && input.placeholderAgent
|
||||
? { ...input.agent, status: input.placeholderAgent.status }
|
||||
: input.agent ?? input.placeholderAgent;
|
||||
if (candidateAgent) {
|
||||
nextMemory.hasRenderedReady = true;
|
||||
nextMemory.lastReadyAgent = candidateAgent;
|
||||
@@ -132,11 +142,13 @@ export function deriveAgentScreenViewState({
|
||||
};
|
||||
}
|
||||
|
||||
const source: "authoritative" | "optimistic" | "stale" = input.agent
|
||||
? "authoritative"
|
||||
: input.shouldUseOptimisticStream
|
||||
? "optimistic"
|
||||
: "stale";
|
||||
const source: "authoritative" | "optimistic" | "stale" = useOptimisticCreateFlowAgent
|
||||
? "optimistic"
|
||||
: input.agent
|
||||
? "authoritative"
|
||||
: input.shouldUseOptimisticStream
|
||||
? "optimistic"
|
||||
: "stale";
|
||||
|
||||
let sync: AgentScreenReadySyncState;
|
||||
if (!input.isConnected) {
|
||||
|
||||
@@ -652,6 +652,14 @@ function AgentScreenContent({
|
||||
}, [optimisticStreamItems, streamItems]);
|
||||
|
||||
const shouldUseOptimisticStream = isPendingCreateForRoute && optimisticStreamItems.length > 0;
|
||||
const authoritativeStatus = agent?.status;
|
||||
const isAuthoritativeBootstrapping =
|
||||
authoritativeStatus === "initializing" || authoritativeStatus === "idle";
|
||||
const showPendingCreateSubmitLoading =
|
||||
isPendingCreateForRoute &&
|
||||
(!authoritativeStatus || isAuthoritativeBootstrapping);
|
||||
const canFinalizePendingCreate =
|
||||
Boolean(authoritativeStatus) && !isAuthoritativeBootstrapping;
|
||||
|
||||
const placeholderAgent: Agent | null = useMemo(() => {
|
||||
if (!shouldUseOptimisticStream || !resolvedAgentId) {
|
||||
@@ -730,7 +738,7 @@ function AgentScreenContent({
|
||||
item.kind === "user_message" &&
|
||||
item.id === pendingCreate.clientMessageId
|
||||
);
|
||||
if (agent && hasUserMessage) {
|
||||
if (hasUserMessage && canFinalizePendingCreate) {
|
||||
if (
|
||||
resolvedAgentId &&
|
||||
pendingCreate.images &&
|
||||
@@ -760,7 +768,7 @@ function AgentScreenContent({
|
||||
clearPendingCreate();
|
||||
}
|
||||
}, [
|
||||
agent,
|
||||
canFinalizePendingCreate,
|
||||
clearPendingCreate,
|
||||
isPendingCreateForRoute,
|
||||
markPendingCreateLifecycle,
|
||||
@@ -1239,6 +1247,7 @@ function AgentScreenContent({
|
||||
agentId={resolvedAgentId}
|
||||
serverId={serverId}
|
||||
autoFocus
|
||||
isSubmitLoading={showPendingCreateSubmitLoading}
|
||||
onAddImages={handleAddImagesCallback}
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user