Merge remote-tracking branch 'origin/main' into feature/mobile-app
This commit is contained in:
@@ -175,7 +175,6 @@ describe("AgentManager", () => {
|
|||||||
// Create agent without initial prompt first, so we can subscribe
|
// Create agent without initial prompt first, so we can subscribe
|
||||||
const agentId = await manager.createAgent({
|
const agentId = await manager.createAgent({
|
||||||
cwd: tmpDir,
|
cwd: tmpDir,
|
||||||
permissionsMode: "auto_approve",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(agentId).toBeDefined();
|
expect(agentId).toBeDefined();
|
||||||
@@ -229,7 +228,6 @@ describe("AgentManager", () => {
|
|||||||
|
|
||||||
const agentId = await manager.createAgent({
|
const agentId = await manager.createAgent({
|
||||||
cwd: tmpDir,
|
cwd: tmpDir,
|
||||||
permissionsMode: "auto_approve",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(agentId).toBeDefined();
|
expect(agentId).toBeDefined();
|
||||||
@@ -275,7 +273,6 @@ describe("AgentManager", () => {
|
|||||||
|
|
||||||
const agentId = await manager.createAgent({
|
const agentId = await manager.createAgent({
|
||||||
cwd: tmpDir,
|
cwd: tmpDir,
|
||||||
permissionsMode: "reject_all",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(agentId).toBeDefined();
|
expect(agentId).toBeDefined();
|
||||||
@@ -338,7 +335,6 @@ describe("AgentManager", () => {
|
|||||||
|
|
||||||
const agentId = await manager.createAgent({
|
const agentId = await manager.createAgent({
|
||||||
cwd: tmpDir,
|
cwd: tmpDir,
|
||||||
permissionsMode: "auto_approve",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(agentId).toBeDefined();
|
expect(agentId).toBeDefined();
|
||||||
@@ -393,7 +389,6 @@ describe("AgentManager", () => {
|
|||||||
|
|
||||||
const agentId = await manager.createAgent({
|
const agentId = await manager.createAgent({
|
||||||
cwd: tmpDir,
|
cwd: tmpDir,
|
||||||
permissionsMode: "auto_approve",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(agentId).toBeDefined();
|
expect(agentId).toBeDefined();
|
||||||
@@ -439,7 +434,6 @@ describe("AgentManager", () => {
|
|||||||
|
|
||||||
const agentId = await manager.createAgent({
|
const agentId = await manager.createAgent({
|
||||||
cwd: tmpDir,
|
cwd: tmpDir,
|
||||||
permissionsMode: "auto_approve",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(agentId).toBeDefined();
|
expect(agentId).toBeDefined();
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
import { spawn, ChildProcess } from "child_process";
|
import { spawn, ChildProcess } from "child_process";
|
||||||
import { Writable, Readable } from "stream";
|
import { Writable, Readable } from "stream";
|
||||||
import { ClientSideConnection, ndJsonStream, PROTOCOL_VERSION } from "@agentclientprotocol/sdk";
|
import {
|
||||||
|
ClientSideConnection,
|
||||||
|
ndJsonStream,
|
||||||
|
PROTOCOL_VERSION,
|
||||||
|
type Client,
|
||||||
|
type SessionNotification,
|
||||||
|
type RequestPermissionRequest,
|
||||||
|
type RequestPermissionResponse,
|
||||||
|
type ReadTextFileRequest,
|
||||||
|
type ReadTextFileResponse,
|
||||||
|
type WriteTextFileRequest,
|
||||||
|
type WriteTextFileResponse,
|
||||||
|
} from "@agentclientprotocol/sdk";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import type {
|
import type {
|
||||||
AgentStatus,
|
AgentStatus,
|
||||||
@@ -8,7 +20,7 @@ import type {
|
|||||||
AgentUpdate,
|
AgentUpdate,
|
||||||
CreateAgentOptions,
|
CreateAgentOptions,
|
||||||
AgentUpdateCallback,
|
AgentUpdateCallback,
|
||||||
PermissionMode,
|
SessionMode,
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
|
|
||||||
interface ManagedAgent {
|
interface ManagedAgent {
|
||||||
@@ -19,7 +31,8 @@ interface ManagedAgent {
|
|||||||
connection: ClientSideConnection;
|
connection: ClientSideConnection;
|
||||||
sessionId?: string;
|
sessionId?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
permissionsMode: PermissionMode;
|
currentModeId?: string;
|
||||||
|
availableModes?: SessionMode[];
|
||||||
subscribers: Set<AgentUpdateCallback>;
|
subscribers: Set<AgentUpdateCallback>;
|
||||||
updates: AgentUpdate[];
|
updates: AgentUpdate[];
|
||||||
}
|
}
|
||||||
@@ -27,53 +40,30 @@ interface ManagedAgent {
|
|||||||
/**
|
/**
|
||||||
* Client implementation for ACP callbacks
|
* Client implementation for ACP callbacks
|
||||||
*/
|
*/
|
||||||
class ACPClient {
|
class ACPClient implements Client {
|
||||||
constructor(
|
constructor(
|
||||||
private agentId: string,
|
private agentId: string,
|
||||||
private onUpdate: (agentId: string, update: any) => void,
|
private onUpdate: (agentId: string, update: SessionNotification) => void
|
||||||
private getPermissionMode: () => PermissionMode
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async requestPermission(params: any) {
|
async requestPermission(params: RequestPermissionRequest): Promise<RequestPermissionResponse> {
|
||||||
const mode = this.getPermissionMode();
|
// TODO: Forward permission requests to the UI for user approval
|
||||||
console.log(`[Agent ${this.agentId}] Permission requested (mode: ${mode}):`, params);
|
// For now, auto-approve all permissions
|
||||||
|
console.log(`[Agent ${this.agentId}] Permission requested (auto-approving):`, params);
|
||||||
|
|
||||||
if (mode === "auto_approve") {
|
return {
|
||||||
// Auto-approve all permissions
|
outcome: {
|
||||||
return {
|
outcome: "selected" as const,
|
||||||
outcome: {
|
optionId: params.options[0]?.optionId || "",
|
||||||
outcome: "selected" as const,
|
},
|
||||||
optionId: params.options[0]?.optionId || "",
|
};
|
||||||
},
|
|
||||||
};
|
|
||||||
} else if (mode === "reject_all") {
|
|
||||||
// Reject all permissions
|
|
||||||
const rejectOption = params.options.find((opt: any) =>
|
|
||||||
opt.kind === "reject_once" || opt.kind === "reject_all"
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
outcome: {
|
|
||||||
outcome: "selected" as const,
|
|
||||||
optionId: rejectOption?.optionId || params.options[params.options.length - 1]?.optionId || "",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// ask_user mode - for now, auto-approve (TODO: implement user prompts)
|
|
||||||
console.log(`[Agent ${this.agentId}] User permission prompt not yet implemented, auto-approving`);
|
|
||||||
return {
|
|
||||||
outcome: {
|
|
||||||
outcome: "selected" as const,
|
|
||||||
optionId: params.options[0]?.optionId || "",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async sessionUpdate(params: any) {
|
async sessionUpdate(params: SessionNotification): Promise<void> {
|
||||||
this.onUpdate(this.agentId, params.update);
|
this.onUpdate(this.agentId, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
async readTextFile(params: any) {
|
async readTextFile(params: ReadTextFileRequest): Promise<ReadTextFileResponse> {
|
||||||
console.log(`[Agent ${this.agentId}] Read text file:`, params.path);
|
console.log(`[Agent ${this.agentId}] Read text file:`, params.path);
|
||||||
const fs = await import("fs/promises");
|
const fs = await import("fs/promises");
|
||||||
try {
|
try {
|
||||||
@@ -85,7 +75,7 @@ class ACPClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async writeTextFile(params: any) {
|
async writeTextFile(params: WriteTextFileRequest): Promise<WriteTextFileResponse> {
|
||||||
console.log(`[Agent ${this.agentId}] Write text file:`, params.path);
|
console.log(`[Agent ${this.agentId}] Write text file:`, params.path);
|
||||||
const fs = await import("fs/promises");
|
const fs = await import("fs/promises");
|
||||||
try {
|
try {
|
||||||
@@ -111,7 +101,6 @@ export class AgentManager {
|
|||||||
async createAgent(options: CreateAgentOptions): Promise<string> {
|
async createAgent(options: CreateAgentOptions): Promise<string> {
|
||||||
const agentId = uuidv4();
|
const agentId = uuidv4();
|
||||||
const cwd = options.cwd;
|
const cwd = options.cwd;
|
||||||
const permissionsMode = options.permissionsMode || "auto_approve";
|
|
||||||
|
|
||||||
// Spawn the ACP process
|
// Spawn the ACP process
|
||||||
const agentProcess = spawn("npx", ["@zed-industries/claude-code-acp"], {
|
const agentProcess = spawn("npx", ["@zed-industries/claude-code-acp"], {
|
||||||
@@ -129,10 +118,6 @@ export class AgentManager {
|
|||||||
agentId,
|
agentId,
|
||||||
(id, update) => {
|
(id, update) => {
|
||||||
this.handleSessionNotification(id, update);
|
this.handleSessionNotification(id, update);
|
||||||
},
|
|
||||||
() => {
|
|
||||||
const agent = this.agents.get(agentId);
|
|
||||||
return agent?.permissionsMode || "auto_approve";
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const connection = new ClientSideConnection(() => client, stream);
|
const connection = new ClientSideConnection(() => client, stream);
|
||||||
@@ -144,7 +129,6 @@ export class AgentManager {
|
|||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
process: agentProcess,
|
process: agentProcess,
|
||||||
connection,
|
connection,
|
||||||
permissionsMode,
|
|
||||||
subscribers: new Set(),
|
subscribers: new Set(),
|
||||||
updates: [],
|
updates: [],
|
||||||
};
|
};
|
||||||
@@ -189,11 +173,24 @@ export class AgentManager {
|
|||||||
const sessionResponse = await agent.connection.newSession({
|
const sessionResponse = await agent.connection.newSession({
|
||||||
cwd,
|
cwd,
|
||||||
mcpServers: [],
|
mcpServers: [],
|
||||||
|
...(options.initialMode ? { initialMode: options.initialMode } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
agent.sessionId = sessionResponse.sessionId;
|
agent.sessionId = sessionResponse.sessionId;
|
||||||
|
|
||||||
|
// Store session modes from response
|
||||||
|
if (sessionResponse.modes) {
|
||||||
|
agent.currentModeId = sessionResponse.modes.currentModeId;
|
||||||
|
agent.availableModes = sessionResponse.modes.availableModes;
|
||||||
|
console.log(
|
||||||
|
`[Agent ${agentId}] Session created with mode: ${agent.currentModeId}`,
|
||||||
|
`Available modes:`, agent.availableModes?.map(m => m.id).join(', ')
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log(`[Agent ${agentId}] Session created:`, sessionResponse.sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
agent.status = "ready";
|
agent.status = "ready";
|
||||||
console.log(`[Agent ${agentId}] Session created:`, sessionResponse.sessionId);
|
|
||||||
|
|
||||||
// If an initial prompt was provided, send it
|
// If an initial prompt was provided, send it
|
||||||
if (options.initialPrompt) {
|
if (options.initialPrompt) {
|
||||||
@@ -320,6 +317,8 @@ export class AgentManager {
|
|||||||
type: "claude" as const,
|
type: "claude" as const,
|
||||||
sessionId: agent.sessionId,
|
sessionId: agent.sessionId,
|
||||||
error: agent.error,
|
error: agent.error,
|
||||||
|
currentModeId: agent.currentModeId,
|
||||||
|
availableModes: agent.availableModes,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,32 +354,69 @@ export class AgentManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the permission mode for an agent
|
* Get the current session mode for an agent
|
||||||
*/
|
*/
|
||||||
setPermissionMode(agentId: string, mode: PermissionMode): void {
|
getCurrentMode(agentId: string): string | undefined {
|
||||||
const agent = this.agents.get(agentId);
|
const agent = this.agents.get(agentId);
|
||||||
if (!agent) {
|
if (!agent) {
|
||||||
throw new Error(`Agent ${agentId} not found`);
|
throw new Error(`Agent ${agentId} not found`);
|
||||||
}
|
}
|
||||||
agent.permissionsMode = mode;
|
return agent.currentModeId;
|
||||||
console.log(`[Agent ${agentId}] Permission mode set to: ${mode}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the permission mode for an agent
|
* Get available session modes for an agent
|
||||||
*/
|
*/
|
||||||
getPermissionMode(agentId: string): PermissionMode {
|
getAvailableModes(agentId: string): SessionMode[] {
|
||||||
const agent = this.agents.get(agentId);
|
const agent = this.agents.get(agentId);
|
||||||
if (!agent) {
|
if (!agent) {
|
||||||
throw new Error(`Agent ${agentId} not found`);
|
throw new Error(`Agent ${agentId} not found`);
|
||||||
}
|
}
|
||||||
return agent.permissionsMode;
|
return agent.availableModes || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the session mode for an agent
|
||||||
|
* Validates that the mode is available before setting
|
||||||
|
*/
|
||||||
|
async setSessionMode(agentId: string, modeId: string): Promise<void> {
|
||||||
|
const agent = this.agents.get(agentId);
|
||||||
|
if (!agent) {
|
||||||
|
throw new Error(`Agent ${agentId} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!agent.sessionId) {
|
||||||
|
throw new Error(`Agent ${agentId} has no active session`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate mode is available
|
||||||
|
const availableModes = agent.availableModes || [];
|
||||||
|
const mode = availableModes.find((m) => m.id === modeId);
|
||||||
|
if (!mode && availableModes.length > 0) {
|
||||||
|
const availableIds = availableModes.map((m) => m.id).join(", ");
|
||||||
|
throw new Error(
|
||||||
|
`Mode '${modeId}' not available for agent ${agentId}. Available modes: ${availableIds}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await agent.connection.setSessionMode({
|
||||||
|
sessionId: agent.sessionId,
|
||||||
|
modeId,
|
||||||
|
});
|
||||||
|
|
||||||
|
agent.currentModeId = modeId;
|
||||||
|
console.log(`[Agent ${agentId}] Session mode changed to: ${modeId}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[Agent ${agentId}] Failed to set mode:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle session notifications from the ACP connection
|
* Handle session notifications from the ACP connection
|
||||||
*/
|
*/
|
||||||
private handleSessionNotification(agentId: string, update: any): void {
|
private handleSessionNotification(agentId: string, update: SessionNotification): void {
|
||||||
const agent = this.agents.get(agentId);
|
const agent = this.agents.get(agentId);
|
||||||
if (!agent) return;
|
if (!agent) return;
|
||||||
|
|
||||||
@@ -393,10 +429,16 @@ export class AgentManager {
|
|||||||
// Store the update in history
|
// Store the update in history
|
||||||
agent.updates.push(agentUpdate);
|
agent.updates.push(agentUpdate);
|
||||||
|
|
||||||
|
// Handle mode change notifications
|
||||||
|
if ((update as any).type === "currentModeUpdate" && (update as any).currentModeId) {
|
||||||
|
agent.currentModeId = (update as any).currentModeId;
|
||||||
|
console.log(`[Agent ${agentId}] Mode changed to: ${agent.currentModeId}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Track completion based on final message state
|
// Track completion based on final message state
|
||||||
// ACP protocol indicates completion when the last agent_message_chunk arrives
|
// ACP protocol indicates completion when the last agent_message_chunk arrives
|
||||||
// We detect this by tracking a completion timer that fires if no more updates arrive
|
// We detect this by tracking a completion timer that fires if no more updates arrive
|
||||||
const updateType = update.sessionUpdate?.sessionUpdate;
|
const updateType = update.update.sessionUpdate;
|
||||||
|
|
||||||
// Clear and reset completion timer on any update during processing
|
// Clear and reset completion timer on any update during processing
|
||||||
if (agent.status === "processing") {
|
if (agent.status === "processing") {
|
||||||
@@ -418,7 +460,7 @@ export class AgentManager {
|
|||||||
// Log update for debugging
|
// Log update for debugging
|
||||||
console.log(
|
console.log(
|
||||||
`[Agent ${agentId}] Session update:`,
|
`[Agent ${agentId}] Session update:`,
|
||||||
updateType || update.sessionUpdate || update
|
updateType
|
||||||
);
|
);
|
||||||
|
|
||||||
// Notify all subscribers
|
// Notify all subscribers
|
||||||
|
|||||||
@@ -59,11 +59,11 @@ export async function createAgentMcpServer(
|
|||||||
.describe(
|
.describe(
|
||||||
"Optional initial task or prompt for the agent to start working on immediately after creation. If provided, agent will begin processing this task right away."
|
"Optional initial task or prompt for the agent to start working on immediately after creation. If provided, agent will begin processing this task right away."
|
||||||
),
|
),
|
||||||
permissionsMode: z
|
initialMode: z
|
||||||
.enum(["auto_approve", "ask_user", "reject_all"])
|
.string()
|
||||||
.optional()
|
.optional()
|
||||||
.describe(
|
.describe(
|
||||||
"Permission mode for file operations. 'auto_approve' (default): automatically allow all operations. 'ask_user': prompt user for each permission (not yet implemented, falls back to auto_approve). 'reject_all': reject all permission requests."
|
"Initial session mode for the agent (e.g., 'ask', 'code', 'architect'). If not specified, the agent will use its default mode. The available modes depend on the specific agent implementation."
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
outputSchema: {
|
outputSchema: {
|
||||||
@@ -74,26 +74,34 @@ export async function createAgentMcpServer(
|
|||||||
"Current agent status: 'initializing', 'ready', 'processing', etc."
|
"Current agent status: 'initializing', 'ready', 'processing', etc."
|
||||||
),
|
),
|
||||||
cwd: z.string().describe("The resolved absolute working directory the agent is running in"),
|
cwd: z.string().describe("The resolved absolute working directory the agent is running in"),
|
||||||
permissionsMode: z.string().describe("The permission mode the agent is using"),
|
currentModeId: z.string().optional().describe("The agent's current session mode"),
|
||||||
|
availableModes: z.array(z.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
})).optional().describe("Available session modes for this agent"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async ({ cwd, initialPrompt, permissionsMode }) => {
|
async ({ cwd, initialPrompt, initialMode }) => {
|
||||||
// Expand and resolve the working directory
|
// Expand and resolve the working directory
|
||||||
const resolvedCwd = expandPath(cwd);
|
const resolvedCwd = expandPath(cwd);
|
||||||
|
|
||||||
const agentId = await agentManager.createAgent({
|
const agentId = await agentManager.createAgent({
|
||||||
cwd: resolvedCwd,
|
cwd: resolvedCwd,
|
||||||
initialPrompt,
|
initialPrompt,
|
||||||
permissionsMode: permissionsMode || "auto_approve",
|
initialMode,
|
||||||
});
|
});
|
||||||
|
|
||||||
const status = agentManager.getAgentStatus(agentId);
|
const status = agentManager.getAgentStatus(agentId);
|
||||||
|
const currentModeId = agentManager.getCurrentMode(agentId);
|
||||||
|
const availableModes = agentManager.getAvailableModes(agentId);
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
agentId,
|
agentId,
|
||||||
status,
|
status,
|
||||||
cwd: resolvedCwd,
|
cwd: resolvedCwd,
|
||||||
permissionsMode: permissionsMode || "auto_approve",
|
currentModeId,
|
||||||
|
availableModes,
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -156,6 +164,12 @@ export async function createAgentMcpServer(
|
|||||||
type: z.literal("claude"),
|
type: z.literal("claude"),
|
||||||
sessionId: z.string().optional(),
|
sessionId: z.string().optional(),
|
||||||
error: 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().optional(),
|
||||||
|
})).optional(),
|
||||||
})
|
})
|
||||||
.describe("Detailed agent information"),
|
.describe("Detailed agent information"),
|
||||||
},
|
},
|
||||||
@@ -198,6 +212,12 @@ export async function createAgentMcpServer(
|
|||||||
type: z.literal("claude"),
|
type: z.literal("claude"),
|
||||||
sessionId: z.string().optional(),
|
sessionId: z.string().optional(),
|
||||||
error: 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().optional(),
|
||||||
|
})).optional(),
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -325,35 +345,35 @@ export async function createAgentMcpServer(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Tool: set_agent_permission_mode
|
// Tool: set_agent_mode
|
||||||
server.registerTool(
|
server.registerTool(
|
||||||
"set_agent_permission_mode",
|
"set_agent_mode",
|
||||||
{
|
{
|
||||||
title: "Set Agent Permission Mode",
|
title: "Set Agent Session Mode",
|
||||||
description:
|
description:
|
||||||
"Change how the agent handles file operation permission requests. This is a client-side setting that controls how we respond to the agent's permission requests. 'auto_approve' automatically allows all operations (use with caution). 'reject_all' blocks all file operations. 'ask_user' prompts the user for each permission (not yet implemented, currently falls back to auto_approve).",
|
"Change the agent's session mode (e.g., from 'ask' to 'code', or 'architect' to 'code'). Each mode affects the agent's behavior - 'ask' mode requests permission before changes, 'code' mode writes code directly, 'architect' mode plans without implementation. The available modes depend on the specific agent. Use get_agent_status or list_agents to see available modes for each agent.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
agentId: z.string().describe("Agent ID to configure"),
|
agentId: z.string().describe("Agent ID to configure"),
|
||||||
mode: z
|
modeId: z
|
||||||
.enum(["auto_approve", "ask_user", "reject_all"])
|
.string()
|
||||||
.describe(
|
.describe(
|
||||||
"Permission mode: 'auto_approve' (allow all), 'ask_user' (prompt user), 'reject_all' (deny all)"
|
"The session mode to set (e.g., 'ask', 'code', 'architect'). Must be one of the agent's available modes."
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
outputSchema: {
|
outputSchema: {
|
||||||
agentId: z.string(),
|
success: z.boolean().describe("Whether the mode change succeeded"),
|
||||||
previousMode: z.string().describe("The previous permission mode"),
|
previousMode: z.string().optional().describe("The previous session mode"),
|
||||||
newMode: z.string().describe("The new permission mode"),
|
newMode: z.string().describe("The new session mode"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async ({ agentId, mode }) => {
|
async ({ agentId, modeId }) => {
|
||||||
const previousMode = agentManager.getPermissionMode(agentId);
|
const previousMode = agentManager.getCurrentMode(agentId);
|
||||||
agentManager.setPermissionMode(agentId, mode);
|
await agentManager.setSessionMode(agentId, modeId);
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
agentId,
|
success: true,
|
||||||
previousMode,
|
previousMode,
|
||||||
newMode: mode,
|
newMode: modeId,
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ async function testDirectoryAndInitialPrompt(ctx: TestContext) {
|
|||||||
// Create agent without initial prompt first, so we can subscribe
|
// Create agent without initial prompt first, so we can subscribe
|
||||||
const agentId = await ctx.manager.createAgent({
|
const agentId = await ctx.manager.createAgent({
|
||||||
cwd: ctx.tmpDir,
|
cwd: ctx.tmpDir,
|
||||||
permissionsMode: "auto_approve",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`✓ Agent created: ${agentId}`);
|
console.log(`✓ Agent created: ${agentId}`);
|
||||||
@@ -151,7 +150,6 @@ async function testPermissionAutoApprove(ctx: TestContext) {
|
|||||||
|
|
||||||
const agentId = await ctx.manager.createAgent({
|
const agentId = await ctx.manager.createAgent({
|
||||||
cwd: ctx.tmpDir,
|
cwd: ctx.tmpDir,
|
||||||
permissionsMode: "auto_approve",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`✓ Agent created: ${agentId}`);
|
console.log(`✓ Agent created: ${agentId}`);
|
||||||
@@ -208,7 +206,6 @@ async function testPermissionRejectAll(ctx: TestContext) {
|
|||||||
|
|
||||||
const agentId = await ctx.manager.createAgent({
|
const agentId = await ctx.manager.createAgent({
|
||||||
cwd: ctx.tmpDir,
|
cwd: ctx.tmpDir,
|
||||||
permissionsMode: "reject_all",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`✓ Agent created: ${agentId}`);
|
console.log(`✓ Agent created: ${agentId}`);
|
||||||
@@ -282,7 +279,6 @@ async function testMultiplePrompts(ctx: TestContext) {
|
|||||||
|
|
||||||
const agentId = await ctx.manager.createAgent({
|
const agentId = await ctx.manager.createAgent({
|
||||||
cwd: ctx.tmpDir,
|
cwd: ctx.tmpDir,
|
||||||
permissionsMode: "auto_approve",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`✓ Agent created: ${agentId}`);
|
console.log(`✓ Agent created: ${agentId}`);
|
||||||
@@ -341,7 +337,6 @@ async function testUpdateStreaming(ctx: TestContext) {
|
|||||||
|
|
||||||
const agentId = await ctx.manager.createAgent({
|
const agentId = await ctx.manager.createAgent({
|
||||||
cwd: ctx.tmpDir,
|
cwd: ctx.tmpDir,
|
||||||
permissionsMode: "auto_approve",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`✓ Agent created: ${agentId}`);
|
console.log(`✓ Agent created: ${agentId}`);
|
||||||
@@ -408,7 +403,6 @@ async function testStateManagement(ctx: TestContext) {
|
|||||||
|
|
||||||
const agentId = await ctx.manager.createAgent({
|
const agentId = await ctx.manager.createAgent({
|
||||||
cwd: ctx.tmpDir,
|
cwd: ctx.tmpDir,
|
||||||
permissionsMode: "auto_approve",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`✓ Agent created: ${agentId}`);
|
console.log(`✓ Agent created: ${agentId}`);
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ export interface AgentInfo {
|
|||||||
type: "claude";
|
type: "claude";
|
||||||
sessionId?: string;
|
sessionId?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
|
currentModeId?: string;
|
||||||
|
availableModes?: SessionMode[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,12 +36,13 @@ export interface AgentUpdate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Permission modes for agent file operations
|
* Session mode definition from ACP
|
||||||
*/
|
*/
|
||||||
export type PermissionMode =
|
export interface SessionMode {
|
||||||
| "auto_approve" // Automatically approve all permissions
|
id: string;
|
||||||
| "ask_user" // Prompt user for each permission
|
name: string;
|
||||||
| "reject_all"; // Reject all permission requests
|
description?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for creating an agent
|
* Options for creating an agent
|
||||||
@@ -47,7 +50,7 @@ export type PermissionMode =
|
|||||||
export interface CreateAgentOptions {
|
export interface CreateAgentOptions {
|
||||||
cwd: string;
|
cwd: string;
|
||||||
initialPrompt?: string;
|
initialPrompt?: string;
|
||||||
permissionsMode?: PermissionMode;
|
initialMode?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -123,6 +123,12 @@ export const AgentCreatedMessageSchema = z.object({
|
|||||||
agentId: z.string(),
|
agentId: z.string(),
|
||||||
status: z.string(),
|
status: z.string(),
|
||||||
type: z.literal("claude"),
|
type: z.literal("claude"),
|
||||||
|
currentModeId: z.string().optional(),
|
||||||
|
availableModes: z.array(z.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
description: z.string().nullable().optional(),
|
||||||
|
})).optional(),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -151,6 +157,38 @@ export const AgentStatusMessageSchema = z.object({
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const SessionStateMessageSchema = z.object({
|
||||||
|
type: z.literal("session_state"),
|
||||||
|
payload: z.object({
|
||||||
|
agents: z.array(
|
||||||
|
z.object({
|
||||||
|
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(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
commands: z.array(
|
||||||
|
z.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
workingDirectory: z.string(),
|
||||||
|
currentCommand: z.string(),
|
||||||
|
isDead: z.boolean(),
|
||||||
|
exitCode: z.number().nullable(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
|
export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
|
||||||
ActivityLogMessageSchema,
|
ActivityLogMessageSchema,
|
||||||
AssistantChunkMessageSchema,
|
AssistantChunkMessageSchema,
|
||||||
@@ -162,6 +200,7 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
|
|||||||
AgentCreatedMessageSchema,
|
AgentCreatedMessageSchema,
|
||||||
AgentUpdateMessageSchema,
|
AgentUpdateMessageSchema,
|
||||||
AgentStatusMessageSchema,
|
AgentStatusMessageSchema,
|
||||||
|
SessionStateMessageSchema,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export type SessionOutboundMessage = z.infer<
|
export type SessionOutboundMessage = z.infer<
|
||||||
@@ -179,6 +218,7 @@ export type ConversationLoadedMessage = z.infer<typeof ConversationLoadedMessage
|
|||||||
export type AgentCreatedMessage = z.infer<typeof AgentCreatedMessageSchema>;
|
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>;
|
||||||
|
|
||||||
// Type exports for payload types
|
// Type exports for payload types
|
||||||
export type ActivityLogPayload = z.infer<typeof ActivityLogPayloadSchema>;
|
export type ActivityLogPayload = z.infer<typeof ActivityLogPayloadSchema>;
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ export class Session {
|
|||||||
// Per-session MCP client and tools
|
// Per-session MCP client and tools
|
||||||
private terminalMcpClient: Awaited<ReturnType<typeof experimental_createMCPClient>> | null = null;
|
private terminalMcpClient: Awaited<ReturnType<typeof experimental_createMCPClient>> | null = null;
|
||||||
private terminalTools: Record<string, any> | null = null;
|
private terminalTools: Record<string, any> | null = null;
|
||||||
|
private terminalManager: any | null = null;
|
||||||
private agentMcpClient: Awaited<ReturnType<typeof experimental_createMCPClient>> | null = null;
|
private agentMcpClient: Awaited<ReturnType<typeof experimental_createMCPClient>> | null = null;
|
||||||
private agentTools: Record<string, any> | null = null;
|
private agentTools: Record<string, any> | null = null;
|
||||||
private agentManager: AgentManager | null = null;
|
private agentManager: AgentManager | null = null;
|
||||||
@@ -156,6 +157,11 @@ export class Session {
|
|||||||
*/
|
*/
|
||||||
private async initializeTerminalMcp(): Promise<void> {
|
private async initializeTerminalMcp(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
// Create Terminal Manager directly
|
||||||
|
const { TerminalManager } = await import("./terminal-mcp/terminal-manager.js");
|
||||||
|
this.terminalManager = new TerminalManager(this.conversationId);
|
||||||
|
await this.terminalManager.initialize();
|
||||||
|
|
||||||
// Create Terminal MCP server with conversation-specific session
|
// Create Terminal MCP server with conversation-specific session
|
||||||
const server = await createTerminalMcpServer({
|
const server = await createTerminalMcpServer({
|
||||||
sessionName: this.conversationId
|
sessionName: this.conversationId
|
||||||
@@ -281,6 +287,50 @@ export class Session {
|
|||||||
messageCount: this.messages.length,
|
messageCount: this.messages.length,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Send current session state (live agents and commands)
|
||||||
|
await this.sendSessionState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send current session state (live agents and commands) to client
|
||||||
|
*/
|
||||||
|
private async sendSessionState(): Promise<void> {
|
||||||
|
try {
|
||||||
|
// Get live agents with session modes
|
||||||
|
const agents = this.agentManager?.listAgents() || [];
|
||||||
|
|
||||||
|
// Get live commands from terminal manager
|
||||||
|
let commands: any[] = [];
|
||||||
|
if (this.terminalManager) {
|
||||||
|
try {
|
||||||
|
commands = await this.terminalManager.listCommands();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
`[Session ${this.clientId}] Failed to list commands:`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit session state
|
||||||
|
this.emit({
|
||||||
|
type: "session_state",
|
||||||
|
payload: {
|
||||||
|
agents,
|
||||||
|
commands,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`[Session ${this.clientId}] Sent session state: ${agents.length} agents, ${commands.length} commands`
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
`[Session ${this.clientId}] Failed to send session state:`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -637,6 +687,8 @@ export class Session {
|
|||||||
if (result.structuredContent?.agentId) {
|
if (result.structuredContent?.agentId) {
|
||||||
const agentId = result.structuredContent.agentId;
|
const agentId = result.structuredContent.agentId;
|
||||||
const status = result.structuredContent.status;
|
const status = result.structuredContent.status;
|
||||||
|
const currentModeId = result.structuredContent.currentModeId;
|
||||||
|
const availableModes = result.structuredContent.availableModes;
|
||||||
|
|
||||||
// Subscribe to agent updates
|
// Subscribe to agent updates
|
||||||
this.subscribeToAgent(agentId);
|
this.subscribeToAgent(agentId);
|
||||||
@@ -648,6 +700,8 @@ export class Session {
|
|||||||
agentId,
|
agentId,
|
||||||
status,
|
status,
|
||||||
type: "claude",
|
type: "claude",
|
||||||
|
currentModeId,
|
||||||
|
availableModes,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,9 +74,26 @@ function App() {
|
|||||||
const [agents, setAgents] = useState<
|
const [agents, setAgents] = useState<
|
||||||
Map<
|
Map<
|
||||||
string,
|
string,
|
||||||
{ id: string; status: AgentStatus; createdAt: Date; type: "claude" }
|
{
|
||||||
|
id: string;
|
||||||
|
status: AgentStatus;
|
||||||
|
createdAt: Date;
|
||||||
|
type: "claude";
|
||||||
|
currentModeId?: string;
|
||||||
|
availableModes?: Array<{ id: string; name: string; description?: string | null }>;
|
||||||
|
}
|
||||||
>
|
>
|
||||||
>(new Map());
|
>(new Map());
|
||||||
|
const [commands, setCommands] = useState<
|
||||||
|
Array<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
workingDirectory: string;
|
||||||
|
currentCommand: string;
|
||||||
|
isDead: boolean;
|
||||||
|
exitCode: number | null;
|
||||||
|
}>
|
||||||
|
>([]);
|
||||||
const [agentUpdates, setAgentUpdates] = useState<
|
const [agentUpdates, setAgentUpdates] = useState<
|
||||||
Map<string, Array<{ timestamp: Date; notification: SessionNotification }>>
|
Map<string, Array<{ timestamp: Date; notification: SessionNotification }>>
|
||||||
>(new Map());
|
>(new Map());
|
||||||
@@ -345,10 +362,39 @@ function App() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Listen for session state
|
||||||
|
const unsubSessionState = ws.on("session_state", (message) => {
|
||||||
|
if (message.type !== "session_state") return;
|
||||||
|
const { agents: liveAgents, commands: liveCommands } = message.payload;
|
||||||
|
|
||||||
|
// Update agents with session modes
|
||||||
|
setAgents((prev) => {
|
||||||
|
const newMap = new Map(prev);
|
||||||
|
liveAgents.forEach((agent) => {
|
||||||
|
newMap.set(agent.id, {
|
||||||
|
id: agent.id,
|
||||||
|
status: agent.status as AgentStatus,
|
||||||
|
createdAt: new Date(agent.createdAt),
|
||||||
|
type: agent.type,
|
||||||
|
currentModeId: agent.currentModeId,
|
||||||
|
availableModes: agent.availableModes,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return newMap;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update commands
|
||||||
|
setCommands(liveCommands);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`[App] Session state loaded: ${liveAgents.length} agents, ${liveCommands.length} commands`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// Listen for agent created
|
// Listen for agent created
|
||||||
const unsubAgentCreated = ws.on("agent_created", (message) => {
|
const unsubAgentCreated = ws.on("agent_created", (message) => {
|
||||||
if (message.type !== "agent_created") return;
|
if (message.type !== "agent_created") return;
|
||||||
const { agentId, status, type } = message.payload;
|
const { agentId, status, type, currentModeId, availableModes } = message.payload;
|
||||||
|
|
||||||
setAgents((prev) => {
|
setAgents((prev) => {
|
||||||
const newMap = new Map(prev);
|
const newMap = new Map(prev);
|
||||||
@@ -357,6 +403,8 @@ function App() {
|
|||||||
status: status as AgentStatus,
|
status: status as AgentStatus,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
type,
|
type,
|
||||||
|
currentModeId,
|
||||||
|
availableModes,
|
||||||
});
|
});
|
||||||
return newMap;
|
return newMap;
|
||||||
});
|
});
|
||||||
@@ -411,6 +459,7 @@ function App() {
|
|||||||
unsubConversationLoaded();
|
unsubConversationLoaded();
|
||||||
unsubArtifact();
|
unsubArtifact();
|
||||||
unsubAudioOutput();
|
unsubAudioOutput();
|
||||||
|
unsubSessionState();
|
||||||
unsubAgentCreated();
|
unsubAgentCreated();
|
||||||
unsubAgentUpdate();
|
unsubAgentUpdate();
|
||||||
unsubAgentStatus();
|
unsubAgentStatus();
|
||||||
@@ -599,6 +648,8 @@ function App() {
|
|||||||
setCurrentAssistantMessage("");
|
setCurrentAssistantMessage("");
|
||||||
setArtifacts(new Map());
|
setArtifacts(new Map());
|
||||||
setCurrentArtifact(null);
|
setCurrentArtifact(null);
|
||||||
|
setAgents(new Map());
|
||||||
|
setCommands([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectProcess = (id: string, type: "terminal" | "agent") => {
|
const handleSelectProcess = (id: string, type: "terminal" | "agent") => {
|
||||||
@@ -868,6 +919,7 @@ function App() {
|
|||||||
<ActiveProcesses
|
<ActiveProcesses
|
||||||
terminals={[]}
|
terminals={[]}
|
||||||
agents={Array.from(agents.values())}
|
agents={Array.from(agents.values())}
|
||||||
|
commands={commands}
|
||||||
activeProcessId={activeProcessId}
|
activeProcessId={activeProcessId}
|
||||||
activeProcessType={activeView === "orchestrator" ? null : activeView}
|
activeProcessType={activeView === "orchestrator" ? null : activeView}
|
||||||
onSelectProcess={handleSelectProcess}
|
onSelectProcess={handleSelectProcess}
|
||||||
|
|||||||
@@ -1,9 +1,23 @@
|
|||||||
import { Terminal, Bot } from "lucide-react";
|
import { Terminal, Bot, Code } from "lucide-react";
|
||||||
import type { AgentStatus } from "../../server/acp/types.js";
|
import type { AgentStatus } from "../../server/acp/types.js";
|
||||||
|
|
||||||
export interface ActiveProcessesProps {
|
export interface ActiveProcessesProps {
|
||||||
terminals: Array<{ id: string; name: string }>;
|
terminals: Array<{ id: string; name: string }>;
|
||||||
agents: Array<{ id: string; status: AgentStatus; type: "claude" }>;
|
agents: Array<{
|
||||||
|
id: string;
|
||||||
|
status: AgentStatus;
|
||||||
|
type: "claude";
|
||||||
|
currentModeId?: string;
|
||||||
|
availableModes?: Array<{ id: string; name: string; description?: string | null }>;
|
||||||
|
}>;
|
||||||
|
commands: Array<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
workingDirectory: string;
|
||||||
|
currentCommand: string;
|
||||||
|
isDead: boolean;
|
||||||
|
exitCode: number | null;
|
||||||
|
}>;
|
||||||
activeProcessId: string | null;
|
activeProcessId: string | null;
|
||||||
activeProcessType: "terminal" | "agent" | null;
|
activeProcessType: "terminal" | "agent" | null;
|
||||||
onSelectProcess: (id: string, type: "terminal" | "agent") => void;
|
onSelectProcess: (id: string, type: "terminal" | "agent") => void;
|
||||||
@@ -33,9 +47,27 @@ function getAgentStatusLabel(status: AgentStatus): string {
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getModeName(modeId?: string, availableModes?: Array<{ id: string; name: string }>): string {
|
||||||
|
if (!modeId) return "unknown";
|
||||||
|
const mode = availableModes?.find(m => m.id === modeId);
|
||||||
|
return mode?.name || modeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModeColor(modeId?: string): string {
|
||||||
|
if (!modeId) return "#9ca3af"; // gray
|
||||||
|
|
||||||
|
// Color based on common mode types
|
||||||
|
if (modeId.includes("ask")) return "#f59e0b"; // orange - asks permission
|
||||||
|
if (modeId.includes("code")) return "#22c55e"; // green - writes code
|
||||||
|
if (modeId.includes("architect") || modeId.includes("plan")) return "#3b82f6"; // blue - plans
|
||||||
|
|
||||||
|
return "#9ca3af"; // gray - unknown
|
||||||
|
}
|
||||||
|
|
||||||
export function ActiveProcesses({
|
export function ActiveProcesses({
|
||||||
terminals,
|
terminals,
|
||||||
agents,
|
agents,
|
||||||
|
commands,
|
||||||
activeProcessId,
|
activeProcessId,
|
||||||
activeProcessType,
|
activeProcessType,
|
||||||
onSelectProcess,
|
onSelectProcess,
|
||||||
@@ -73,28 +105,70 @@ export function ActiveProcesses({
|
|||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{agents.map((agent) => (
|
{agents.map((agent) => {
|
||||||
<button
|
const modeName = getModeName(agent.currentModeId, agent.availableModes);
|
||||||
key={`agent-${agent.id}`}
|
const modesList = agent.availableModes?.map(m => m.name).join(", ") || "unknown";
|
||||||
className={`process-pill agent ${
|
|
||||||
activeProcessType === "agent" && activeProcessId === agent.id
|
return (
|
||||||
? "active"
|
<button
|
||||||
: ""
|
key={`agent-${agent.id}`}
|
||||||
}`}
|
className={`process-pill agent ${
|
||||||
onClick={() => onSelectProcess(agent.id, "agent")}
|
activeProcessType === "agent" && activeProcessId === agent.id
|
||||||
type="button"
|
? "active"
|
||||||
data-status={agent.status}
|
: ""
|
||||||
|
}`}
|
||||||
|
onClick={() => onSelectProcess(agent.id, "agent")}
|
||||||
|
type="button"
|
||||||
|
data-status={agent.status}
|
||||||
|
title={`Status: ${getAgentStatusLabel(agent.status)} | Mode: ${modeName} | Available: ${modesList}`}
|
||||||
|
>
|
||||||
|
<Bot size={14} />
|
||||||
|
<span className="process-name">
|
||||||
|
{agent.id.substring(0, 8)}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={`process-status-indicator ${agent.status}`}
|
||||||
|
style={{ backgroundColor: getAgentStatusColor(agent.status) }}
|
||||||
|
title={getAgentStatusLabel(agent.status)}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className="session-mode-badge"
|
||||||
|
style={{
|
||||||
|
backgroundColor: getModeColor(agent.currentModeId),
|
||||||
|
opacity: 0.3,
|
||||||
|
width: "6px",
|
||||||
|
height: "6px",
|
||||||
|
borderRadius: "50%",
|
||||||
|
marginLeft: "4px",
|
||||||
|
}}
|
||||||
|
title={`Mode: ${modeName}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{commands.map((command) => (
|
||||||
|
<div
|
||||||
|
key={`command-${command.id}`}
|
||||||
|
className="process-pill command"
|
||||||
|
title={`${command.currentCommand} | ${command.workingDirectory}${command.isDead ? ` | Exit code: ${command.exitCode}` : ""}`}
|
||||||
>
|
>
|
||||||
<Bot size={14} />
|
<Code size={14} />
|
||||||
<span className="process-name">
|
<span className="process-name">
|
||||||
{agent.id.substring(0, 8)}
|
{command.name || command.currentCommand.substring(0, 20)}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={`process-status-indicator ${agent.status}`}
|
className={`process-status-indicator ${command.isDead ? "dead" : "running"}`}
|
||||||
style={{ backgroundColor: getAgentStatusColor(agent.status) }}
|
style={{
|
||||||
title={getAgentStatusLabel(agent.status)}
|
backgroundColor: command.isDead
|
||||||
|
? command.exitCode === 0
|
||||||
|
? "#22c55e"
|
||||||
|
: "#ef4444"
|
||||||
|
: "#3b82f6",
|
||||||
|
}}
|
||||||
|
title={command.isDead ? `Exited: ${command.exitCode}` : "Running"}
|
||||||
/>
|
/>
|
||||||
</button>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user