From 3b9d332384956c3e94b6fcbde012e4bba36fbc6e Mon Sep 17 00:00:00 2001 From: Jiadong Bai <43344272+bobbai00@users.noreply.github.com> Date: Wed, 24 Jun 2026 01:23:09 -0700 Subject: [PATCH] refactor(agent-service): remove dead checkout/headChange path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The step-checkout feature was wired end-to-end but unreachable: nothing in the product ever invokes the frontend `checkoutStep()`, so the backend `/agents/:id/checkout` endpoint and its `WsServerHeadChangeEvent` broadcast were never triggered. Remove the dead vertical slice: - types/ws/server.ts: drop the `WsServerHeadChangeEvent` class and its membership in the `WsServerEvent` union (and the now-unused `WorkflowContent` import). - server.ts: drop the `POST /:id/checkout` route, the `WsServerHeadChangeEvent` import, and its mention in the WS usage banner. - texera-agent.ts: drop `TexeraAgent.checkout()`. - server.spec.ts: drop the `checkout route` test block. - agent.service.ts (frontend): drop the `case "WsServerHeadChangeEvent"` handler and `checkoutStep()`. HEAD tracking itself is retained — the snapshot/step messages still carry `headId`, and the chat panel still walks the ancestor path from HEAD to render the visible step chain. Only the backward HEAD move (checkout) is removed. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_012qFkyrpTd5PrkNBPcBeo4Q --- agent-service/src/agent/texera-agent.ts | 12 +------- agent-service/src/server.spec.ts | 26 ----------------- agent-service/src/server.ts | 29 ++----------------- agent-service/src/types/ws/server.ts | 25 +--------------- .../workspace/service/agent/agent.service.ts | 28 ------------------ 5 files changed, 4 insertions(+), 116 deletions(-) diff --git a/agent-service/src/agent/texera-agent.ts b/agent-service/src/agent/texera-agent.ts index 62cf20f145e..ccd0545919a 100644 --- a/agent-service/src/agent/texera-agent.ts +++ b/agent-service/src/agent/texera-agent.ts @@ -75,7 +75,7 @@ type ReActStepCallback = (step: ReActStep) => void; /** * A single Texera agent instance. * - * Owns the conversation (ReAct step tree with HEAD/checkout semantics), the + * Owns the conversation (ReAct step tree with HEAD tracking), the * workflow being edited (`WorkflowState`), cached operator execution results * (`WorkflowResultState`), and the tool surface exposed to the LLM. Each call * to `sendMessage` drives one multi-step generation via the Vercel AI SDK, @@ -298,16 +298,6 @@ export class TexeraAgent { return Array.from(this.stepsById.values()).filter(s => s.id !== INITIAL_STEP_ID); } - checkout(stepId: string): boolean { - const step = this.stepsById.get(stepId); - if (!step && stepId !== INITIAL_STEP_ID) return false; - this.head = stepId; - if (step?.afterWorkflowContent) { - this.workflowState.setWorkflowContent(step.afterWorkflowContent); - } - return true; - } - setStepCallback(callback: ReActStepCallback | null): void { this.stepCallback = callback; } diff --git a/agent-service/src/server.spec.ts b/agent-service/src/server.spec.ts index 2024c634138..c1fdddc9339 100644 --- a/agent-service/src/server.spec.ts +++ b/agent-service/src/server.spec.ts @@ -354,32 +354,6 @@ describe("agent read routes", () => { }); }); -describe("checkout route", () => { - test("broadcasts and survives a websocket whose send throws", async () => { - const id = (await readJson<{ id: string }>(await createAgent())).id; - const agent = _getAgentForTests(id)!; - (agent as any).checkout = () => true; - (agent as any).getAllSteps = () => []; - // A failing socket must be dropped inside broadcastToAgentClients, not crash the request. - agent.addClient({ - send: () => { - throw new Error("send failed"); - }, - } as any); - - const res = await postJson(`${API}/agents/${id}/checkout`, { stepId: "step-1" }); - expect(res.status).toBe(200); - expect((await readJson<{ headId: string }>(res)).headId).toBe("step-1"); - }); - - test("returns 500 when the step cannot be found", async () => { - const id = (await readJson<{ id: string }>(await createAgent())).id; - (_getAgentForTests(id) as any).checkout = () => false; - const res = await postJson(`${API}/agents/${id}/checkout`, { stepId: "missing" }); - expect(res.status).toBe(500); - }); -}); - describe("non-router routes", () => { test("unknown routes fall through to the catch-all error handler", async () => { const res = await getJson("/no-such-route"); diff --git a/agent-service/src/server.ts b/agent-service/src/server.ts index 9370e7b5c18..030d27b95bf 100644 --- a/agent-service/src/server.ts +++ b/agent-service/src/server.ts @@ -41,13 +41,7 @@ import type { } from "./types/agent"; import { AgentState, OperatorResultSerializationMode } from "./types/agent"; import type { WsClientCommand, WsServerEvent } from "./types/ws"; -import { - WsServerSnapshotEvent, - WsServerStepEvent, - WsServerStatusEvent, - WsServerErrorEvent, - WsServerHeadChangeEvent, -} from "./types/ws"; +import { WsServerSnapshotEvent, WsServerStepEvent, WsServerStatusEvent, WsServerErrorEvent } from "./types/ws"; import type { OperatorResultSummary } from "./types/execution"; const agentStore = new Map(); @@ -317,25 +311,6 @@ const agentsRouter = new Elysia({ prefix: "/agents" }) return { status: "cleared" }; }) - .post("/:id/checkout", ({ params: { id }, body }) => { - const agent = getAgent(id); - const { stepId } = body as { stepId: string }; - if (!stepId) throw new Error("stepId is required"); - - const success = agent.checkout(stepId); - if (!success) throw new Error(`Step ${stepId} not found or checkout failed`); - - const allSteps = agent.getAllSteps(); - const workflowContent = agent.getWorkflowState().getWorkflowContent(); - - broadcastToAgentClients(id, new WsServerHeadChangeEvent(stepId, allSteps, workflowContent)); - - return { - status: "checked out", - headId: stepId, - }; - }) - .get("/:id/operator-types", ({ params: { id } }) => { const agent = getAgent(id); const metadataStore = agent.getMetadataStore(); @@ -614,7 +589,7 @@ function printStartupMessage(app: ReturnType) { console.log(" Send: { type: 'WsClientPromptCommand', content: '...' }"); console.log(" Send: { type: 'WsClientStopCommand' }"); console.log( - " Recv: { type: 'WsServerSnapshotEvent' | 'WsServerStepEvent' | 'WsServerStatusEvent' | 'WsServerErrorEvent' | 'WsServerHeadChangeEvent', ... }" + " Recv: { type: 'WsServerSnapshotEvent' | 'WsServerStepEvent' | 'WsServerStatusEvent' | 'WsServerErrorEvent', ... }" ); } diff --git a/agent-service/src/types/ws/server.ts b/agent-service/src/types/ws/server.ts index 019d2a1717a..4fdf4e3a06e 100644 --- a/agent-service/src/types/ws/server.ts +++ b/agent-service/src/types/ws/server.ts @@ -23,7 +23,6 @@ // for you. `WsServerEvent` is their discriminated union. import type { AgentState, ReActStep } from "../agent"; -import type { WorkflowContent } from "../workflow"; /** * Full state pushed once when a client connects: the agent's current lifecycle @@ -60,27 +59,5 @@ export class WsServerErrorEvent { constructor(readonly error: string) {} } -/** - * Emitted after a checkout: HEAD moved, carrying the full step list and the - * workflow snapshot at the new head. - * - * @deprecated Unused by the frontend/UI — nothing invokes the client's - * `checkoutStep()`. The `/agents/:id/checkout` route (and its tests) still - * broadcast it, so it remains reachable; do not build new UI on it. - */ -export class WsServerHeadChangeEvent { - readonly type = "WsServerHeadChangeEvent"; - constructor( - readonly headId: string, - readonly steps: ReActStep[], - readonly workflowContent?: WorkflowContent - ) {} -} - /** Discriminated union of every server -> client frame. */ -export type WsServerEvent = - | WsServerSnapshotEvent - | WsServerStepEvent - | WsServerStatusEvent - | WsServerErrorEvent - | WsServerHeadChangeEvent; +export type WsServerEvent = WsServerSnapshotEvent | WsServerStepEvent | WsServerStatusEvent | WsServerErrorEvent; diff --git a/frontend/src/app/workspace/service/agent/agent.service.ts b/frontend/src/app/workspace/service/agent/agent.service.ts index 76ccbd4b9b4..5e7c254f22c 100644 --- a/frontend/src/app/workspace/service/agent/agent.service.ts +++ b/frontend/src/app/workspace/service/agent/agent.service.ts @@ -519,26 +519,6 @@ export class AgentService { } break; - case "WsServerHeadChangeEvent": - // HEAD moved (checkout) — update HEAD, visible steps, and workflow - if (message.headId !== undefined) { - tracking.headIdSubject.next(message.headId); - } - if (message.steps && Array.isArray(message.steps)) { - const steps = message.steps.map((s: any) => this.convertApiReActStep(s)); - tracking.reActStepsSubject.next(steps); - } - // Update workflow content from agent service (ground truth) - if (message.workflowContent) { - tracking.wsWorkflowActive = true; - const workflow: Workflow = { - ...(message.workflowMetadata || tracking.workflowSubject.getValue() || {}), - content: message.workflowContent, - }; - tracking.workflowSubject.next(workflow as Workflow); - } - break; - case "WsServerErrorEvent": // Error occurred console.error(`Agent ${agentId} error:`, message.error); @@ -1006,14 +986,6 @@ export class AgentService { return tracking ? tracking.headIdSubject.getValue() : null; } - /** - * Checkout to a specific step (move HEAD, restore workflow). - * The backend broadcasts headChange + visible steps via WebSocket to all clients. - */ - public checkoutStep(agentId: string, stepId: string): Observable { - return this.http.post(`${this.AGENT_API_BASE}/agents/${agentId}/checkout`, { stepId }); - } - /** * Get visible steps for an agent (current snapshot). */