(input, context)
| 241 | renderToolResultMessage, |
| 242 | renderToolUseRejectedMessage, |
| 243 | async call(input, context) { |
| 244 | const isAgent = !!context.agentId |
| 245 | |
| 246 | const filePath = getPlanFilePath(context.agentId) |
| 247 | // CCR web UI may send an edited plan via permissionResult.updatedInput. |
| 248 | // queryHelpers.ts full-replaces finalInput, so when CCR sends {} (no edit) |
| 249 | // input.plan is undefined -> disk fallback. The internal inputSchema omits |
| 250 | // `plan` (normally injected by normalizeToolInput), hence the narrowing. |
| 251 | const inputPlan = |
| 252 | 'plan' in input && typeof input.plan === 'string' ? input.plan : undefined |
| 253 | const plan = inputPlan ?? getPlan(context.agentId) |
| 254 | |
| 255 | // Sync disk so VerifyPlanExecution / Read see the edit. Re-snapshot |
| 256 | // after: the only other persistFileSnapshotIfRemote call (api.ts) runs |
| 257 | // in normalizeToolInput, pre-permission — it captured the old plan. |
| 258 | if (inputPlan !== undefined && filePath) { |
| 259 | await writeFile(filePath, inputPlan, 'utf-8').catch(e => logError(e)) |
| 260 | void persistFileSnapshotIfRemote() |
| 261 | } |
| 262 | |
| 263 | // Check if this is a teammate that requires leader approval |
| 264 | if (isTeammate() && isPlanModeRequired()) { |
| 265 | // Plan is required for plan_mode_required teammates |
| 266 | if (!plan) { |
| 267 | throw new Error( |
| 268 | `No plan file found at ${filePath}. Please write your plan to this file before calling ExitPlanMode.`, |
| 269 | ) |
| 270 | } |
| 271 | const agentName = getAgentName() || 'unknown' |
| 272 | const teamName = getTeamName() |
| 273 | const requestId = generateRequestId( |
| 274 | 'plan_approval', |
| 275 | formatAgentId(agentName, teamName || 'default'), |
| 276 | ) |
| 277 | |
| 278 | const approvalRequest = { |
| 279 | type: 'plan_approval_request', |
| 280 | from: agentName, |
| 281 | timestamp: new Date().toISOString(), |
| 282 | planFilePath: filePath, |
| 283 | planContent: plan, |
| 284 | requestId, |
| 285 | } |
| 286 | |
| 287 | await writeToMailbox( |
| 288 | 'team-lead', |
| 289 | { |
| 290 | from: agentName, |
| 291 | text: jsonStringify(approvalRequest), |
| 292 | timestamp: new Date().toISOString(), |
| 293 | }, |
| 294 | teamName, |
| 295 | ) |
| 296 | |
| 297 | // Update task state to show awaiting approval (for in-process teammates) |
| 298 | const appState = context.getAppState() |
| 299 | const agentTaskId = findInProcessTeammateTaskId(agentName, appState) |
| 300 | if (agentTaskId) { |
nothing calls this directly
no test coverage detected