| 175 | } |
| 176 | |
| 177 | export async function resolveAgentForStream( |
| 178 | opts: ResolveAgentOptions |
| 179 | ): Promise<Result<AgentResolutionResult, SendMessageError>> { |
| 180 | const { |
| 181 | workspaceId, |
| 182 | metadata, |
| 183 | runtime, |
| 184 | workspacePath, |
| 185 | requestedAgentId: rawAgentId, |
| 186 | disableWorkspaceAgents, |
| 187 | callerToolPolicy, |
| 188 | cfg, |
| 189 | emitError, |
| 190 | isAdvisorExperimentEnabled, |
| 191 | } = opts; |
| 192 | |
| 193 | const workspaceLog = log.withFields({ workspaceId, workspaceName: metadata.name }); |
| 194 | |
| 195 | // --- Agent ID resolution --- |
| 196 | // Precedence: |
| 197 | // - Child workspaces (tasks) use their persisted agentId/agentType. |
| 198 | // - Main workspaces use the requested agentId (frontend), falling back to exec. |
| 199 | const requestedAgentIds = metadata.parentWorkspaceId |
| 200 | ? [...resolvePersistedAgentIdCandidates(metadata), "exec"].filter( |
| 201 | (agentId, index, candidates) => candidates.indexOf(agentId) === index |
| 202 | ) |
| 203 | : [normalizeRequestedAgentId(rawAgentId)]; |
| 204 | const requestedAgentId = requestedAgentIds[0] ?? ("exec" as const); |
| 205 | let effectiveAgentId = requestedAgentId; |
| 206 | |
| 207 | // When disableWorkspaceAgents is true, skip workspace-specific agents entirely. |
| 208 | // Use project path so only built-in/global agents are available. This allows "unbricking" |
| 209 | // when iterating on agent files — a broken agent in the worktree won't affect message sending. |
| 210 | const agentDiscoveryCandidates = getAgentDiscoveryCandidates({ |
| 211 | metadata, |
| 212 | runtime, |
| 213 | workspacePath, |
| 214 | disableWorkspaceAgents, |
| 215 | cfg, |
| 216 | }); |
| 217 | let agentDiscoveryRuntime = agentDiscoveryCandidates[0]?.runtime ?? runtime; |
| 218 | let agentDiscoveryPath = agentDiscoveryCandidates[0]?.workspacePath ?? workspacePath; |
| 219 | |
| 220 | const isSubagentWorkspace = Boolean(metadata.parentWorkspaceId); |
| 221 | |
| 222 | // --- Load agent definition (with fallback to exec) --- |
| 223 | let agentDefinition: Awaited<ReturnType<typeof readAgentDefinition>> | undefined; |
| 224 | for (const candidateAgentId of requestedAgentIds) { |
| 225 | let fallbackDefinition: |
| 226 | | { |
| 227 | definition: Awaited<ReturnType<typeof readAgentDefinition>>; |
| 228 | discovery: AgentDiscoveryCandidate; |
| 229 | } |
| 230 | | undefined; |
| 231 | |
| 232 | for (const discovery of agentDiscoveryCandidates) { |
| 233 | try { |
| 234 | const definition = await readAgentDefinition( |