@@ -229,49 +296,121 @@ export default function ChatSessionView() {
}
return (
-
- {/* Main Chat Area */}
+
+ {/* Toast notification */}
+ {toast && (
+
+ {toast}
+
+ )}
+
+ {/* Content Area */}
- {/* Messages */}
-
- {messages.map((message) => (
-
- ))}
-
+ {/* Chat View (75%) */}
+
+ {/* Messages */}
+
+ {messages.map((message) => (
+
+ ))}
+
+
+
+ {/* Prompt Input */}
+
+
+
- {/* Prompt Input */}
-
-
- setPromptInput(e.target.value)}
- onKeyDown={(e) => {
- if (e.key === 'Enter' && !e.shiftKey) {
- e.preventDefault();
- handleSubmitPrompt(e);
- }
- }}
- placeholder="Enter your prompt... (Shift+Enter for new line)"
- className="flex-1 px-3 py-2 bg-bg-tertiary border border-border-default rounded text-text-primary focus:border-brand focus:outline-none resize-none"
- rows={3}
- disabled={isProcessing || !sessionLocked}
- />
-
- {isProcessing ? 'Processing...' : 'Send'}
-
-
-
+ {/* Log Panel (25%) */}
+
setLogExpanded(!logExpanded)}
+ />
- {/* Session Sidebar */}
+ {/* Sidebar */}
-
+ {/* SESSION Panel */}
+
+
+
+ Session
+
+
+
+
+
+ ✓
+ {sessionLocked ? 'Locked' : 'Unlocked'}
+
+
+
Name
+
{session?.name}
+
+
+
Model
+
{session?.selectedModel}
+
+
+
+
+ {/* PROJECT Panel */}
+
+
+
+ Project
+
+
+
+ {project ? (
+ <>
+
+
Name
+
{project.name}
+
+
+
Slug
+
{project.slug}
+
+ >
+ ) : (
+
Loading...
+ )}
+
+
+
+ {/* FILES Panel */}
+
);
@@ -336,97 +475,4 @@ function ChatMessageBubble({ message }: { message: ChatMessage }) {
);
-}
-
-function SessionSidebar({ session, project, sessionLocked }: {
- session: ChatSession | null;
- project: Project | null;
- sessionLocked: boolean;
-}) {
- if (!session) {
- return (
-
- );
- }
-
- return (
-
-
-
- Session
-
-
-
-
Name
-
{session.name}
-
-
-
ID
-
- {session._id}
-
-
-
-
Mode
-
{session.mode}
-
-
-
Status
-
- ✓
- {sessionLocked ? 'Locked' : 'Unlocked'}
-
-
-
-
-
-
-
- Model
-
-
- {session.selectedModel}
-
-
- Provider: {typeof session.provider === 'string' ? 'Loaded' : session.provider?.name}
-
-
-
-
-
- Stats
-
-
-
-
TC
-
{session.stats.toolCallCount}
-
-
-
-
-
-
-
-
- Project
-
- {project ? (
-
-
{project.name}
-
{project.slug}
-
- ) : (
-
Loading...
- )}
-
-
- );
-}
+}
\ No newline at end of file
diff --git a/gadget-code/src/lib/code-session.ts b/gadget-code/src/lib/code-session.ts
index 126acf6..c2d1929 100644
--- a/gadget-code/src/lib/code-session.ts
+++ b/gadget-code/src/lib/code-session.ts
@@ -15,6 +15,7 @@ import {
ChatTurnStatus,
GadgetId,
ChatTurnDocument,
+ WorkspaceMode,
} from "@gadget/api";
import { ChatSessionService, SocketService } from "../services/index.ts";
@@ -26,6 +27,7 @@ export class CodeSession extends SocketSession {
protected chatSession: IChatSession | undefined;
protected selectedDrone: IDroneRegistration | undefined;
protected currentTurnId: GadgetId | undefined;
+ protected workspaceMode: WorkspaceMode = WorkspaceMode.Idle;
constructor(socket: GadgetSocket, user: IUser) {
super(socket, user);
@@ -81,6 +83,7 @@ export class CodeSession extends SocketSession {
this.project = project;
SocketService.registerChatSession(chatSession._id, this);
droneSession.setChatSessionId(chatSession._id);
+ droneSession.setCodeSession(this);
}
cb(success, chatSessionId);
},
@@ -142,4 +145,13 @@ export class CodeSession extends SocketSession {
},
);
}
+
+ /**
+ * Called by DroneSession when the drone emits a workspace mode change.
+ * Updates local state and forwards to the IDE socket.
+ */
+ onWorkspaceModeChanged(mode: WorkspaceMode): void {
+ this.workspaceMode = mode;
+ this.socket.emit("workspaceModeChanged", mode);
+ }
}
diff --git a/gadget-code/src/lib/drone-session.ts b/gadget-code/src/lib/drone-session.ts
index e700383..b19bdda 100644
--- a/gadget-code/src/lib/drone-session.ts
+++ b/gadget-code/src/lib/drone-session.ts
@@ -7,6 +7,7 @@ import {
IDroneRegistration,
ChatTurnStatus,
GadgetId,
+ WorkspaceMode,
} from "@gadget/api";
import {
GadgetSocket,
@@ -21,6 +22,8 @@ export class DroneSession extends SocketSession {
registration: IDroneRegistration;
chatSessionId: GadgetId | undefined;
currentTurnId: GadgetId | undefined;
+ workspaceMode: WorkspaceMode = WorkspaceMode.Idle;
+ codeSession: import("./code-session.js").CodeSession | undefined;
constructor(socket: GadgetSocket, registration: IDroneRegistration) {
super(socket, registration.user as IUser);
@@ -39,6 +42,7 @@ export class DroneSession extends SocketSession {
this.onRequestCrashRecovery.bind(this),
);
this.socket.on("requestTermination", this.onRequestTermination.bind(this));
+ this.socket.on("workspaceModeChanged", this.onWorkspaceModeChanged.bind(this));
}
/**
@@ -179,6 +183,25 @@ export class DroneSession extends SocketSession {
this.currentTurnId = turnId;
}
+ /**
+ * Sets the associated code session for routing events back to the IDE.
+ */
+ setCodeSession(codeSession: import("./code-session.js").CodeSession): void {
+ this.codeSession = codeSession;
+ }
+
+ /**
+ * Called when the drone emits a workspace mode change.
+ */
+ async onWorkspaceModeChanged(mode: WorkspaceMode): Promise
{
+ this.workspaceMode = mode;
+ this.log.info("workspace mode changed", { mode });
+
+ if (this.codeSession) {
+ this.codeSession.onWorkspaceModeChanged(mode);
+ }
+ }
+
/**
* Called when the drone requests crash recovery for an incomplete work order.
*/
diff --git a/gadget-drone/src/gadget-drone.ts b/gadget-drone/src/gadget-drone.ts
index ac2082b..9183136 100644
--- a/gadget-drone/src/gadget-drone.ts
+++ b/gadget-drone/src/gadget-drone.ts
@@ -313,6 +313,7 @@ class GadgetDrone extends GadgetProcess {
});
if (this.workspaceMode === WorkspaceMode.Idle) {
this.workspaceMode = mode;
+ this.socket!.emit("workspaceModeChanged", this.workspaceMode);
return cb(true, this.workspaceMode);
}
return cb(false, this.workspaceMode);
diff --git a/packages/api/src/messages/drone.ts b/packages/api/src/messages/drone.ts
index 1156f52..834ebc6 100644
--- a/packages/api/src/messages/drone.ts
+++ b/packages/api/src/messages/drone.ts
@@ -6,6 +6,7 @@ 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";
+import { WorkspaceMode } from "./ide.ts";
export type ProcessWorkOrderCallback = (
success: boolean,
@@ -49,3 +50,5 @@ export type CrashRecoveryResponseMessage = (data: {
export type RequestTerminationMessage = (
cb: (success: boolean) => void,
) => void;
+
+export type WorkspaceModeChangedMessage = (mode: WorkspaceMode) => void;
diff --git a/packages/api/src/messages/socket.ts b/packages/api/src/messages/socket.ts
index c7ac222..3e1a863 100644
--- a/packages/api/src/messages/socket.ts
+++ b/packages/api/src/messages/socket.ts
@@ -11,6 +11,7 @@ import {
RequestCrashRecoveryMessage,
CrashRecoveryResponseMessage,
RequestTerminationMessage,
+ WorkspaceModeChangedMessage,
} from "./drone.ts";
import {
RequestSessionLockMessage,
@@ -55,6 +56,7 @@ export interface ClientToServerEvents {
workOrderComplete: WorkOrderCompleteMessage;
requestCrashRecovery: RequestCrashRecoveryMessage;
requestTermination: RequestTerminationMessage;
+ workspaceModeChanged: WorkspaceModeChangedMessage;
}
export interface ServerToClientEvents {
@@ -76,6 +78,7 @@ export interface ServerToClientEvents {
response: ResponseMessage;
toolCall: ToolCallMessage;
workOrderComplete: WorkOrderCompleteMessage;
+ workspaceModeChanged: WorkspaceModeChangedMessage;
}
export interface SocketData {