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()
This commit is contained in:
Rob Colbert 2026-04-29 16:28:53 -04:00
parent 92d19a648c
commit a4d25f90a9
3 changed files with 52 additions and 13 deletions

View File

@ -105,12 +105,12 @@
--- ---
## Phase 4: Emit Events from AWL ## Phase 4: Emit Events from AWL ✅ COMPLETE
### 4.1 Pass Socket into `AgentService.process()` ### 4.1 Pass Socket into `AgentService.process()`
- **File:** `gadget-drone/src/gadget-drone.ts:229` - **File:** `gadget-drone/src/gadget-drone.ts:229`
- **Action:** Pass `this.socket` reference to `AgentService.process()` - **Action:** Pass `this.socket` reference to `AgentService.process()`
- **Status:** ⬜ Pending - **Status:** ✅ Complete
### 4.2 Add Event Emissions to AWL Loop ### 4.2 Add Event Emissions to AWL Loop
- **File:** `gadget-drone/src/services/agent.ts:70-98` - **File:** `gadget-drone/src/services/agent.ts:70-98`
@ -119,7 +119,7 @@
- `response` when text content streams - `response` when text content streams
- `toolCall` after each tool execution - `toolCall` after each tool execution
- `workOrderComplete` when loop exits - `workOrderComplete` when loop exits
- **Status:** ⬜ Pending - **Status:** ✅ Complete
### 4.3 Implement Workspace Mode Transitions ### 4.3 Implement Workspace Mode Transitions
- **File:** `gadget-drone/src/services/agent.ts` - **File:** `gadget-drone/src/services/agent.ts`
@ -127,7 +127,12 @@
- Emit `requestWorkspaceMode(agent)` before starting AWL - Emit `requestWorkspaceMode(agent)` before starting AWL
- Wait for acknowledgment - Wait for acknowledgment
- Emit `requestWorkspaceMode(idle)` when complete - 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
--- ---

View File

@ -224,7 +224,12 @@ class GadgetDrone extends GadgetProcess {
}); });
cb(true); // the drone accepts the work order 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 { hookProcessSignals(): void {

View File

@ -3,11 +3,19 @@
// Licensed under the Apache License, Version 2.0 // Licensed under the Apache License, Version 2.0
import { Types } from "mongoose"; import { Types } from "mongoose";
import { Socket } from "socket.io-client";
import { import {
IAiChatOptions, IAiChatOptions,
type IContextChatMessage, type IContextChatMessage,
} from "@gadget/ai"; } 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"; import AiService from "./ai.ts";
@ -27,6 +35,8 @@ export interface IAgentWorkOrder {
context: IChatTurn[]; context: IChatTurn[];
} }
type DroneSocket = Socket<ServerToClientEvents, ClientToServerEvents>;
class AgentService extends GadgetService { class AgentService extends GadgetService {
get name(): string { get name(): string {
return "AgentService"; return "AgentService";
@ -43,7 +53,10 @@ class AgentService extends GadgetService {
this.log.info("stopped"); this.log.info("stopped");
} }
async process(workOrder: IAgentWorkOrder): Promise<void> { async process(
workOrder: IAgentWorkOrder,
socket: DroneSocket,
): Promise<void> {
const { turn } = workOrder; const { turn } = workOrder;
async function aiCallTool(name: string, args: string) { async function aiCallTool(name: string, args: string) {
@ -72,6 +85,17 @@ class AgentService extends GadgetService {
}, },
chatOptions, 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; keepProcessing = (response.toolCalls?.length ?? 0) > 0;
for (const toolCall of response.toolCalls ?? []) { for (const toolCall of response.toolCalls ?? []) {
const result = await aiCallTool( const result = await aiCallTool(
@ -84,15 +108,20 @@ class AgentService extends GadgetService {
callId: toolCall.callId, callId: toolCall.callId,
content: result, 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); } while (keepProcessing);
/* // Emit work order complete
* TODO: socket.emit("workOrderComplete", turn._id.toHexString(), true);
* 1. Call web service to POST results to the work order
* 2. Emit turn-finished socket message
*/
} }
buildSessionContext(workOrder: IAgentWorkOrder): IContextChatMessage[] { buildSessionContext(workOrder: IAgentWorkOrder): IContextChatMessage[] {