Prompt is too long

This commit is contained in:
Mohamed Boudra
2025-10-25 15:14:44 +02:00
parent 3115c702bf
commit 64d1518a6f
10 changed files with 1982 additions and 1689 deletions
-1
View File
@@ -1 +0,0 @@
.claude/CLAUDE.md
+48
View File
@@ -0,0 +1,48 @@
# Agent Lifecycle
This project uses client-driven lazy initialization for Claude Code agents. Sessions start without spinning up agent runtimes; the client opts in when needed.
## Connection Bootstrapping
- The session emits `session_state` after connection with an array of agents. Each entry matches the server `AgentInfo` structure (`id`, `status`, `createdAt`, `type`, `sessionId`, `error`, `currentModeId`, `availableModes`, `title`, `cwd`).
- No history or runtime is loaded at this point. Every agent starts in `uninitialized` and only streams live status updates once subscribed.
## Opting In To An Agent
1. When the UI needs an agent, send an inbound session message:
```json
{
"type": "initialize_agent_request",
"agentId": "<agent-id>",
"requestId": "<optional-correlation-id>"
}
```
2. The session subscribes to the agent, calls `AgentManager.initializeAgentAndGetHistory`, and lazily starts the runtime if required.
3. The server responds with:
```json
{
"type": "agent_initialized",
"payload": {
"agentId": "<agent-id>",
"info": { /* same shape as session_state agents */ },
"updates": [
{
"agentId": "<agent-id>",
"timestamp": "2024-01-01T00:00:00.000Z",
"notification": { /* AgentNotification */ }
}
],
"requestId": "<optional-correlation-id>"
}
}
```
4. After the response, all ongoing traffic uses the existing channels:
- `agent_status` continues to broadcast status changes.
- `agent_update` streams real-time session notifications, permission prompts, etc.
## Notes
- `initialize_agent_request` is idempotent. The manager short-circuits if the agent is already ready and returns the cached history.
- Session state no longer replays history when clients reconnect; clients must explicitly reinitialize agents they care about.
- `requestId` lets the caller correlate UI state but remains optional throughout the flow.
+1589 -1539
View File
File diff suppressed because it is too large Load Diff
+8 -1
View File
@@ -16,7 +16,7 @@ export default function AgentScreen() {
const { theme } = useUnistyles(); const { theme } = useUnistyles();
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
const { id } = useLocalSearchParams<{ id: string }>(); const { id } = useLocalSearchParams<{ id: string }>();
const { agents, agentStreamState, pendingPermissions, respondToPermission } = useSession(); const { agents, agentStreamState, pendingPermissions, respondToPermission, initializeAgent } = useSession();
const { registerFooterControls, unregisterFooterControls } = useFooterControls(); const { registerFooterControls, unregisterFooterControls } = useFooterControls();
const [isContentReady, setIsContentReady] = useState(false); const [isContentReady, setIsContentReady] = useState(false);
@@ -38,6 +38,13 @@ export default function AgentScreen() {
Array.from(pendingPermissions.entries()).filter(([_, perm]) => perm.agentId === id) Array.from(pendingPermissions.entries()).filter(([_, perm]) => perm.agentId === id)
); );
useEffect(() => {
if (!id) {
return;
}
initializeAgent({ agentId: id });
}, [id, initializeAgent]);
const agentControls = useMemo(() => { const agentControls = useMemo(() => {
if (!id) return null; if (!id) return null;
return <AgentInputArea agentId={id} />; return <AgentInputArea agentId={id} />;
+118 -21
View File
@@ -57,15 +57,15 @@ export interface Agent {
status: AgentStatus; status: AgentStatus;
createdAt: Date; createdAt: Date;
type: "claude"; type: "claude";
sessionId?: string; sessionId: string | null;
error?: string; error: string | null;
currentModeId?: string; currentModeId: string | null;
availableModes?: Array<{ availableModes: Array<{
id: string; id: string;
name: string; name: string;
description?: string | null; description?: string | null;
}>; }> | null;
title?: string; title: string | null;
cwd: string; cwd: string;
} }
@@ -112,6 +112,7 @@ interface SessionContextValue {
setPendingPermissions: (perms: Map<string, PendingPermission> | ((prev: Map<string, PendingPermission>) => Map<string, PendingPermission>)) => void; setPendingPermissions: (perms: Map<string, PendingPermission> | ((prev: Map<string, PendingPermission>) => Map<string, PendingPermission>)) => void;
// Helpers // Helpers
initializeAgent: (params: { agentId: string; requestId?: string }) => void;
sendAgentMessage: (agentId: string, message: string) => void; sendAgentMessage: (agentId: string, message: string) => void;
sendAgentAudio: (agentId: string, audioBlob: Blob) => Promise<void>; sendAgentAudio: (agentId: string, audioBlob: Blob) => Promise<void>;
createAgent: (options: { cwd: string; initialMode?: string; requestId?: string }) => void; createAgent: (options: { cwd: string; initialMode?: string; requestId?: string }) => void;
@@ -165,12 +166,32 @@ export function SessionProvider({ children, serverUrl }: SessionProviderProps) {
console.log("[Session] Session state:", agentsList.length, "agents,", commandsList.length, "commands"); console.log("[Session] Session state:", agentsList.length, "agents,", commandsList.length, "commands");
setAgents(new Map(agentsList.map((a) => [a.id, { setAgents(
...a, new Map(
createdAt: new Date(a.createdAt), agentsList.map((agentInfo) => {
} as Agent]))); const createdAt = new Date(agentInfo.createdAt);
return [
agentInfo.id,
{
id: agentInfo.id,
status: agentInfo.status as AgentStatus,
type: agentInfo.type,
createdAt,
title: agentInfo.title ?? null,
cwd: agentInfo.cwd,
sessionId: agentInfo.sessionId ?? null,
error: agentInfo.error ?? null,
currentModeId: agentInfo.currentModeId ?? null,
availableModes: agentInfo.availableModes ?? null,
} satisfies Agent,
];
})
)
);
setCommands(new Map(commandsList.map((c) => [c.id, c as Command]))); setCommands(new Map(commandsList.map((c) => [c.id, c as Command])));
setAgentStreamState(new Map());
setAgentUpdates(new Map());
}); });
// Agent created // Agent created
@@ -185,16 +206,77 @@ export function SessionProvider({ children, serverUrl }: SessionProviderProps) {
status: status as AgentStatus, status: status as AgentStatus,
type, type,
createdAt: new Date(), createdAt: new Date(),
title, title: title ?? null,
cwd, cwd,
currentModeId, sessionId: null,
availableModes, error: null,
currentModeId: currentModeId ?? null,
availableModes: availableModes ?? null,
}; };
setAgents((prev) => new Map(prev).set(agentId, agent)); setAgents((prev) => new Map(prev).set(agentId, agent));
setAgentStreamState((prev) => new Map(prev).set(agentId, [])); setAgentStreamState((prev) => new Map(prev).set(agentId, []));
}); });
// Agent initialized - receive full history on demand
const unsubAgentInitialized = ws.on("agent_initialized", (message) => {
if (message.type !== "agent_initialized") return;
const { agentId, info, updates } = message.payload;
console.log(
"[Session] Agent initialized:",
agentId,
"updates:",
updates.length
);
setAgents((prev) => {
const next = new Map(prev);
const createdAt = new Date(info.createdAt);
const existing = next.get(agentId);
const normalizedAgent: Agent = {
id: info.id,
status: info.status as AgentStatus,
type: info.type,
createdAt,
title: info.title ?? null,
cwd: info.cwd,
sessionId: info.sessionId ?? null,
error: info.error ?? null,
currentModeId: info.currentModeId ?? null,
availableModes: info.availableModes ?? null,
};
next.set(
agentId,
existing
? {
...existing,
...normalizedAgent,
}
: normalizedAgent
);
return next;
});
const normalizedUpdates: AgentUpdate[] = updates.map((update) => ({
agentId: update.agentId,
timestamp: new Date(update.timestamp),
notification: update.notification,
}));
setAgentUpdates((prev) => new Map(prev).set(agentId, normalizedUpdates));
setAgentStreamState((prev) => {
const reconstructedStream = normalizedUpdates.reduce<StreamItem[]>(
(acc, update) => reduceStreamUpdate(acc, update.notification, update.timestamp),
[]
);
return new Map(prev).set(agentId, reconstructedStream);
});
});
// Agent status update (mode changes, title changes, etc.) // Agent status update (mode changes, title changes, etc.)
const unsubAgentStatus = ws.on("agent_status", (message) => { const unsubAgentStatus = ws.on("agent_status", (message) => {
if (message.type !== "agent_status") return; if (message.type !== "agent_status") return;
@@ -209,11 +291,11 @@ export function SessionProvider({ children, serverUrl }: SessionProviderProps) {
const updatedAgent: Agent = { const updatedAgent: Agent = {
...existingAgent, ...existingAgent,
status: info.status as AgentStatus, status: info.status as AgentStatus,
sessionId: info.sessionId, sessionId: info.sessionId ?? null,
error: info.error, error: info.error ?? null,
currentModeId: info.currentModeId, currentModeId: info.currentModeId ?? null,
availableModes: info.availableModes, availableModes: info.availableModes ?? null,
title: info.title, title: info.title ?? null,
cwd: info.cwd, cwd: info.cwd,
}; };
@@ -224,11 +306,12 @@ export function SessionProvider({ children, serverUrl }: SessionProviderProps) {
// Agent update // Agent update
const unsubAgentUpdate = ws.on("agent_update", (message) => { const unsubAgentUpdate = ws.on("agent_update", (message) => {
if (message.type !== "agent_update") return; if (message.type !== "agent_update") return;
const { agentId, notification } = message.payload; const { agentId, notification, timestamp: rawTimestamp } = message.payload;
const timestamp = new Date(rawTimestamp);
const update: AgentUpdate = { const update: AgentUpdate = {
agentId, agentId,
timestamp: new Date(), timestamp,
notification, notification,
}; };
@@ -240,7 +323,7 @@ export function SessionProvider({ children, serverUrl }: SessionProviderProps) {
// Update stream state using reducer // Update stream state using reducer
setAgentStreamState((prev) => { setAgentStreamState((prev) => {
const currentStream = prev.get(agentId) || []; const currentStream = prev.get(agentId) || [];
const newStream = reduceStreamUpdate(currentStream, notification, new Date()); const newStream = reduceStreamUpdate(currentStream, notification, timestamp);
return new Map(prev).set(agentId, newStream); return new Map(prev).set(agentId, newStream);
}); });
}); });
@@ -480,6 +563,7 @@ export function SessionProvider({ children, serverUrl }: SessionProviderProps) {
return () => { return () => {
unsubSessionState(); unsubSessionState();
unsubAgentCreated(); unsubAgentCreated();
unsubAgentInitialized();
unsubAgentStatus(); unsubAgentStatus();
unsubAgentUpdate(); unsubAgentUpdate();
unsubPermissionRequest(); unsubPermissionRequest();
@@ -491,6 +575,18 @@ export function SessionProvider({ children, serverUrl }: SessionProviderProps) {
}; };
}, [ws, audioPlayer, setIsPlayingAudio]); }, [ws, audioPlayer, setIsPlayingAudio]);
const initializeAgent = useCallback(({ agentId, requestId }: { agentId: string; requestId?: string }) => {
const msg: WSInboundMessage = {
type: "session",
message: {
type: "initialize_agent_request",
agentId,
requestId,
},
};
ws.send(msg);
}, [ws]);
const sendAgentMessage = useCallback((agentId: string, message: string) => { const sendAgentMessage = useCallback((agentId: string, message: string) => {
// Generate unique message ID for deduplication // Generate unique message ID for deduplication
const messageId = generateMessageId(); const messageId = generateMessageId();
@@ -636,6 +732,7 @@ export function SessionProvider({ children, serverUrl }: SessionProviderProps) {
setAgentUpdates, setAgentUpdates,
pendingPermissions, pendingPermissions,
setPendingPermissions, setPendingPermissions,
initializeAgent,
sendAgentMessage, sendAgentMessage,
sendAgentAudio, sendAgentAudio,
createAgent, createAgent,
+33
View File
@@ -20,5 +20,38 @@
}, },
"createdAt": "2025-10-24T15:57:04.442Z", "createdAt": "2025-10-24T15:57:04.442Z",
"cwd": "/Users/moboudra/dev/voice-dev" "cwd": "/Users/moboudra/dev/voice-dev"
},
{
"id": "50128b81-017c-4ab5-8901-3d3f6b65898c",
"title": "Agent 50128b81",
"sessionId": null,
"options": {
"type": "claude",
"sessionId": null
},
"createdAt": "2025-10-25T10:48:01.416Z",
"cwd": "/Users/moboudra/dev/faro/main"
},
{
"id": "cf327185-2307-4744-b2d0-c50f8bfa8f55",
"title": "Agent cf327185",
"sessionId": "019a1b66-dbf2-76b0-b578-60abfbd65d22",
"options": {
"type": "claude",
"sessionId": null
},
"createdAt": "2025-10-25T12:37:28.278Z",
"cwd": "/Users/moboudra/dev/faro/main"
},
{
"id": "9af00580-418b-4053-a942-a8d168ab5703",
"title": "Initial User Greeting",
"sessionId": "7adb8247-5ffe-44a0-ae8b-177a5611fe6c",
"options": {
"type": "claude",
"sessionId": "7adb8247-5ffe-44a0-ae8b-177a5611fe6c"
},
"createdAt": "2025-10-25T12:44:52.183Z",
"cwd": "/Users/moboudra/dev/faro/main"
} }
] ]
+1 -1
View File
@@ -18,7 +18,7 @@
"dependencies": { "dependencies": {
"@agentclientprotocol/sdk": "^0.4.9", "@agentclientprotocol/sdk": "^0.4.9",
"@ai-sdk/openai": "^2.0.52", "@ai-sdk/openai": "^2.0.52",
"@boudra/claude-code-acp": "file:../../../claude-code-acp", "@boudra/claude-code-acp": "^0.8.2",
"@deepgram/sdk": "^3.4.0", "@deepgram/sdk": "^3.4.0",
"@modelcontextprotocol/sdk": "^1.20.1", "@modelcontextprotocol/sdk": "^1.20.1",
"@openrouter/ai-sdk-provider": "^1.2.0", "@openrouter/ai-sdk-provider": "^1.2.0",
+51 -14
View File
@@ -505,18 +505,10 @@ export class AgentManager {
return Array.from(this.agents.values()).map((agent) => { return Array.from(this.agents.values()).map((agent) => {
const status = getAgentStatusFromState(agent.state); const status = getAgentStatusFromState(agent.state);
const error = getAgentError(agent.state); const error = getAgentError(agent.state);
const sessionId = const runtime = this.getRuntime(agent);
agent.state.type !== "uninitialized" && agent.state.type !== "killed" && agent.state.type !== "failed" const sessionId = runtime?.sessionId ?? null;
? agent.state.runtime.sessionId const currentModeId = runtime?.currentModeId ?? null;
: null; const availableModes = runtime?.availableModes ?? null;
const currentModeId =
agent.state.type !== "uninitialized" && agent.state.type !== "killed" && agent.state.type !== "failed"
? agent.state.runtime.currentModeId
: null;
const availableModes =
agent.state.type !== "uninitialized" && agent.state.type !== "killed" && agent.state.type !== "failed"
? agent.state.runtime.availableModes
: null;
return { return {
id: agent.id, id: agent.id,
@@ -564,6 +556,43 @@ export class AgentManager {
return [...agent.updates]; return [...agent.updates];
} }
/**
* Lazily initialize an agent and return its info and existing history
* Used by clients to opt-in to agent startup on demand
*/
async initializeAgentAndGetHistory(
agentId: string
): Promise<{ info: AgentInfo; updates: AgentUpdate[] }> {
await this.ensureInitialized(agentId);
const agent = this.agents.get(agentId);
if (!agent) {
throw new Error(`Agent ${agentId} not found`);
}
const status = getAgentStatusFromState(agent.state);
const error = getAgentError(agent.state);
const runtime = this.getRuntime(agent);
const info: AgentInfo = {
id: agent.id,
status,
createdAt: agent.createdAt,
type: "claude",
sessionId: runtime?.sessionId ?? null,
error: error ?? null,
currentModeId: runtime?.currentModeId ?? null,
availableModes: runtime?.availableModes ?? null,
title: agent.title,
cwd: agent.cwd,
};
return {
info,
updates: [...agent.updates],
};
}
/** /**
* Get the current session mode for an agent * Get the current session mode for an agent
*/ */
@@ -645,6 +674,8 @@ export class AgentManager {
case "initializing": case "initializing":
agent.state = { agent.state = {
type: "initializing", type: "initializing",
persistedSessionId: agent.state.persistedSessionId,
initPromise: agent.state.initPromise,
runtime: updatedRuntime, runtime: updatedRuntime,
initStartedAt: agent.state.initStartedAt, initStartedAt: agent.state.initStartedAt,
}; };
@@ -755,7 +786,7 @@ export class AgentManager {
} }
if (agent.state.type === "initializing") { if (agent.state.type === "initializing") {
return agent.state.runtime; return agent.state.runtime ?? null;
} }
if (agent.state.type === "failed" && agent.state.runtime) { if (agent.state.type === "failed" && agent.state.runtime) {
@@ -820,10 +851,16 @@ export class AgentManager {
availableModes: null, availableModes: null,
}; };
if (agent.state.type !== "initializing") {
throw new Error(`Agent ${agentId} must be initializing before starting runtime`);
}
agent.state = { agent.state = {
type: "initializing", type: "initializing",
persistedSessionId: agent.state.persistedSessionId,
initPromise: agent.state.initPromise,
initStartedAt: agent.state.initStartedAt,
runtime, runtime,
initStartedAt: new Date(),
}; };
agentProcess.on("error", (error) => { agentProcess.on("error", (error) => {
+7 -1
View File
@@ -65,7 +65,13 @@ export interface AgentRuntime {
*/ */
export type ManagedAgentState = export type ManagedAgentState =
| { type: "uninitialized"; persistedSessionId: string | null; lastError?: string } | { type: "uninitialized"; persistedSessionId: string | null; lastError?: string }
| { type: "initializing"; persistedSessionId: string | null; initPromise: Promise<void>; initStartedAt: Date } | {
type: "initializing";
persistedSessionId: string | null;
initPromise: Promise<void>;
initStartedAt: Date;
runtime?: AgentRuntime;
}
| { type: "ready"; runtime: AgentRuntime } | { type: "ready"; runtime: AgentRuntime }
| { type: "processing"; runtime: AgentRuntime } | { type: "processing"; runtime: AgentRuntime }
| { type: "completed"; runtime: AgentRuntime; stopReason?: string } | { type: "completed"; runtime: AgentRuntime; stopReason?: string }
+49 -45
View File
@@ -1,5 +1,30 @@
import { z } from "zod"; import { z } from "zod";
const AgentModeSchema = z.object({
id: z.string(),
name: z.string(),
description: z.string().nullable().optional(),
});
const AgentInfoSchema = z.object({
id: z.string(),
status: z.string(),
createdAt: z.date(),
type: z.literal("claude"),
sessionId: z.string().nullable(),
error: z.string().nullable(),
currentModeId: z.string().nullable(),
availableModes: z.array(AgentModeSchema).nullable(),
title: z.string().nullable(),
cwd: z.string(),
});
const AgentUpdatePayloadSchema = z.object({
agentId: z.string(),
timestamp: z.date(),
notification: z.any(),
});
// ============================================================================ // ============================================================================
// Session Inbound Messages (Session receives these) // Session Inbound Messages (Session receives these)
// ============================================================================ // ============================================================================
@@ -66,6 +91,12 @@ export const CreateAgentRequestMessageSchema = z.object({
requestId: z.string().optional(), requestId: z.string().optional(),
}); });
export const InitializeAgentRequestMessageSchema = z.object({
type: z.literal("initialize_agent_request"),
agentId: z.string(),
requestId: z.string().optional(),
});
export const SetAgentModeMessageSchema = z.object({ export const SetAgentModeMessageSchema = z.object({
type: z.literal("set_agent_mode"), type: z.literal("set_agent_mode"),
agentId: z.string(), agentId: z.string(),
@@ -91,6 +122,7 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
SendAgentMessageSchema, SendAgentMessageSchema,
SendAgentAudioSchema, SendAgentAudioSchema,
CreateAgentRequestMessageSchema, CreateAgentRequestMessageSchema,
InitializeAgentRequestMessageSchema,
SetAgentModeMessageSchema, SetAgentModeMessageSchema,
AgentPermissionResponseMessageSchema, AgentPermissionResponseMessageSchema,
]); ]);
@@ -182,11 +214,7 @@ export const AgentCreatedMessageSchema = z.object({
status: z.string(), status: z.string(),
type: z.literal("claude"), type: z.literal("claude"),
currentModeId: z.string().optional(), currentModeId: z.string().optional(),
availableModes: z.array(z.object({ availableModes: z.array(AgentModeSchema).optional(),
id: z.string(),
name: z.string(),
description: z.string().nullable().optional(),
})).optional(),
title: z.string().optional(), title: z.string().optional(),
cwd: z.string(), cwd: z.string(),
requestId: z.string().optional(), requestId: z.string().optional(),
@@ -195,12 +223,7 @@ export const AgentCreatedMessageSchema = z.object({
export const AgentUpdateMessageSchema = z.object({ export const AgentUpdateMessageSchema = z.object({
type: z.literal("agent_update"), type: z.literal("agent_update"),
payload: z.object({ payload: AgentUpdatePayloadSchema,
agentId: z.string(),
timestamp: z.date(),
// Runtime validation with z.any(), TypeScript enforces AgentNotification type
notification: z.any(),
}),
}); });
export const AgentStatusMessageSchema = z.object({ export const AgentStatusMessageSchema = z.object({
@@ -208,46 +231,14 @@ export const AgentStatusMessageSchema = z.object({
payload: z.object({ payload: z.object({
agentId: z.string(), agentId: z.string(),
status: z.string(), status: z.string(),
info: z.object({ info: AgentInfoSchema,
id: z.string(),
status: z.string(),
createdAt: z.date(),
type: z.literal("claude"),
sessionId: z.string().optional(),
error: z.string().optional(),
currentModeId: z.string().optional(),
availableModes: z.array(z.object({
id: z.string(),
name: z.string(),
description: z.string().nullable().optional(),
})).optional(),
title: z.string().optional(),
cwd: z.string(),
}),
}), }),
}); });
export const SessionStateMessageSchema = z.object({ export const SessionStateMessageSchema = z.object({
type: z.literal("session_state"), type: z.literal("session_state"),
payload: z.object({ payload: z.object({
agents: z.array( agents: z.array(AgentInfoSchema),
z.object({
id: z.string(),
status: z.string(),
createdAt: z.date(),
type: z.literal("claude"),
sessionId: z.string().nullable(),
error: z.string().nullable(),
currentModeId: z.string().nullable(),
availableModes: z.array(z.object({
id: z.string(),
name: z.string(),
description: z.string().nullable().optional(),
})).nullable(),
title: z.string().nullable(),
cwd: z.string(),
})
),
commands: z.array( commands: z.array(
z.object({ z.object({
id: z.string(), id: z.string(),
@@ -261,6 +252,16 @@ export const SessionStateMessageSchema = z.object({
}), }),
}); });
export const AgentInitializedMessageSchema = z.object({
type: z.literal("agent_initialized"),
payload: z.object({
agentId: z.string(),
info: AgentInfoSchema,
updates: z.array(AgentUpdatePayloadSchema),
requestId: z.string().optional(),
}),
});
export const ListConversationsResponseMessageSchema = z.object({ export const ListConversationsResponseMessageSchema = z.object({
type: z.literal("list_conversations_response"), type: z.literal("list_conversations_response"),
payload: z.object({ payload: z.object({
@@ -319,6 +320,7 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
AgentUpdateMessageSchema, AgentUpdateMessageSchema,
AgentStatusMessageSchema, AgentStatusMessageSchema,
SessionStateMessageSchema, SessionStateMessageSchema,
AgentInitializedMessageSchema,
ListConversationsResponseMessageSchema, ListConversationsResponseMessageSchema,
DeleteConversationResponseMessageSchema, DeleteConversationResponseMessageSchema,
AgentPermissionRequestMessageSchema, AgentPermissionRequestMessageSchema,
@@ -341,6 +343,7 @@ export type AgentCreatedMessage = z.infer<typeof AgentCreatedMessageSchema>;
export type AgentUpdateMessage = z.infer<typeof AgentUpdateMessageSchema>; export type AgentUpdateMessage = z.infer<typeof AgentUpdateMessageSchema>;
export type AgentStatusMessage = z.infer<typeof AgentStatusMessageSchema>; export type AgentStatusMessage = z.infer<typeof AgentStatusMessageSchema>;
export type SessionStateMessage = z.infer<typeof SessionStateMessageSchema>; export type SessionStateMessage = z.infer<typeof SessionStateMessageSchema>;
export type AgentInitializedMessage = z.infer<typeof AgentInitializedMessageSchema>;
export type ListConversationsResponseMessage = z.infer<typeof ListConversationsResponseMessageSchema>; export type ListConversationsResponseMessage = z.infer<typeof ListConversationsResponseMessageSchema>;
export type DeleteConversationResponseMessage = z.infer<typeof DeleteConversationResponseMessageSchema>; export type DeleteConversationResponseMessage = z.infer<typeof DeleteConversationResponseMessageSchema>;
export type AgentPermissionRequestMessage = z.infer<typeof AgentPermissionRequestMessageSchema>; export type AgentPermissionRequestMessage = z.infer<typeof AgentPermissionRequestMessageSchema>;
@@ -355,6 +358,7 @@ export type RealtimeAudioChunkMessage = z.infer<typeof RealtimeAudioChunkMessage
export type SendAgentMessage = z.infer<typeof SendAgentMessageSchema>; export type SendAgentMessage = z.infer<typeof SendAgentMessageSchema>;
export type SendAgentAudio = z.infer<typeof SendAgentAudioSchema>; export type SendAgentAudio = z.infer<typeof SendAgentAudioSchema>;
export type CreateAgentRequestMessage = z.infer<typeof CreateAgentRequestMessageSchema>; export type CreateAgentRequestMessage = z.infer<typeof CreateAgentRequestMessageSchema>;
export type InitializeAgentRequestMessage = z.infer<typeof InitializeAgentRequestMessageSchema>;
export type SetAgentModeMessage = z.infer<typeof SetAgentModeMessageSchema>; export type SetAgentModeMessage = z.infer<typeof SetAgentModeMessageSchema>;
export type AgentPermissionResponseMessage = z.infer<typeof AgentPermissionResponseMessageSchema>; export type AgentPermissionResponseMessage = z.infer<typeof AgentPermissionResponseMessageSchema>;
+78 -66
View File
@@ -222,18 +222,7 @@ export class Session {
payload: { payload: {
agentId, agentId,
status, status,
info: { info,
id: info.id,
status: info.status,
createdAt: info.createdAt,
type: info.type,
sessionId: info.sessionId ?? undefined,
error: info.error ?? undefined,
currentModeId: info.currentModeId ?? undefined,
availableModes: info.availableModes ?? undefined,
title: info.title ?? undefined,
cwd: info.cwd,
},
}, },
}); });
console.log( console.log(
@@ -331,18 +320,7 @@ export class Session {
payload: { payload: {
agentId, agentId,
status: updatedInfo.status, status: updatedInfo.status,
info: { info: updatedInfo,
id: updatedInfo.id,
status: updatedInfo.status,
createdAt: updatedInfo.createdAt,
type: updatedInfo.type,
sessionId: updatedInfo.sessionId ?? undefined,
error: updatedInfo.error ?? undefined,
currentModeId: updatedInfo.currentModeId ?? undefined,
availableModes: updatedInfo.availableModes ?? undefined,
title: updatedInfo.title ?? undefined,
cwd: updatedInfo.cwd,
},
}, },
}); });
} }
@@ -492,6 +470,13 @@ export class Session {
await this.handleCreateAgentRequest(msg.cwd, msg.initialMode, msg.requestId); await this.handleCreateAgentRequest(msg.cwd, msg.initialMode, msg.requestId);
break; break;
case "initialize_agent_request":
await this.handleInitializeAgentRequest(
msg.agentId,
msg.requestId
);
break;
case "set_agent_mode": case "set_agent_mode":
await this.handleSetAgentMode(msg.agentId, msg.modeId); await this.handleSetAgentMode(msg.agentId, msg.modeId);
break; break;
@@ -715,6 +700,61 @@ export class Session {
} }
} }
/**
* Handle on-demand agent initialization request from client
*/
private async handleInitializeAgentRequest(
agentId: string,
requestId?: string
): Promise<void> {
console.log(
`[Session ${this.clientId}] Initializing agent ${agentId} on demand`
);
try {
this.subscribeToAgent(agentId);
const { info, updates } =
await this.agentManager.initializeAgentAndGetHistory(agentId);
this.emit({
type: "agent_initialized",
payload: {
agentId,
info,
updates: updates.map((update) => ({
agentId: update.agentId,
timestamp:
update.timestamp instanceof Date
? update.timestamp
: new Date(update.timestamp),
notification: update.notification,
})),
requestId,
},
});
console.log(
`[Session ${this.clientId}] Agent ${agentId} initialized with ${updates.length} historical updates`
);
} catch (error: any) {
console.error(
`[Session ${this.clientId}] Failed to initialize agent ${agentId}:`,
error
);
this.emit({
type: "activity_log",
payload: {
id: uuidv4(),
timestamp: new Date(),
type: "error",
content: `Failed to initialize agent: ${error.message}`,
},
});
throw error;
}
}
/** /**
* Handle create agent request * Handle create agent request
*/ */
@@ -749,23 +789,27 @@ export class Session {
// Subscribe to agent updates // Subscribe to agent updates
this.subscribeToAgent(agentId); this.subscribeToAgent(agentId);
// Emit agent_created message // Auto-initialize agent immediately on explicit creation so it's ready to use
console.log(`[Session ${this.clientId}] Auto-initializing agent ${agentId}`);
const { info } = await this.agentManager.initializeAgentAndGetHistory(agentId);
// Emit agent_created message with initialized info
this.emit({ this.emit({
type: "agent_created", type: "agent_created",
payload: { payload: {
agentId, agentId,
status: agentInfo?.status || "initializing", status: info.status,
type: "claude", type: "claude",
currentModeId: agentInfo?.currentModeId ?? undefined, currentModeId: info.currentModeId ?? undefined,
availableModes: agentInfo?.availableModes ?? undefined, availableModes: info.availableModes ?? undefined,
title: agentInfo?.title ?? undefined, title: info.title ?? undefined,
cwd: agentInfo?.cwd || cwd, cwd: info.cwd,
requestId, requestId,
}, },
}); });
console.log( console.log(
`[Session ${this.clientId}] Emitted agent_created with currentModeId:`, `[Session ${this.clientId}] Emitted agent_created with status: ${info.status}, currentModeId:`,
agentInfo?.currentModeId info.currentModeId
); );
} catch (error: any) { } catch (error: any) {
console.error( console.error(
@@ -810,18 +854,7 @@ export class Session {
payload: { payload: {
agentId, agentId,
status: info.status, status: info.status,
info: { info,
id: info.id,
status: info.status,
createdAt: info.createdAt,
type: info.type,
title: info.title ?? undefined,
cwd: info.cwd,
sessionId: info.sessionId ?? undefined,
error: info.error ?? undefined,
currentModeId: info.currentModeId ?? undefined,
availableModes: info.availableModes ?? undefined,
},
}, },
}); });
} }
@@ -886,30 +919,9 @@ export class Session {
// Get live agents with session modes // Get live agents with session modes
const agents = this.agentManager?.listAgents() || []; const agents = this.agentManager?.listAgents() || [];
// Subscribe to all existing agents and send their history (in case of reconnection) // Subscribe to all existing agents so future updates stream through
for (const agent of agents) { for (const agent of agents) {
this.subscribeToAgent(agent.id); this.subscribeToAgent(agent.id);
// Get historical updates from AgentManager
const history = this.agentManager.getAgentUpdates(agent.id);
// Send each historical update to client
console.log(
`[Session ${this.clientId}] Sending ${history.length} historical updates for agent ${agent.id}`
);
for (const update of history) {
this.emit({
type: "agent_update",
payload: {
agentId: update.agentId,
timestamp:
update.timestamp instanceof Date
? update.timestamp
: new Date(update.timestamp),
notification: update.notification,
},
});
}
} }
// Get live commands from terminal manager // Get live commands from terminal manager