(
params: AgentRunParams,
ctx: AgentAdapterContext,
)
| 205 | capabilities: { structuredOutput: true, tools: true }, |
| 206 | |
| 207 | async run( |
| 208 | params: AgentRunParams, |
| 209 | ctx: AgentAdapterContext, |
| 210 | ): Promise<AgentRunResult> { |
| 211 | const { toolUseContext, canUseTool } = readHostBundle(ctx.host) |
| 212 | const appState = toolUseContext.getAppState() |
| 213 | const agentDef = resolveAgentDefinition(params.agentType, toolUseContext) |
| 214 | const model = mapWorkflowModel(params.model) |
| 215 | // coreAgentId: the tracking ID for the core-layer subagent (a string, used inside runAgent). |
| 216 | // Different from ctx.agentId (the engine's number seq, used for panel / killAgent routing) - two distinct concepts, must not be mixed up. |
| 217 | const coreAgentId = createAgentId() |
| 218 | |
| 219 | // isolation:'worktree' - run the agent inside an independent git worktree, so concurrent writes do not conflict. |
| 220 | let worktreeInfo: WorkflowWorktreeInfo | null = null |
| 221 | if (params.isolation === 'worktree') { |
| 222 | try { |
| 223 | worktreeInfo = await createAgentWorktree( |
| 224 | makeWorkflowWorktreeSlug(ctx.runId, coreAgentId), |
| 225 | ) |
| 226 | } catch (e) { |
| 227 | // fail-closed: when isolation fails, do not silently fall back to a shared cwd (otherwise concurrent writes race on data) |
| 228 | const detail = (e as Error).message |
| 229 | logForDebugging( |
| 230 | `workflow worktree creation failed (${agentDef.agentType}): ${detail}`, |
| 231 | ) |
| 232 | return { kind: 'dead', reason: 'worktree-failed', detail } |
| 233 | } |
| 234 | } |
| 235 | // runWithCwdOverride makes tools such as Bash/Read inside the agent see the worktree path |
| 236 | // (AsyncLocalStorage is preserved across awaits); the worktreePath parameter of runAgent only writes metadata. |
| 237 | const runInCwd = worktreeInfo |
| 238 | ? <T>(fn: () => T): T => |
| 239 | runWithCwdOverride(worktreeInfo!.worktreePath, fn) |
| 240 | : <T>(fn: () => T): T => fn() |
| 241 | |
| 242 | // Bridge ctx.signal -> runAgent.override.abortController. Otherwise, when the workflow is killed |
| 243 | // runAgent is unaware (root cause of 'x' being ineffective): the abort signal cannot reach the internal fetch, and the agent runs to completion. |
| 244 | // Single-agent kill goes through service.kill(runId, agentId) -> ports.taskRegistrar.killAgent -> |
| 245 | // agentAbortControllers.get(agentId).abort(); the same controller takes over both paths. |
| 246 | const agentAbort = new AbortController() |
| 247 | const onParentAbort = (): void => agentAbort.abort() |
| 248 | if (ctx.signal.aborted) { |
| 249 | agentAbort.abort() |
| 250 | } else { |
| 251 | ctx.signal.addEventListener('abort', onParentAbort, { once: true }) |
| 252 | } |
| 253 | if (typeof ctx.registerAgentAbort === 'function') { |
| 254 | ctx.registerAgentAbort(ctx.agentId, agentAbort) |
| 255 | } |
| 256 | |
| 257 | const workerPermissionContext = { |
| 258 | ...appState.toolPermissionContext, |
| 259 | mode: agentDef.permissionMode ?? 'acceptEdits', |
| 260 | } |
| 261 | const workerTools = assembleToolPool( |
| 262 | workerPermissionContext, |
| 263 | appState.mcp.tools, |
| 264 | ) |
nothing calls this directly
no test coverage detected