more progress along ChatTurn processing

The agent has been failing and failing and failing, so I:
1. Swapped models
2. Did some by-hand enhancements
3. Set this checkpoint for continuing
This commit is contained in:
Rob Colbert 2026-05-05 14:34:52 -04:00
parent b94fe24287
commit cb73d276a3
8 changed files with 86 additions and 32 deletions

View File

@ -80,9 +80,21 @@ const ChatTurn = memo(function ChatTurn({ turn, onUpdate }: ChatTurnProps) {
)} )}
</div> </div>
{/* Error Alert */}
{turn.errorMessage && turn.status === "error" && (
<div className="max-w-[80%] ml-0 mb-4">
<div className="bg-red-600 text-white rounded p-4 border-2 border-red-400">
<div className="text-sm font-semibold mb-2 flex items-center gap-2">
<span></span> Error
</div>
<div className="whitespace-pre-wrap">{turn.errorMessage}</div>
</div>
</div>
)}
{/* User Prompt */} {/* User Prompt */}
<div className="max-w-[80%] ml-0 mb-4"> <div className="max-w-[80%] ml-0 mb-4">
<div className="bg-brand text-white rounded p-4"> <div className="bg-[#4a0000] text-white rounded p-4">
<div className="text-sm font-semibold mb-2">You</div> <div className="text-sm font-semibold mb-2">You</div>
<div className="whitespace-pre-wrap">{turn.prompts?.user || ""}</div> <div className="whitespace-pre-wrap">{turn.prompts?.user || ""}</div>
</div> </div>

View File

@ -311,6 +311,7 @@ export interface ChatTurn {
prompts: ChatTurnPrompts; prompts: ChatTurnPrompts;
thinking?: string; thinking?: string;
response?: string; response?: string;
errorMessage?: string;
toolCalls: Array<{ toolCalls: Array<{
callId: string; callId: string;
name: string; name: string;

View File

@ -19,7 +19,6 @@ interface LogEntry {
export default function ChatSessionView() { export default function ChatSessionView() {
const { projectId, sessionId } = useParams<{ projectId: string; sessionId: string }>(); const { projectId, sessionId } = useParams<{ projectId: string; sessionId: string }>();
const navigate = useNavigate(); const navigate = useNavigate();
const socket = socketClient.socket;
const appContext = useContext(AppContext); const appContext = useContext(AppContext);
const [project, setProject] = useState<Project | null>(null); const [project, setProject] = useState<Project | null>(null);
@ -91,27 +90,23 @@ export default function ChatSessionView() {
}; };
const setupSocketListeners = () => { const setupSocketListeners = () => {
if (!socket) return; socketClient.on('thinking', handleThinking);
socketClient.on('response', handleResponse);
socket.on('thinking', handleThinking); socketClient.on('toolCall', handleToolCall);
socket.on('response', handleResponse); socketClient.on('workOrderComplete', handleWorkOrderComplete);
socket.on('toolCall', handleToolCall); socketClient.on('workspaceModeChanged', handleWorkspaceModeChanged);
socket.on('workOrderComplete', handleWorkOrderComplete); socketClient.on('log:entry', handleLogEntry);
socket.on('workspaceModeChanged', handleWorkspaceModeChanged); socketClient.on('status', handleStatus);
socket.on('log:entry', handleLogEntry);
socket.on('status', handleStatus);
}; };
const cleanupSocketListeners = () => { const cleanupSocketListeners = () => {
if (!socket) return; socketClient.off('thinking', handleThinking);
socketClient.off('response', handleResponse);
socket.off('thinking', handleThinking); socketClient.off('toolCall', handleToolCall);
socket.off('response', handleResponse); socketClient.off('workOrderComplete', handleWorkOrderComplete);
socket.off('toolCall', handleToolCall); socketClient.off('workspaceModeChanged', handleWorkspaceModeChanged);
socket.off('workOrderComplete', handleWorkOrderComplete); socketClient.off('log:entry', handleLogEntry);
socket.off('workspaceModeChanged', handleWorkspaceModeChanged); socketClient.off('status', handleStatus);
socket.off('log:entry', handleLogEntry);
socket.off('status', handleStatus);
}; };
const scheduleUpdate = useCallback(() => { const scheduleUpdate = useCallback(() => {
@ -194,7 +189,7 @@ export default function ChatSessionView() {
setTurns(prevTurns => setTurns(prevTurns =>
prevTurns.map(turn => prevTurns.map(turn =>
turn._id === turnId turn._id === turnId
? { ...turn, status: success ? 'finished' : 'error', response: message && !success ? message : turn.response } ? { ...turn, status: success ? 'finished' : 'error', errorMessage: message && !success ? message : turn.errorMessage }
: turn : turn
) )
); );
@ -264,7 +259,7 @@ export default function ChatSessionView() {
const handleSubmitPrompt = async (e: React.FormEvent) => { const handleSubmitPrompt = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
if (!promptInput.trim() || isProcessing || !socket || !sessionLocked) return; if (!promptInput.trim() || isProcessing || !socketClient.connected || !sessionLocked) return;
if (workspaceMode !== WorkspaceMode.Agent) return; if (workspaceMode !== WorkspaceMode.Agent) return;
const userTurn: ChatTurn = { const userTurn: ChatTurn = {

View File

@ -16,6 +16,7 @@ import {
GadgetId, GadgetId,
ChatTurnDocument, ChatTurnDocument,
WorkspaceMode, WorkspaceMode,
SubmitPromptCallback,
} from "@gadget/api"; } from "@gadget/api";
import { ChatSessionService, SocketService } from "../services/index.ts"; import { ChatSessionService, SocketService } from "../services/index.ts";
@ -134,18 +135,21 @@ export class CodeSession extends SocketSession {
* Called when the IDE submits a prompt to be processed by the agent. * Called when the IDE submits a prompt to be processed by the agent.
* Creates a ChatTurn document and sends a work order to the selected drone. * Creates a ChatTurn document and sends a work order to the selected drone.
*/ */
async onSubmitPrompt(content: string): Promise<void> { async onSubmitPrompt(
content: string,
cb: SubmitPromptCallback,
): Promise<void> {
if (!this.selectedDrone) { if (!this.selectedDrone) {
this.log.warn("prompt rejected: no drone selected"); this.log.warn("prompt rejected: no drone selected");
throw new Error("No drone selected"); return cb(false, { message: "No drone selected" });
} }
if (!this.chatSession) { if (!this.chatSession) {
this.log.warn("prompt rejected: no chat session active"); this.log.warn("prompt rejected: no chat session active");
throw new Error("No chat session active"); return cb(false, { message: "No chat session active" });
} }
if (!this.project) { if (!this.project) {
this.log.warn("prompt rejected: no project selected"); this.log.warn("prompt rejected: no project selected");
throw new Error("No project selected"); return cb(false, { message: "No project selected" });
} }
const droneSession = SocketService.getDroneSession(this.selectedDrone); const droneSession = SocketService.getDroneSession(this.selectedDrone);
@ -162,6 +166,7 @@ export class CodeSession extends SocketSession {
}); });
droneSession.setCurrentTurnId(turn._id); droneSession.setCurrentTurnId(turn._id);
cb(true, { turnId: turn._id, message: "turn created successfully" });
droneSession.socket.emit( droneSession.socket.emit(
"processWorkOrder", "processWorkOrder",
@ -198,4 +203,29 @@ export class CodeSession extends SocketSession {
onStatus(content: string): void { onStatus(content: string): void {
this.socket.emit("status", content); this.socket.emit("status", content);
} }
onThinking(content: string): void {
this.socket.emit("thinking", content);
}
onResponse(content: string): void {
this.socket.emit("response", content);
}
onToolCall(
callId: string,
name: string,
params: string,
response: string,
): void {
this.socket.emit("toolCall", callId, name, params, response);
}
onWorkOrderComplete(
turnId: string,
success: boolean,
message?: string,
): void {
this.socket.emit("workOrderComplete", turnId, success, message);
}
} }

View File

@ -82,7 +82,7 @@ export class DroneSession extends SocketSession {
const codeSession = SocketService.getCodeSessionByChatSessionId( const codeSession = SocketService.getCodeSessionByChatSessionId(
this.chatSessionId, this.chatSessionId,
); );
codeSession.socket.emit("thinking", content); codeSession.onThinking(content);
if (this.currentTurnId) { if (this.currentTurnId) {
await ChatTurn.findByIdAndUpdate(this.currentTurnId, { await ChatTurn.findByIdAndUpdate(this.currentTurnId, {
@ -107,7 +107,7 @@ export class DroneSession extends SocketSession {
const codeSession = SocketService.getCodeSessionByChatSessionId( const codeSession = SocketService.getCodeSessionByChatSessionId(
this.chatSessionId, this.chatSessionId,
); );
codeSession.socket.emit("response", content); codeSession.onResponse(content);
if (this.currentTurnId) { if (this.currentTurnId) {
await ChatTurn.findByIdAndUpdate(this.currentTurnId, { await ChatTurn.findByIdAndUpdate(this.currentTurnId, {
@ -137,7 +137,7 @@ export class DroneSession extends SocketSession {
const codeSession = SocketService.getCodeSessionByChatSessionId( const codeSession = SocketService.getCodeSessionByChatSessionId(
this.chatSessionId, this.chatSessionId,
); );
codeSession.socket.emit("toolCall", callId, name, params, response); codeSession.onToolCall(callId, name, params, response);
if (this.currentTurnId) { if (this.currentTurnId) {
const turn = await ChatTurn.findById(this.currentTurnId); const turn = await ChatTurn.findById(this.currentTurnId);
@ -177,7 +177,7 @@ export class DroneSession extends SocketSession {
if (turn) { if (turn) {
turn.status = success ? ChatTurnStatus.Finished : ChatTurnStatus.Error; turn.status = success ? ChatTurnStatus.Finished : ChatTurnStatus.Error;
if (!success && message) { if (!success && message) {
turn.response = message; turn.errorMessage = message;
} }
await turn.save(); await turn.save();
} }
@ -185,7 +185,7 @@ export class DroneSession extends SocketSession {
const codeSession = SocketService.getCodeSessionByChatSessionId( const codeSession = SocketService.getCodeSessionByChatSessionId(
this.chatSessionId, this.chatSessionId,
); );
codeSession.socket.emit("workOrderComplete", turnId, success, message); codeSession.onWorkOrderComplete(turnId, success, message);
this.currentTurnId = undefined; this.currentTurnId = undefined;
} catch (error) { } catch (error) {

View File

@ -67,6 +67,7 @@ export const ChatTurnSchema = new Schema<IChatTurn>({
prompts: { type: ChatTurnPromptsSchema, required: true }, prompts: { type: ChatTurnPromptsSchema, required: true },
thinking: { type: String, required: false }, thinking: { type: String, required: false },
response: { type: String, required: false }, response: { type: String, required: false },
errorMessage: { type: String, required: false },
toolCalls: { type: [ChatToolCallSchema], default: [], required: true }, toolCalls: { type: [ChatToolCallSchema], default: [], required: true },
subagents: { type: [ChatSubagentProcessSchema], default: [], required: true }, subagents: { type: [ChatSubagentProcessSchema], default: [], required: true },
stats: { stats: {

View File

@ -65,6 +65,7 @@ export interface IChatTurn {
prompts: IChatTurnPrompts; prompts: IChatTurnPrompts;
thinking?: string; thinking?: string;
response?: string; response?: string;
errorMessage?: string;
toolCalls: IChatToolCall[]; toolCalls: IChatToolCall[];
subagents: IChatSubagentProcess[]; // subagents used while processing this turn subagents: IChatSubagentProcess[]; // subagents used while processing this turn
stats: IChatTurnStats; stats: IChatTurnStats;

View File

@ -5,6 +5,7 @@
import { IChatSession } from "../interfaces/chat-session.ts"; import { IChatSession } from "../interfaces/chat-session.ts";
import { IDroneRegistration } from "../interfaces/drone-registration.ts"; import { IDroneRegistration } from "../interfaces/drone-registration.ts";
import { IProject } from "../interfaces/project.ts"; import { IProject } from "../interfaces/project.ts";
import { GadgetId } from "../lib/gadget-id.ts";
/* /*
* requestSessionLock * requestSessionLock
@ -51,4 +52,17 @@ export type RequestWorkspaceModeMessage = (
* submitPrompt * submitPrompt
*/ */
export type SubmitPromptMessage = (prompt: string) => void; export interface SubmitPromptCallbackData {
turnId?: GadgetId;
message?: string;
}
export type SubmitPromptCallback = (
success: boolean,
data: SubmitPromptCallbackData,
) => void;
export type SubmitPromptMessage = (
prompt: string,
cb: SubmitPromptCallback,
) => void;