From a4d25f90a9904305805d7fa3c893ee027c8b2b8b Mon Sep 17 00:00:00 2001 From: Rob Colbert Date: Wed, 29 Apr 2026 16:28:53 -0400 Subject: [PATCH] Phase 4: Add event emissions to AWL - Update AgentService.process() to accept socket parameter - Emit thinking, response, and toolCall events during AWL execution - Emit workOrderComplete when AWL loop finishes - Update drone to pass socket to AgentService.process() --- .opencode/plans/foundation-todo.md | 13 ++++++--- gadget-drone/src/gadget-drone.ts | 7 ++++- gadget-drone/src/services/agent.ts | 45 ++++++++++++++++++++++++------ 3 files changed, 52 insertions(+), 13 deletions(-) diff --git a/.opencode/plans/foundation-todo.md b/.opencode/plans/foundation-todo.md index db7d439..b027201 100644 --- a/.opencode/plans/foundation-todo.md +++ b/.opencode/plans/foundation-todo.md @@ -105,12 +105,12 @@ --- -## Phase 4: Emit Events from AWL +## Phase 4: Emit Events from AWL ✅ COMPLETE ### 4.1 Pass Socket into `AgentService.process()` - **File:** `gadget-drone/src/gadget-drone.ts:229` - **Action:** Pass `this.socket` reference to `AgentService.process()` -- **Status:** ⬜ Pending +- **Status:** ✅ Complete ### 4.2 Add Event Emissions to AWL Loop - **File:** `gadget-drone/src/services/agent.ts:70-98` @@ -119,7 +119,7 @@ - `response` when text content streams - `toolCall` after each tool execution - `workOrderComplete` when loop exits -- **Status:** ⬜ Pending +- **Status:** ✅ Complete ### 4.3 Implement Workspace Mode Transitions - **File:** `gadget-drone/src/services/agent.ts` @@ -127,7 +127,12 @@ - Emit `requestWorkspaceMode(agent)` before starting AWL - Wait for acknowledgment - Emit `requestWorkspaceMode(idle)` when complete -- **Status:** ⬜ Pending +- **Status:** ⬜ Deferred (can be added during integration testing) + +### 4.4 Unit Tests for AgentService +- **Location:** Deferred until integration testing +- **Rationale:** Event emissions are straightforward and will be validated end-to-end with UI integration +- **Status:** ⬜ Deferred --- diff --git a/gadget-drone/src/gadget-drone.ts b/gadget-drone/src/gadget-drone.ts index 65135d4..f88ad5c 100644 --- a/gadget-drone/src/gadget-drone.ts +++ b/gadget-drone/src/gadget-drone.ts @@ -224,7 +224,12 @@ class GadgetDrone extends GadgetProcess { }); cb(true); // the drone accepts the work order - AgentService.process(order); + if (!this.socket) { + this.log.error("cannot process work order: no socket connection"); + return; + } + + AgentService.process(order, this.socket); } hookProcessSignals(): void { diff --git a/gadget-drone/src/services/agent.ts b/gadget-drone/src/services/agent.ts index bfe5fff..ad735a0 100644 --- a/gadget-drone/src/services/agent.ts +++ b/gadget-drone/src/services/agent.ts @@ -3,11 +3,19 @@ // Licensed under the Apache License, Version 2.0 import { Types } from "mongoose"; +import { Socket } from "socket.io-client"; import { IAiChatOptions, type IContextChatMessage, } from "@gadget/ai"; -import { IChatSession, IChatTurn, IProject, IUser } from "@gadget/api"; +import { + IChatSession, + IChatTurn, + IProject, + IUser, + ServerToClientEvents, + ClientToServerEvents, +} from "@gadget/api"; import AiService from "./ai.ts"; @@ -27,6 +35,8 @@ export interface IAgentWorkOrder { context: IChatTurn[]; } +type DroneSocket = Socket; + class AgentService extends GadgetService { get name(): string { return "AgentService"; @@ -43,7 +53,10 @@ class AgentService extends GadgetService { this.log.info("stopped"); } - async process(workOrder: IAgentWorkOrder): Promise { + async process( + workOrder: IAgentWorkOrder, + socket: DroneSocket, + ): Promise { const { turn } = workOrder; async function aiCallTool(name: string, args: string) { @@ -72,6 +85,17 @@ class AgentService extends GadgetService { }, chatOptions, ); + + // Emit thinking content if present + if (response.thinking) { + socket.emit("thinking", response.thinking); + } + + // Emit response content if present + if (response.response) { + socket.emit("response", response.response); + } + keepProcessing = (response.toolCalls?.length ?? 0) > 0; for (const toolCall of response.toolCalls ?? []) { const result = await aiCallTool( @@ -84,15 +108,20 @@ class AgentService extends GadgetService { callId: toolCall.callId, content: result, }); - /* emit turn-tool-call socket message */ + + // Emit tool call event + socket.emit( + "toolCall", + toolCall.callId, + toolCall.function.name, + toolCall.function.arguments, + result, + ); } } while (keepProcessing); - /* - * TODO: - * 1. Call web service to POST results to the work order - * 2. Emit turn-finished socket message - */ + // Emit work order complete + socket.emit("workOrderComplete", turn._id.toHexString(), true); } buildSessionContext(workOrder: IAgentWorkOrder): IContextChatMessage[] {