Download src/utils/swarm/backends/InProcessBackend.ts from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 10.5 kB
-
https://huggingface.co/chenbhao/codev/resolve/main/src/utils/swarm/backends/InProcessBackend.ts
- Command line
-
hf download hf://chenbhao/codev/src/utils/swarm/backends/InProcessBackend.ts
-
curl -L -o InProcessBackend.ts https://huggingface.co/chenbhao/codev/resolve/main/src/utils/swarm/backends/InProcessBackend.ts
10.5 kB
| import type { ToolUseContext } from '../../../Tool.js' | |
| import { | |
| findTeammateTaskByAgentId, | |
| requestTeammateShutdown, | |
| } from '../../../tasks/InProcessTeammateTask/InProcessTeammateTask.js' | |
| import { parseAgentId } from '../../../utils/agentId.js' | |
| import { logForDebugging } from '../../../utils/debug.js' | |
| import { jsonStringify } from '../../../utils/slowOperations.js' | |
| import { | |
| createShutdownRequestMessage, | |
| writeToMailbox, | |
| } from '../../../utils/teammateMailbox.js' | |
| import { startInProcessTeammate } from '../inProcessRunner.js' | |
| import { | |
| killInProcessTeammate, | |
| spawnInProcessTeammate, | |
| } from '../spawnInProcess.js' | |
| import type { | |
| TeammateExecutor, | |
| TeammateMessage, | |
| TeammateSpawnConfig, | |
| TeammateSpawnResult, | |
| } from './types.js' | |
| /** | |
| * InProcessBackend implements TeammateExecutor for in-process teammates. | |
| * | |
| * Unlike pane-based backends (tmux/iTerm2), in-process teammates run in the | |
| * same Node.js process with isolated context via AsyncLocalStorage. They: | |
| * - Share resources (API client, MCP connections) with the leader | |
| * - Communicate via file-based mailbox (same as pane-based teammates) | |
| * - Are terminated via AbortController (not kill-pane) | |
| * | |
| * IMPORTANT: Before spawning, call setContext() to provide the ToolUseContext | |
| * needed for AppState access. This is intended for use via the TeammateExecutor | |
| * abstraction (getTeammateExecutor() in registry.ts). | |
| */ | |
| export class InProcessBackend implements TeammateExecutor { | |
| readonly type = 'in-process' as const | |
| /** | |
| * Tool use context for AppState access. | |
| * Must be set via setContext() before spawn() is called. | |
| */ | |
| private context: ToolUseContext | null = null | |
| /** | |
| * Sets the ToolUseContext for this backend. | |
| * Called by TeammateTool before spawning to provide AppState access. | |
| */ | |
| setContext(context: ToolUseContext): void { | |
| this.context = context | |
| } | |
| /** | |
| * In-process backend is always available (no external dependencies). | |
| */ | |
| async isAvailable(): Promise<boolean> { | |
| return true | |
| } | |
| /** | |
| * Spawns an in-process teammate. | |
| * | |
| * Uses spawnInProcessTeammate() to: | |
| * 1. Create TeammateContext via createTeammateContext() | |
| * 2. Create independent AbortController (not linked to parent) | |
| * 3. Register teammate in AppState.tasks | |
| * 4. Start agent execution via startInProcessTeammate() | |
| * 5. Return spawn result with agentId, taskId, abortController | |
| */ | |
| async spawn(config: TeammateSpawnConfig): Promise<TeammateSpawnResult> { | |
| if (!this.context) { | |
| logForDebugging( | |
| `[InProcessBackend] spawn() called without context for ${config.name}`, | |
| ) | |
| return { | |
| success: false, | |
| agentId: `${config.name}@${config.teamName}`, | |
| error: | |
| 'InProcessBackend not initialized. Call setContext() before spawn().', | |
| } | |
| } | |
| logForDebugging(`[InProcessBackend] spawn() called for ${config.name}`) | |
| const result = await spawnInProcessTeammate( | |
| { | |
| name: config.name, | |
| teamName: config.teamName, | |
| prompt: config.prompt, | |
| color: config.color, | |
| planModeRequired: config.planModeRequired ?? false, | |
| }, | |
| this.context, | |
| ) | |
| // If spawn succeeded, start the agent execution loop | |
| if ( | |
| result.success && | |
| result.taskId && | |
| result.teammateContext && | |
| result.abortController | |
| ) { | |
| // Start the agent loop in the background (fire-and-forget) | |
| // The prompt is passed through the task state and config | |
| startInProcessTeammate({ | |
| identity: { | |
| agentId: result.agentId, | |
| agentName: config.name, | |
| teamName: config.teamName, | |
| color: config.color, | |
| planModeRequired: config.planModeRequired ?? false, | |
| parentSessionId: result.teammateContext.parentSessionId, | |
| }, | |
| taskId: result.taskId, | |
| prompt: config.prompt, | |
| teammateContext: result.teammateContext, | |
| // Strip messages: the teammate never reads toolUseContext.messages | |
| // (runAgent overrides it via createSubagentContext). Passing the | |
| // parent's conversation would pin it for the teammate's lifetime. | |
| toolUseContext: { ...this.context, messages: [] }, | |
| abortController: result.abortController, | |
| model: config.model, | |
| systemPrompt: config.systemPrompt, | |
| systemPromptMode: config.systemPromptMode, | |
| allowedTools: config.permissions, | |
| allowPermissionPrompts: config.allowPermissionPrompts, | |
| }) | |
| logForDebugging( | |
| `[InProcessBackend] Started agent execution for ${result.agentId}`, | |
| ) | |
| } | |
| return { | |
| success: result.success, | |
| agentId: result.agentId, | |
| taskId: result.taskId, | |
| abortController: result.abortController, | |
| error: result.error, | |
| } | |
| } | |
| /** | |
| * Sends a message to an in-process teammate. | |
| * | |
| * All teammates use file-based mailboxes for simplicity. | |
| */ | |
| async sendMessage(agentId: string, message: TeammateMessage): Promise<void> { | |
| logForDebugging( | |
| `[InProcessBackend] sendMessage() to ${agentId}: ${message.text.substring(0, 50)}...`, | |
| ) | |
| // Parse agentId to get agentName and teamName | |
| // agentId format: "agentName@teamName" (e.g., "researcher@my-team") | |
| const parsed = parseAgentId(agentId) | |
| if (!parsed) { | |
| logForDebugging(`[InProcessBackend] Invalid agentId format: ${agentId}`) | |
| throw new Error( | |
| `Invalid agentId format: ${agentId}. Expected format: agentName@teamName`, | |
| ) | |
| } | |
| const { agentName, teamName } = parsed | |
| // Write to file-based mailbox | |
| await writeToMailbox( | |
| agentName, | |
| { | |
| text: message.text, | |
| from: message.from, | |
| color: message.color, | |
| timestamp: message.timestamp ?? new Date().toISOString(), | |
| }, | |
| teamName, | |
| ) | |
| logForDebugging(`[InProcessBackend] sendMessage() completed for ${agentId}`) | |
| } | |
| /** | |
| * Gracefully terminates an in-process teammate. | |
| * | |
| * Sends a shutdown request message to the teammate and sets the | |
| * shutdownRequested flag. The teammate processes the request and | |
| * either approves (exits) or rejects (continues working). | |
| * | |
| * Unlike pane-based teammates, in-process teammates handle their own | |
| * exit via the shutdown flow - no external killPane() is needed. | |
| */ | |
| async terminate(agentId: string, reason?: string): Promise<boolean> { | |
| logForDebugging( | |
| `[InProcessBackend] terminate() called for ${agentId}: ${reason}`, | |
| ) | |
| if (!this.context) { | |
| logForDebugging( | |
| `[InProcessBackend] terminate() failed: no context set for ${agentId}`, | |
| ) | |
| return false | |
| } | |
| // Get current AppState to find the task | |
| const state = this.context.getAppState() | |
| const task = findTeammateTaskByAgentId(agentId, state.tasks) | |
| if (!task) { | |
| logForDebugging( | |
| `[InProcessBackend] terminate() failed: task not found for ${agentId}`, | |
| ) | |
| return false | |
| } | |
| // Don't send another shutdown request if one is already pending | |
| if (task.shutdownRequested) { | |
| logForDebugging( | |
| `[InProcessBackend] terminate(): shutdown already requested for ${agentId}`, | |
| ) | |
| return true | |
| } | |
| // Generate deterministic request ID | |
| const requestId = `shutdown-${agentId}-${Date.now()}` | |
| // Create shutdown request message | |
| const shutdownRequest = createShutdownRequestMessage({ | |
| requestId, | |
| from: 'team-lead', // Terminate is always called by the leader | |
| reason, | |
| }) | |
| // Send to teammate's mailbox | |
| const teammateAgentName = task.identity.agentName | |
| await writeToMailbox( | |
| teammateAgentName, | |
| { | |
| from: 'team-lead', | |
| text: jsonStringify(shutdownRequest), | |
| timestamp: new Date().toISOString(), | |
| }, | |
| task.identity.teamName, | |
| ) | |
| // Mark the task as shutdown requested | |
| requestTeammateShutdown(task.id, this.context.setAppState) | |
| logForDebugging( | |
| `[InProcessBackend] terminate() sent shutdown request to ${agentId}`, | |
| ) | |
| return true | |
| } | |
| /** | |
| * Force kills an in-process teammate immediately. | |
| * | |
| * Uses the teammate's AbortController to cancel all async operations | |
| * and updates the task state to 'killed'. | |
| */ | |
| async kill(agentId: string): Promise<boolean> { | |
| logForDebugging(`[InProcessBackend] kill() called for ${agentId}`) | |
| if (!this.context) { | |
| logForDebugging( | |
| `[InProcessBackend] kill() failed: no context set for ${agentId}`, | |
| ) | |
| return false | |
| } | |
| // Get current AppState to find the task | |
| const state = this.context.getAppState() | |
| const task = findTeammateTaskByAgentId(agentId, state.tasks) | |
| if (!task) { | |
| logForDebugging( | |
| `[InProcessBackend] kill() failed: task not found for ${agentId}`, | |
| ) | |
| return false | |
| } | |
| // Kill the teammate via the existing helper function | |
| const killed = killInProcessTeammate(task.id, this.context.setAppState) | |
| logForDebugging( | |
| `[InProcessBackend] kill() ${killed ? 'succeeded' : 'failed'} for ${agentId}`, | |
| ) | |
| return killed | |
| } | |
| /** | |
| * Checks if an in-process teammate is still active. | |
| * | |
| * Returns true if the teammate exists, has status 'running', | |
| * and its AbortController has not been aborted. | |
| */ | |
| async isActive(agentId: string): Promise<boolean> { | |
| logForDebugging(`[InProcessBackend] isActive() called for ${agentId}`) | |
| if (!this.context) { | |
| logForDebugging( | |
| `[InProcessBackend] isActive() failed: no context set for ${agentId}`, | |
| ) | |
| return false | |
| } | |
| // Get current AppState to find the task | |
| const state = this.context.getAppState() | |
| const task = findTeammateTaskByAgentId(agentId, state.tasks) | |
| if (!task) { | |
| logForDebugging( | |
| `[InProcessBackend] isActive(): task not found for ${agentId}`, | |
| ) | |
| return false | |
| } | |
| // Check if task is running and not aborted | |
| const isRunning = task.status === 'running' | |
| const isAborted = task.abortController?.signal.aborted ?? true | |
| const active = isRunning && !isAborted | |
| logForDebugging( | |
| `[InProcessBackend] isActive() for ${agentId}: ${active} (running=${isRunning}, aborted=${isAborted})`, | |
| ) | |
| return active | |
| } | |
| } | |
| /** | |
| * Factory function to create an InProcessBackend instance. | |
| * Used by the registry (Task #8) to get backend instances. | |
| */ | |
| export function createInProcessBackend(): InProcessBackend { | |
| return new InProcessBackend() | |
| } | |