Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 1 addition & 11 deletions agent-service/src/agent/texera-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}
Expand Down
26 changes: 0 additions & 26 deletions agent-service/src/server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
29 changes: 2 additions & 27 deletions agent-service/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, TexeraAgent>();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -614,7 +589,7 @@ function printStartupMessage(app: ReturnType<typeof buildApp>) {
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', ... }"
);
}

Expand Down
25 changes: 1 addition & 24 deletions agent-service/src/types/ws/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
28 changes: 0 additions & 28 deletions frontend/src/app/workspace/service/agent/agent.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<any> {
return this.http.post(`${this.AGENT_API_BASE}/agents/${agentId}/checkout`, { stepId });
}

/**
* Get visible steps for an agent (current snapshot).
*/
Expand Down
Loading