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()`
- **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
---

View File

@ -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 {

View File

@ -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<ServerToClientEvents, ClientToServerEvents>;
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<void> {
async process(
workOrder: IAgentWorkOrder,
socket: DroneSocket,
): Promise<void> {
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[] {