// Pure spawn-form model, kept when spawnForm.ts's imperative DOM form was // deleted (#253): the value/seed shapes, the request builder, and the preset // fallback. SpawnForm.tsx and useFastForward.ts render from these. import { splitCommand } from "../../toolIcon.js"; import type { CreateSessionRequest, SpawnPreset } from "../../types.js"; export const STRUCTURED_VIEW_TOOLS = new Set(["claude", "codex"]); export function supportsStructuredView(command: string): boolean { const tool = splitCommand(command).tool; return tool !== null && STRUCTURED_VIEW_TOOLS.has(tool); } /** Where a worktree's branch comes from: freshly created (default) or picked * from the repository's existing branches (#61). */ export type WorktreeBranchMode = "new" | "existing"; /** Seed values for the spawn form — used by "Spawn similar" to open the modal * with the same cwd, command, and name as an existing session, and by the * fast-forward fallback to pre-fill whatever it did resolve. */ export interface SpawnFormSeed { command?: string; cwd?: string; name?: string; worktreeEnabled?: boolean; worktreeBranch?: string; /** Whether the operator typed the branch themselves, rather than it having * been auto-synced from the session name. Carried separately because the * value alone cannot distinguish the two. */ worktreeBranchEdited?: boolean; worktreeBranchMode?: WorktreeBranchMode; cwdRawVisible?: boolean; } /** The form's current values, reported on every change. */ export interface SpawnFormValues { command: string; cwd: string; name: string; structuredView: boolean; autoApprove: boolean; worktreeEnabled: boolean; worktreeBranch: string; worktreeBranchEdited: boolean; /** "new" creates the branch; "existing" checks out a picked one (#61). */ worktreeBranchMode: WorktreeBranchMode; /** Whether the cwd lives in the raw text input (vs a picked recent row). */ cwdRawVisible?: boolean; } export function buildSpawnRequest( values: SpawnFormValues, baseRepo: string | null, ): { req: CreateSessionRequest } | { error: string } { if (!values.cwd) return { error: "Pick a working directory." }; if (!values.command) return { error: "Pick a command (or fill the custom command)." }; const metadata: Record = {}; const name = values.name.trim(); if (name) metadata.name = name; const req: CreateSessionRequest = { cwd: values.cwd, command: values.command, metadata, }; if (values.structuredView && supportsStructuredView(values.command)) { req.view = "Structured"; } if (values.autoApprove) { req.auto_approve = true; } if (values.worktreeEnabled) { const mode = values.worktreeBranchMode ?? "new"; const branch = values.worktreeBranch.trim(); if (!baseRepo) return { error: "Worktree needs a directory inside a git repository." }; if (!branch) { return { error: mode === "existing" ? "Pick an existing branch." : "Worktree requires a branch name." }; } req.worktree = { base_repo: baseRepo, branch, // Existing-mode picks must check out as-is; the daemon creates // anything else with `worktree add -b` (#56/#61). create_branch: mode !== "existing", }; } return { req }; } /** Built-ins used when the daemon's `/api/info` presets can't be fetched * (older daemon, transient error). Mirrors `maestro_config::builtin_presets`. * Exported for the fast-forward spawn's preset fallback (#67). */ export const FALLBACK_PRESETS: SpawnPreset[] = [ { id: "claude", label: "Claude", command: "claude" }, { id: "opencode", label: "opencode", command: "opencode" }, { id: "zsh", label: "Shell", command: "zsh" }, { id: "custom", label: "Custom…", command: "" }, ];