* Handle spawn operation using split-pane view (default). * When inside tmux: Creates teammates in a shared window with leader on left, teammates on right. * When outside tmux: Creates a ncode-swarm session with all teammates in a tiled layout.
( input: SpawnInput, context: ToolUseContext, )
| 292 | * When outside tmux: Creates a ncode-swarm session with all teammates in a tiled layout. |
| 293 | */ |
| 294 | async function handleSpawnSplitPane( |
| 295 | input: SpawnInput, |
| 296 | context: ToolUseContext, |
| 297 | ): Promise<{ data: SpawnOutput }> { |
| 298 | const { setAppState, getAppState } = context |
| 299 | const { name, prompt, agent_type, cwd, plan_mode_required } = input |
| 300 | |
| 301 | // Resolve model: 'inherit' → leader's model; undefined → default Opus |
| 302 | const model = resolveTeammateModel(input.model, getAppState().mainLoopModel) |
| 303 | |
| 304 | if (!name || !prompt) { |
| 305 | throw new Error('name and prompt are required for spawn operation') |
| 306 | } |
| 307 | |
| 308 | // Get team name from input or inherit from leader's team context |
| 309 | const appState = getAppState() |
| 310 | const teamName = input.team_name || appState.teamContext?.teamName |
| 311 | |
| 312 | if (!teamName) { |
| 313 | throw new Error( |
| 314 | 'team_name is required for spawn operation. Either provide team_name in input or call spawnTeam first to establish team context.', |
| 315 | ) |
| 316 | } |
| 317 | |
| 318 | // Generate unique name if duplicate exists in team |
| 319 | const uniqueName = await generateUniqueTeammateName(name, teamName) |
| 320 | |
| 321 | // Sanitize the name to prevent @ in agent IDs (would break agentName@teamName format) |
| 322 | const sanitizedName = sanitizeAgentName(uniqueName) |
| 323 | |
| 324 | // Generate deterministic agent ID from name and team |
| 325 | const teammateId = formatAgentId(sanitizedName, teamName) |
| 326 | const workingDir = cwd || getCwd() |
| 327 | |
| 328 | // Detect the appropriate backend and check if setup is needed |
| 329 | let detectionResult = await detectAndGetBackend() |
| 330 | |
| 331 | // If in iTerm2 but it2 isn't set up, prompt the user |
| 332 | if (detectionResult.needsIt2Setup && context.setToolJSX) { |
| 333 | const tmuxAvailable = await isTmuxAvailable() |
| 334 | |
| 335 | // Show the setup prompt and wait for user decision |
| 336 | const setupResult = await new Promise< |
| 337 | 'installed' | 'use-tmux' | 'cancelled' |
| 338 | >(resolve => { |
| 339 | context.setToolJSX!({ |
| 340 | jsx: React.createElement(It2SetupPrompt, { |
| 341 | onDone: resolve, |
| 342 | tmuxAvailable, |
| 343 | }), |
| 344 | shouldHidePromptInput: true, |
| 345 | }) |
| 346 | }) |
| 347 | |
| 348 | // Clear the JSX |
| 349 | context.setToolJSX(null) |
| 350 | |
| 351 | if (setupResult === 'cancelled') { |
no test coverage detected