more documentation and progress working towards a usable socket protocol

This commit is contained in:
Rob Colbert 2026-04-29 15:23:03 -04:00
parent c88081b2ba
commit 2314a61efe
8 changed files with 162 additions and 33 deletions

View File

@ -21,12 +21,28 @@ Socket.IO is used to connect these components and exchange messages in real-time
b. The drone synchronizes the project into the workspace.
c. The drone follows the project's instructions for configuring it for development/testing/production.
d. The drone grants the IDE the requested session lock.
6. gadget-code:ide navigates in the Chat Session view.
6. gadget-code:ide navigates to the Chat Session view.
The User can now begin entering prompts and editing files.
### Editing A File
When in the Chat Session view (see: []()), the User can select a file from the Files pane to open it in the File Editor. The mode of the File Editor depends on what's happening in the IDE at that time.
When in the Chat Session view (see: [UI Design & Style Guide](../gadget-code/docs/ui-design-guide.md)), the User can select a file from the Files pane to open it in the File Editor. The mode of the File Editor depends on what's happening in the IDE at that time.
If there is a prompt work order being processed, the File Editor is read-only. The User can browse and scroll through the contents of the file and see all metadata about the file. The information and file content displayed will update as the gadget-drone process is doing it's work. But, the User is not permitted to make any changes to the displayed file content, or save the file.
If the workspace is idle (no prompt is executing), then the File Editor will open in read-write mode. The User will be able to edit the file (using the ACE editor). The User will be able to save the file. The User cannot enter a prompt or create a prompt work order while the session is in file edit mode. The user must close the File Editor and return to idle
## Protocol Sequences
### requestSessionLock
IDE:requestSessionLock => gadget-code:web => gadget-drone => gadget-code:web => IDE
The IDE initiates `requestSessionLock` when it wants to start working with a Chat Session. The event is received by the web server, which selects the drone for which it is intended based on the supplied DroneRegistration.\_id. The web server sends the message to the drone, which process it and returns the result to the web server. The web server sends the message back to the IDE.
### requestWorkspaceMode
IDE:requestWorkspaceMode => gadget-code:web => gadget-drone => gadget-code:web => IDE
The IDE initiates `requestWorkspaceMode` when it wants to request the User mode so the User can begin editing files and making changes within the project. The event is received by the web server, which sends the message to the drone. The drone processes the request and returns the result to the web server. The web server sends the message back to the IDE.

View File

@ -19,10 +19,14 @@ import { GadgetProcess } from "./lib/process.ts";
import {
ClientToServerEvents,
IChatSession,
IChatTurn,
IDroneRegistration,
IProject,
ProcessWorkOrderCallback,
RequestSessionLockCallback,
RequestWorkspaceModeCallback,
ServerToClientEvents,
WorkspaceMode,
} from "@gadget/api";
interface UserCredentials {
@ -30,13 +34,6 @@ interface UserCredentials {
password: string;
}
enum WorkspaceMode {
Idle = "idle",
Syncing = "syncing",
User = "user",
Agent = "agent",
}
type ClientSocket = Socket<ServerToClientEvents, ClientToServerEvents>;
class GadgetDrone extends GadgetProcess {
@ -160,6 +157,11 @@ class GadgetDrone extends GadgetProcess {
"requestSessionLock",
this.onRequestSessionLock.bind(this),
);
this.socket.on(
"requestWorkspaceMode",
this.onRequestWorkspaceMode.bind(this),
);
this.socket.on("processWorkOrder", this.onProcessWorkOrder.bind(this));
});
}
@ -169,7 +171,11 @@ class GadgetDrone extends GadgetProcess {
chatSession: IChatSession,
cb: RequestSessionLockCallback,
) {
this.log.info(`requestSessionLock`, { registration, project, chatSession });
this.log.info("requestSessionLock received", {
registration,
project,
chatSession,
});
if (!this.registration) {
return cb(false, "not registered");
}
@ -181,6 +187,48 @@ class GadgetDrone extends GadgetProcess {
cb(true, chatSession._id.toHexString());
}
async onRequestWorkspaceMode(
registration: IDroneRegistration,
project: IProject,
chatSession: IChatSession,
mode: WorkspaceMode,
cb: RequestWorkspaceModeCallback,
) {
this.log.info("requestWorkspaceMode received", {
registration,
project,
chatSession,
});
if (this.workspaceMode === WorkspaceMode.Idle) {
this.workspaceMode = mode;
return cb(true, this.workspaceMode);
}
return cb(false, this.workspaceMode);
}
async onProcessWorkOrder(
registration: IDroneRegistration,
project: IProject,
chatSession: IChatSession,
turn: IChatTurn,
cb: ProcessWorkOrderCallback,
) {
const order: IAgentWorkOrder = {
createdAt: turn.createdAt,
context: [],
turn,
};
this.log.info("processWorkOrder received", {
registration,
project,
chatSession,
turn,
});
cb(true); // the drone accepts the work order
AgentService.process(order);
}
hookProcessSignals(): void {
process.title = this.name;

View File

@ -23,11 +23,8 @@ export interface IToolCall {
export interface IAgentWorkOrder {
createdAt: Date;
project: IProject;
provider: IAiProvider;
session: IChatSession;
context: IChatTurn[];
turn: IChatTurn;
context: IChatTurn[];
}
class AgentService extends GadgetService {
@ -47,15 +44,15 @@ class AgentService extends GadgetService {
}
async process(workOrder: IAgentWorkOrder): Promise<void> {
const { project, provider, session, turn } = workOrder;
const { turn } = workOrder;
async function aiCallTool(name: string, args: string) {
return "[all tool calls are stubbed out]";
}
const modelConfig = {
provider: workOrder.provider,
modelId: workOrder.turn.llm,
provider: turn.provider,
modelId: turn.llm,
params: {
reasoning: false,
temperature: 0.8,
@ -66,15 +63,15 @@ class AgentService extends GadgetService {
const context = this.buildSessionContext(workOrder);
const chatOptions: IAiChatOptions = {
systemPrompt: workOrder.turn.prompts.system,
systemPrompt: turn.prompts.system,
context,
userPrompt: workOrder.turn.prompts.user,
userPrompt: turn.prompts.user,
};
let keepProcessing = true;
do {
const response = await AiService.chat(
workOrder.provider,
turn.provider,
modelConfig,
chatOptions,
);
@ -102,7 +99,7 @@ class AgentService extends GadgetService {
}
buildSessionContext(workOrder: IAgentWorkOrder): IContextChatMessage[] {
const user: IUser = workOrder.session.user as IUser;
const user: IUser = workOrder.turn.session.user as IUser;
const messages: IContextChatMessage[] = [];
for (const turn of workOrder.context) {

View File

@ -19,7 +19,6 @@ const provider = {
sdk: "ollama",
baseUrl: "http://localhost:11434",
apiKey: "",
defaultModelId: "llama3.2",
};
const modelConfig = {

View File

@ -9,7 +9,6 @@ export interface IAiProvider {
sdk: AiSdkType;
baseUrl: string;
apiKey: string;
defaultModelId?: string;
}
export interface IAiModelConfig {

View File

@ -2,6 +2,23 @@
// Copyright (C) 2026 Rob Colbert <rob.colbert@openplatform.us>
// Licensed under the Apache License, Version 2.0
import { IChatSession } from "../interfaces/chat-session.ts";
import { IChatTurn } from "../interfaces/chat-turn.ts";
import { IDroneRegistration } from "../interfaces/drone-registration.ts";
import { IProject } from "../interfaces/project.ts";
export type ProcessWorkOrderCallback = (
success: boolean,
message?: string,
) => void;
export type ProcessWorkOrderMessage = (
registration: IDroneRegistration,
project: IProject,
chatSession: IChatSession,
turn: IChatTurn,
cb: ProcessWorkOrderCallback,
) => void;
export type ThinkingMessage = (content: string) => void;
export type ResponseMessage = (content: string) => void;
@ -11,3 +28,9 @@ export type ToolCallMessage = (
params: string,
response: string,
) => void;
export type WorkOrderCompleteMessage = (
workOrderId: string,
success: boolean,
message?: string,
) => void;

View File

@ -6,6 +6,10 @@ import { IChatSession } from "../interfaces/chat-session.ts";
import { IDroneRegistration } from "../interfaces/drone-registration.ts";
import { IProject } from "../interfaces/project.ts";
/*
* requestSessionLock
*/
export type RequestSessionLockCallback = (
success: boolean,
chatSessionId: string,
@ -18,4 +22,32 @@ export type RequestSessionLockMessage = (
cb: RequestSessionLockCallback,
) => void;
/*
* requestWorkspaceMode
*/
export enum WorkspaceMode {
Idle = "idle",
Syncing = "syncing",
User = "user",
Agent = "agent",
}
export type RequestWorkspaceModeCallback = (
success: boolean,
mode: WorkspaceMode,
) => void;
export type RequestWorkspaceModeMessage = (
registration: IDroneRegistration,
project: IProject,
chatSession: IChatSession,
mode: WorkspaceMode,
cb: RequestWorkspaceModeCallback,
) => void;
/*
* submitPrompt
*/
export type SubmitPromptMessage = (prompt: string) => void;

View File

@ -2,11 +2,21 @@
// Copyright (C) 2026 Rob Colbert <rob.colbert@openplatform.us>
// Licensed under the Apache License, Version 2.0
import { ResponseMessage, ThinkingMessage, ToolCallMessage } from "./drone.ts";
import { RequestSessionLockMessage, SubmitPromptMessage } from "./ide.ts";
import {
ProcessWorkOrderMessage,
ResponseMessage,
ThinkingMessage,
ToolCallMessage,
WorkOrderCompleteMessage,
} from "./drone.ts";
import {
RequestSessionLockMessage,
RequestWorkspaceModeMessage,
SubmitPromptMessage,
} from "./ide.ts";
/*
There are two different kinds of clients that connect to the gadget-code
There are two different kinds of clients that connect to the gadget-code:web
Socket.IO server:
1. The gadget-code:ide (ReactJS front-end)
@ -23,31 +33,36 @@ and serve as a remote control surface for one or more gadget-drone processes
running work orders on projects in chat sessions.
*/
export interface ServerToClientEvents {
export interface ClientToServerEvents {
/*
* gadget-code:ide => gadget-code:web => gadget-drone
* gadget-code:ide => gadget-code:web
*/
requestSessionLock: RequestSessionLockMessage;
requestWorkspaceMode: RequestWorkspaceModeMessage;
submitPrompt: SubmitPromptMessage;
/*
* gadget-drone => gadget-code => gadget-code:ide
* gadget-drone => gadget-code:web
*/
thinking: ThinkingMessage;
response: ResponseMessage;
toolCall: ToolCallMessage;
workOrderComplete: WorkOrderCompleteMessage;
}
export interface ClientToServerEvents {
export interface ServerToClientEvents {
/*
* gadget-code:ide => gadget-code => gadget-drone
* gadget-code:web => gadget-drone
*/
requestSessionLock: RequestSessionLockMessage;
submitPrompt: SubmitPromptMessage;
requestWorkspaceMode: RequestWorkspaceModeMessage;
processWorkOrder: ProcessWorkOrderMessage;
/*
* gadget-drone => gadget-code => gadget-code:ide
* gadget-code:web => gadget-code:ide
*/
thinking: ThinkingMessage;