{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../../src/missions/actions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAWlD,OAAO,EAEN,KAAK,eAAe,EAEpB,KAAK,cAAc,EAKnB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EAEvB,KAAK,kBAAkB,EAEvB,MAAM,YAAY,CAAC;AAGpB,eAAO,MAAM,eAAe,sHAOlB,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D,MAAM,WAAW,kBAAkB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,GAAG;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC;IACrC,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,CAAC,EAAE,eAAe,EAAE,CAAC;IAC9B,QAAQ,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC,CAAC;IACpD,SAAS,CAAC,EAAE,KAAK,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;QACnB,cAAc,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC,CAAC;CACH;AAED,MAAM,WAAW,mBAAmB;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,oBAAoB;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAiBD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,kBAAkB,CAgDxE;AA2PD,wBAAgB,mBAAmB,CAClC,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,mBAAmB,EAC3B,GAAG,EAAE,oBAAoB,GACvB,eAAe,CAAC,OAAO,CAAC,CAyI1B","sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { AgentToolResult } from \"@lpb-work/pi-agent-core\";\nimport type { Details } from \"../shared/types.ts\";\nimport {\n\tcreateMission,\n\tlistGlobalMissions,\n\tlistMissions,\n\tmissionRecordPath,\n\treadMission,\n\tresolveMissionStoreLocation,\n\tupdateMission,\n\tvalidateMissionId,\n} from \"./store.ts\";\nimport {\n\tMISSION_STATUSES,\n\ttype MissionArtifact,\n\ttype MissionArtifactKind,\n\ttype MissionReceipt,\n\ttype MissionReceiptKind,\n\ttype MissionReceiptStatus,\n\ttype MissionRecord,\n\ttype MissionRunMode,\n\ttype MissionStatus,\n\ttype MissionStoreConfig,\n\ttype MissionStoreLocation,\n\ttype MissionTokenBudget,\n\ttype MissionUpdateInput,\n} from \"./types.ts\";\nimport { missionStatePath } from \"./workflow-state.ts\";\n\nexport const MISSION_ACTIONS = [\n\t\"mission.create\",\n\t\"mission.list\",\n\t\"mission.show\",\n\t\"mission.update\",\n\t\"mission.attach-run\",\n\t\"mission.close\",\n] as const;\n\nexport type MissionAction = (typeof MISSION_ACTIONS)[number];\n\nexport interface MissionLaunchInput {\n\ttitle: string;\n\tobjective?: string;\n\tgoal?: true;\n\tbudget?: MissionTokenBudget;\n\tlabels?: string[];\n}\n\nexport interface MissionUpdateToolInput {\n\ttitle?: string;\n\tobjective?: string;\n\tgoal?: boolean | { paused: boolean };\n\tbudget?: MissionTokenBudget;\n\tstatus?: MissionStatus;\n\tsummary?: string;\n\tlabels?: string[];\n\tartifacts?: MissionArtifact[];\n\treceipts?: Array<Omit<MissionReceipt, \"createdAt\">>;\n\tdecisions?: Array<{\n\t\ttitle: string;\n\t\tprompt?: string;\n\t\toptions?: string[];\n\t\trecommendation?: string;\n\t}>;\n}\n\nexport interface MissionActionParams {\n\tmissionId?: string;\n\tmission?: unknown;\n\tmissionUpdate?: unknown;\n\tmissionStatus?: string;\n\tmissionScope?: string;\n\tid?: string;\n\trunId?: string;\n\tdir?: string;\n\trunMode?: string;\n\trunStatus?: string;\n\tagent?: string;\n\tsummary?: string;\n}\n\ninterface MissionActionContext {\n\tcwd: string;\n\tcurrentSessionId?: string;\n\tconfig?: MissionStoreConfig;\n\tagentDir?: string;\n}\n\nfunction textResult(text: string, details: Details): AgentToolResult<Details> {\n\treturn { content: [{ type: \"text\", text }], details };\n}\n\nfunction requireMissionId(params: MissionActionParams): string {\n\treturn validateMissionId(params.missionId, \"missionId\");\n}\n\nfunction validateStatus(value: unknown, label: string): MissionStatus {\n\tif (typeof value !== \"string\" || !(MISSION_STATUSES as readonly string[]).includes(value)) {\n\t\tthrow new Error(`${label} must be one of ${MISSION_STATUSES.join(\", \")}`);\n\t}\n\treturn value as MissionStatus;\n}\n\nexport function validateMissionLaunch(value: unknown): MissionLaunchInput {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) throw new Error(\"mission must be an object\");\n\tconst input = value as Record<string, unknown>;\n\tfor (const key of Object.keys(input)) {\n\t\tif (\n\t\t\tkey !== \"title\" &&\n\t\t\tkey !== \"summary\" &&\n\t\t\tkey !== \"objective\" &&\n\t\t\tkey !== \"goal\" &&\n\t\t\tkey !== \"budget\" &&\n\t\t\tkey !== \"labels\"\n\t\t)\n\t\t\tthrow new Error(`mission.${key} is unknown`);\n\t}\n\tif (input.title !== undefined && input.summary !== undefined)\n\t\tthrow new Error(\"mission.title and mission.summary cannot both be set\");\n\tconst title = input.title ?? input.summary;\n\tif (typeof title !== \"string\" || !title.trim())\n\t\tthrow new Error(\"mission.title or mission.summary must be a non-empty string\");\n\tif (input.objective !== undefined && (typeof input.objective !== \"string\" || !input.objective.trim()))\n\t\tthrow new Error(\"mission.objective must be a non-empty string\");\n\tif (input.goal !== undefined && input.goal !== true) throw new Error(\"mission.goal must be true when supplied\");\n\tconst budget = input.budget;\n\tif (\n\t\tbudget !== undefined &&\n\t\t(!budget ||\n\t\t\ttypeof budget !== \"object\" ||\n\t\t\tArray.isArray(budget) ||\n\t\t\t!Number.isSafeInteger((budget as { tokens?: unknown }).tokens) ||\n\t\t\t(budget as { tokens: number }).tokens < 1)\n\t) {\n\t\tthrow new Error(\"mission.budget.tokens must be a positive integer\");\n\t}\n\tif (input.goal === true && budget === undefined)\n\t\tthrow new Error(\"mission.budget is required when mission.goal is true\");\n\tif (\n\t\tinput.labels !== undefined &&\n\t\t(!Array.isArray(input.labels) || input.labels.some((label) => typeof label !== \"string\" || !label.trim()))\n\t) {\n\t\tthrow new Error(\"mission.labels must contain only non-empty strings\");\n\t}\n\treturn {\n\t\ttitle: title.trim(),\n\t\t...(typeof input.objective === \"string\" ? { objective: input.objective.trim() } : {}),\n\t\t...(input.goal === true ? { goal: true as const } : {}),\n\t\t...(budget !== undefined ? { budget: { tokens: (budget as { tokens: number }).tokens } } : {}),\n\t\t...(input.labels !== undefined ? { labels: input.labels.map((label) => label.trim()) } : {}),\n\t};\n}\n\nfunction validateArtifact(value: unknown, index: number): MissionArtifact {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value))\n\t\tthrow new Error(`missionUpdate.artifacts[${index}] must be an object`);\n\tconst input = value as Record<string, unknown>;\n\tconst kinds: MissionArtifactKind[] = [\"status\", \"output\", \"patch\", \"manifest\", \"review\", \"note\", \"other\"];\n\tif (typeof input.kind !== \"string\" || !kinds.includes(input.kind as MissionArtifactKind))\n\t\tthrow new Error(`missionUpdate.artifacts[${index}].kind is invalid`);\n\tif (typeof input.path !== \"string\" || !input.path.trim())\n\t\tthrow new Error(`missionUpdate.artifacts[${index}].path must be a non-empty string`);\n\tif (input.description !== undefined && (typeof input.description !== \"string\" || !input.description.trim()))\n\t\tthrow new Error(`missionUpdate.artifacts[${index}].description must be a non-empty string`);\n\treturn {\n\t\tkind: input.kind as MissionArtifactKind,\n\t\tpath: input.path,\n\t\t...(typeof input.description === \"string\" ? { description: input.description } : {}),\n\t};\n}\n\nfunction validateReceipt(value: unknown, index: number): Omit<MissionReceipt, \"createdAt\"> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value))\n\t\tthrow new Error(`missionUpdate.receipts[${index}] must be an object`);\n\tconst input = value as Record<string, unknown>;\n\tfor (const key of Object.keys(input)) {\n\t\tif (![\"kind\", \"status\", \"title\", \"url\", \"description\"].includes(key))\n\t\t\tthrow new Error(`missionUpdate.receipts[${index}].${key} is unknown`);\n\t}\n\tconst kinds: MissionReceiptKind[] = [\"pull_request\", \"ci\", \"deployment\", \"release\"];\n\tconst statuses: MissionReceiptStatus[] = [\"pending\", \"ready\", \"succeeded\", \"failed\"];\n\tif (typeof input.kind !== \"string\" || !kinds.includes(input.kind as MissionReceiptKind))\n\t\tthrow new Error(`missionUpdate.receipts[${index}].kind is invalid`);\n\tif (typeof input.status !== \"string\" || !statuses.includes(input.status as MissionReceiptStatus))\n\t\tthrow new Error(`missionUpdate.receipts[${index}].status is invalid`);\n\tfor (const field of [\"title\", \"url\"] as const) {\n\t\tif (typeof input[field] !== \"string\" || !input[field].trim())\n\t\t\tthrow new Error(`missionUpdate.receipts[${index}].${field} must be a non-empty string`);\n\t}\n\ttry {\n\t\tnew URL(input.url as string);\n\t} catch {\n\t\tthrow new Error(`missionUpdate.receipts[${index}].url must be an absolute URL`);\n\t}\n\tif (input.description !== undefined && (typeof input.description !== \"string\" || !input.description.trim()))\n\t\tthrow new Error(`missionUpdate.receipts[${index}].description must be a non-empty string`);\n\treturn {\n\t\tkind: input.kind as MissionReceiptKind,\n\t\tstatus: input.status as MissionReceiptStatus,\n\t\ttitle: (input.title as string).trim(),\n\t\turl: (input.url as string).trim(),\n\t\t...(typeof input.description === \"string\" ? { description: input.description.trim() } : {}),\n\t};\n}\n\nfunction validateMissionUpdate(value: unknown): MissionUpdateInput {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) throw new Error(\"missionUpdate must be an object\");\n\tconst input = value as Record<string, unknown>;\n\tfor (const key of Object.keys(input)) {\n\t\tif (\n\t\t\t![\n\t\t\t\t\"title\",\n\t\t\t\t\"objective\",\n\t\t\t\t\"goal\",\n\t\t\t\t\"budget\",\n\t\t\t\t\"status\",\n\t\t\t\t\"summary\",\n\t\t\t\t\"labels\",\n\t\t\t\t\"artifacts\",\n\t\t\t\t\"receipts\",\n\t\t\t\t\"decisions\",\n\t\t\t].includes(key)\n\t\t)\n\t\t\tthrow new Error(`missionUpdate.${key} is unknown`);\n\t}\n\tconst update: MissionUpdateInput = {};\n\tfor (const field of [\"title\", \"objective\", \"summary\"] as const) {\n\t\tconst candidate = input[field];\n\t\tif (candidate === undefined) continue;\n\t\tif (typeof candidate !== \"string\" || !candidate.trim())\n\t\t\tthrow new Error(`missionUpdate.${field} must be a non-empty string`);\n\t\tupdate[field] = candidate.trim();\n\t}\n\tif (input.goal !== undefined) {\n\t\tif (typeof input.goal === \"boolean\") update.goal = input.goal ? { status: \"active\" } : false;\n\t\telse {\n\t\t\tif (\n\t\t\t\t!input.goal ||\n\t\t\t\ttypeof input.goal !== \"object\" ||\n\t\t\t\tArray.isArray(input.goal) ||\n\t\t\t\ttypeof (input.goal as { paused?: unknown }).paused !== \"boolean\" ||\n\t\t\t\tObject.keys(input.goal).some((key) => key !== \"paused\")\n\t\t\t) {\n\t\t\t\tthrow new Error(\"missionUpdate.goal must be boolean or { paused: boolean }\");\n\t\t\t}\n\t\t\tupdate.goal = { status: (input.goal as { paused: boolean }).paused ? \"paused\" : \"active\" };\n\t\t}\n\t}\n\tif (input.budget !== undefined) {\n\t\tif (\n\t\t\t!input.budget ||\n\t\t\ttypeof input.budget !== \"object\" ||\n\t\t\tArray.isArray(input.budget) ||\n\t\t\t!Number.isSafeInteger((input.budget as { tokens?: unknown }).tokens) ||\n\t\t\t(input.budget as { tokens: number }).tokens < 1\n\t\t) {\n\t\t\tthrow new Error(\"missionUpdate.budget.tokens must be a positive integer\");\n\t\t}\n\t\tupdate.budget = { tokens: (input.budget as { tokens: number }).tokens };\n\t}\n\tif (input.status !== undefined) update.status = validateStatus(input.status, \"missionUpdate.status\");\n\tif (input.labels !== undefined) {\n\t\tif (!Array.isArray(input.labels) || input.labels.some((label) => typeof label !== \"string\" || !label.trim()))\n\t\t\tthrow new Error(\"missionUpdate.labels must contain only non-empty strings\");\n\t\tupdate.labels = input.labels as string[];\n\t}\n\tif (input.artifacts !== undefined) {\n\t\tif (!Array.isArray(input.artifacts)) throw new Error(\"missionUpdate.artifacts must be an array\");\n\t\tupdate.addArtifacts = input.artifacts.map(validateArtifact);\n\t}\n\tif (input.receipts !== undefined) {\n\t\tif (!Array.isArray(input.receipts)) throw new Error(\"missionUpdate.receipts must be an array\");\n\t\tupdate.addReceipts = input.receipts.map(validateReceipt);\n\t}\n\tif (input.decisions !== undefined) {\n\t\tif (!Array.isArray(input.decisions)) throw new Error(\"missionUpdate.decisions must be an array\");\n\t\tupdate.addDecisions = input.decisions.map((value, index) => {\n\t\t\tif (!value || typeof value !== \"object\" || Array.isArray(value))\n\t\t\t\tthrow new Error(`missionUpdate.decisions[${index}] must be an object`);\n\t\t\tconst decision = value as Record<string, unknown>;\n\t\t\tif (typeof decision.title !== \"string\" || !decision.title.trim())\n\t\t\t\tthrow new Error(`missionUpdate.decisions[${index}].title must be a non-empty string`);\n\t\t\tif (\n\t\t\t\tdecision.options !== undefined &&\n\t\t\t\t(!Array.isArray(decision.options) ||\n\t\t\t\t\tdecision.options.some((option) => typeof option !== \"string\" || !option.trim()))\n\t\t\t)\n\t\t\t\tthrow new Error(`missionUpdate.decisions[${index}].options must contain only non-empty strings`);\n\t\t\treturn {\n\t\t\t\ttitle: decision.title.trim(),\n\t\t\t\t...(typeof decision.prompt === \"string\" && decision.prompt.trim() ? { prompt: decision.prompt } : {}),\n\t\t\t\t...(Array.isArray(decision.options) ? { options: decision.options as string[] } : {}),\n\t\t\t\t...(typeof decision.recommendation === \"string\" && decision.recommendation.trim()\n\t\t\t\t\t? { recommendation: decision.recommendation }\n\t\t\t\t\t: {}),\n\t\t\t};\n\t\t});\n\t}\n\tif (Object.keys(update).length === 0) throw new Error(\"missionUpdate must include at least one supported field\");\n\treturn update;\n}\n\nfunction refreshLinkedRunStatus(\n\tlocation: MissionStoreLocation,\n\trecord: MissionRecord,\n): { record: MissionRecord; warnings: string[] } {\n\tconst warnings: string[] = [];\n\tconst updates = record.runs.flatMap((run) => {\n\t\tif (!run.asyncDir) return [];\n\t\tconst statusPath = path.join(run.asyncDir, \"status.json\");\n\t\tif (!fs.existsSync(statusPath)) return [];\n\t\tlet status: unknown;\n\t\ttry {\n\t\t\tstatus = JSON.parse(fs.readFileSync(statusPath, \"utf-8\"));\n\t\t} catch (error) {\n\t\t\twarnings.push(\n\t\t\t\t`Failed to read linked run status '${statusPath}': ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t\treturn [];\n\t\t}\n\t\tif (!status || typeof status !== \"object\" || Array.isArray(status)) {\n\t\t\twarnings.push(`Linked run status '${statusPath}' must be an object`);\n\t\t\treturn [];\n\t\t}\n\t\tconst state = (status as { state?: unknown }).state;\n\t\tif (typeof state !== \"string\" || !state.trim()) {\n\t\t\twarnings.push(`Linked run status '${statusPath}' is missing state`);\n\t\t\treturn [];\n\t\t}\n\t\tif (state === run.status) return [];\n\t\treturn [\n\t\t\t{\n\t\t\t\t...run,\n\t\t\t\tstatus: state,\n\t\t\t\t...(![\"queued\", \"running\"].includes(state) && !run.completedAt\n\t\t\t\t\t? { completedAt: new Date().toISOString() }\n\t\t\t\t\t: {}),\n\t\t\t},\n\t\t];\n\t});\n\tif (updates.length === 0) return { record, warnings };\n\tconst merged = record.runs.map(\n\t\t(run) => updates.find((update) => update.runId === run.runId && update.childIndex === run.childIndex) ?? run,\n\t);\n\tconst states = merged.map((run) => run.status);\n\tconst status: MissionStatus =\n\t\trecord.status === \"completed\" || record.status === \"failed\" || record.status === \"cancelled\"\n\t\t\t? record.status\n\t\t\t: record.goal && !states.some((state) => state === \"queued\" || state === \"running\" || state === \"active\")\n\t\t\t\t? \"active\"\n\t\t\t\t: states.some((state) => state === \"queued\" || state === \"running\" || state === \"active\")\n\t\t\t\t\t? \"active\"\n\t\t\t\t\t: states.some((state) => state === \"paused\")\n\t\t\t\t\t\t? \"waiting\"\n\t\t\t\t\t\t: states.some((state) => state === \"failed\")\n\t\t\t\t\t\t\t? \"failed\"\n\t\t\t\t\t\t\t: states.some((state) => state === \"stopped\" || state === \"rejected\" || state === \"cancelled\")\n\t\t\t\t\t\t\t\t? \"cancelled\"\n\t\t\t\t\t\t\t\t: states.length > 0 && states.every((state) => state === \"complete\" || state === \"completed\")\n\t\t\t\t\t\t\t\t\t? \"completed\"\n\t\t\t\t\t\t\t\t\t: record.status;\n\treturn { record: updateMission(location, record.id, { status, addRuns: updates }), warnings };\n}\n\nfunction formatMission(record: MissionRecord): string {\n\tconst lines = [\n\t\t`Mission: ${record.id}`,\n\t\t`Title: ${record.title}`,\n\t\t`Status: ${record.status}`,\n\t\t`Objective: ${record.objective}`,\n\t\t`Updated: ${record.updatedAt}`,\n\t];\n\tif (record.goal && record.budget)\n\t\tlines.push(\n\t\t\t`Goal mode: ${record.goal.status}`,\n\t\t\t`Budget: ${record.usage?.tokens ?? 0}/${record.budget.tokens} tokens`,\n\t\t);\n\tif (record.summary) lines.push(`Summary: ${record.summary}`);\n\tif (record.labels?.length) lines.push(`Labels: ${record.labels.join(\", \")}`);\n\tif (record.runs.length) {\n\t\tlines.push(\"Runs:\");\n\t\tfor (const run of record.runs)\n\t\t\tlines.push(\n\t\t\t\t`  ${run.runId} (${run.mode}${run.status ? `, ${run.status}` : \"\"})${run.asyncDir ? ` — ${run.asyncDir}` : \"\"}`,\n\t\t\t);\n\t}\n\tif (record.decisions.length) {\n\t\tlines.push(\"Decisions:\");\n\t\tfor (const decision of record.decisions) lines.push(`  ${decision.id}: ${decision.status} — ${decision.title}`);\n\t}\n\tif (record.artifacts.length) {\n\t\tlines.push(\"Artifacts:\");\n\t\tfor (const artifact of record.artifacts) lines.push(`  ${artifact.kind}: ${artifact.path}`);\n\t}\n\tif (record.receipts.length) {\n\t\tlines.push(\"Delivery receipts:\");\n\t\tfor (const receipt of record.receipts)\n\t\t\tlines.push(`  ${receipt.kind} (${receipt.status}): ${receipt.title} — ${receipt.url}`);\n\t}\n\treturn lines.join(\"\\n\");\n}\n\nexport function handleMissionAction(\n\taction: MissionAction,\n\tparams: MissionActionParams,\n\tctx: MissionActionContext,\n): AgentToolResult<Details> {\n\tconst location = resolveMissionStoreLocation({\n\t\tprojectRoot: ctx.cwd,\n\t\t...(ctx.config ? { config: ctx.config } : {}),\n\t\t...(ctx.agentDir ? { agentDir: ctx.agentDir } : {}),\n\t});\n\tif (action === \"mission.create\") {\n\t\tconst mission = validateMissionLaunch(params.mission);\n\t\tconst record = createMission(\n\t\t\tlocation,\n\t\t\t{\n\t\t\t\ttitle: mission.title,\n\t\t\t\tobjective: mission.objective ?? mission.title,\n\t\t\t\t...(mission.goal === true ? { goal: true as const } : {}),\n\t\t\t\t...(mission.budget ? { budget: mission.budget } : {}),\n\t\t\t\tstatus: params.missionStatus ? validateStatus(params.missionStatus, \"missionStatus\") : \"planned\",\n\t\t\t\t...(mission.labels ? { labels: mission.labels } : {}),\n\t\t\t\t...(ctx.currentSessionId ? { ownerSessionId: ctx.currentSessionId } : {}),\n\t\t\t},\n\t\t\tnew Date(),\n\t\t\tctx.config?.retainTerminal,\n\t\t);\n\t\treturn textResult(`Created mission ${record.id}: ${record.title}`, {\n\t\t\tmode: \"management\",\n\t\t\tresults: [],\n\t\t\tmissionId: record.id,\n\t\t\tmissionPath: pathFor(record.id),\n\t\t\tmission: record,\n\t\t});\n\t}\n\tif (action === \"mission.list\") {\n\t\tif (params.missionScope !== undefined && params.missionScope !== \"project\" && params.missionScope !== \"global\") {\n\t\t\tthrow new Error('missionScope must be \"project\" or \"global\"');\n\t\t}\n\t\tif (params.missionScope === \"global\") {\n\t\t\tconst listed = listGlobalMissions(location.globalIndexDir);\n\t\t\tconst lines =\n\t\t\t\tlisted.entries.length === 0\n\t\t\t\t\t? [\"No indexed missions.\"]\n\t\t\t\t\t: listed.entries.map(\n\t\t\t\t\t\t\t(entry) =>\n\t\t\t\t\t\t\t\t`${entry.missionId}  ${entry.status}${entry.stale ? \" [stale]\" : \"\"}  ${entry.title}  ${entry.projectRoot}`,\n\t\t\t\t\t\t);\n\t\t\tif (listed.warnings.length) lines.push(\"\", ...listed.warnings.map((warning) => `Warning: ${warning}`));\n\t\t\treturn textResult(lines.join(\"\\n\"), {\n\t\t\t\tmode: \"management\",\n\t\t\t\tresults: [],\n\t\t\t\tmissions: { globalEntries: listed.entries, warnings: listed.warnings },\n\t\t\t});\n\t\t}\n\t\tconst listed = listMissions(location);\n\t\tconst lines =\n\t\t\tlisted.records.length === 0\n\t\t\t\t? [\"No project missions.\"]\n\t\t\t\t: listed.records.map((record) => `${record.id}  ${record.status}  ${record.title}  ${record.updatedAt}`);\n\t\tif (listed.warnings.length) lines.push(\"\", ...listed.warnings.map((warning) => `Warning: ${warning}`));\n\t\treturn textResult(lines.join(\"\\n\"), {\n\t\t\tmode: \"management\",\n\t\t\tresults: [],\n\t\t\tmissions: { records: listed.records, warnings: listed.warnings },\n\t\t});\n\t}\n\tif (action === \"mission.show\") {\n\t\tconst refreshed = refreshLinkedRunStatus(location, readMission(location, requireMissionId(params)));\n\t\tconst lines = [formatMission(refreshed.record), `State: ${missionStatePath(location, refreshed.record.id)}`];\n\t\tif (refreshed.warnings.length) lines.push(\"\", ...refreshed.warnings.map((warning) => `Warning: ${warning}`));\n\t\treturn textResult(lines.join(\"\\n\"), {\n\t\t\tmode: \"management\",\n\t\t\tresults: [],\n\t\t\tmissionId: refreshed.record.id,\n\t\t\tmissionPath: pathFor(refreshed.record.id),\n\t\t\tmission: refreshed.record,\n\t\t\tmissions: { warnings: refreshed.warnings },\n\t\t});\n\t}\n\tif (action === \"mission.update\") {\n\t\tconst record = updateMission(location, requireMissionId(params), validateMissionUpdate(params.missionUpdate));\n\t\treturn textResult(`Updated mission ${record.id}.\\n\\n${formatMission(record)}`, {\n\t\t\tmode: \"management\",\n\t\t\tresults: [],\n\t\t\tmissionId: record.id,\n\t\t\tmissionPath: pathFor(record.id),\n\t\t\tmission: record,\n\t\t});\n\t}\n\tif (action === \"mission.attach-run\") {\n\t\tconst missionId = requireMissionId(params);\n\t\tconst runId = params.runId ?? params.id;\n\t\tif (typeof runId !== \"string\" || !runId.trim()) throw new Error(\"mission.attach-run requires runId or id\");\n\t\tconst rawMode = params.runMode ?? \"external\";\n\t\tif (![\"single\", \"parallel\", \"chain\", \"workflow\", \"scheduled\", \"external\"].includes(rawMode))\n\t\t\tthrow new Error(\"runMode is invalid\");\n\t\tconst mode = rawMode as MissionRunMode;\n\t\tif (params.dir !== undefined && !params.dir.trim()) throw new Error(\"dir must be a non-empty string\");\n\t\tif (params.runStatus !== undefined && !params.runStatus.trim())\n\t\t\tthrow new Error(\"runStatus must be a non-empty string\");\n\t\tconst record = updateMission(location, missionId, {\n\t\t\tstatus: \"active\",\n\t\t\taddRuns: [\n\t\t\t\t{\n\t\t\t\t\trunId: runId.trim(),\n\t\t\t\t\tmode,\n\t\t\t\t\t...(params.dir ? { asyncDir: params.dir } : {}),\n\t\t\t\t\t...(params.agent ? { agent: params.agent } : {}),\n\t\t\t\t\t...(params.runStatus ? { status: params.runStatus } : {}),\n\t\t\t\t\tstartedAt: new Date().toISOString(),\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\t\treturn textResult(`Attached run ${runId} to mission ${record.id}.`, {\n\t\t\tmode: \"management\",\n\t\t\tresults: [],\n\t\t\tmissionId: record.id,\n\t\t\tmissionPath: pathFor(record.id),\n\t\t\tmission: record,\n\t\t});\n\t}\n\tconst missionId = requireMissionId(params);\n\tconst status = params.missionStatus ?? \"completed\";\n\tif (status !== \"completed\" && status !== \"failed\" && status !== \"cancelled\")\n\t\tthrow new Error(\"mission.close missionStatus must be completed, failed, or cancelled\");\n\tif (params.summary !== undefined && !params.summary.trim()) throw new Error(\"summary must be a non-empty string\");\n\tconst record = updateMission(location, missionId, {\n\t\tstatus,\n\t\t...(params.summary !== undefined ? { summary: params.summary.trim() } : {}),\n\t});\n\treturn textResult(`Closed mission ${record.id} as ${record.status}.`, {\n\t\tmode: \"management\",\n\t\tresults: [],\n\t\tmissionId: record.id,\n\t\tmissionPath: pathFor(record.id),\n\t\tmission: record,\n\t});\n\n\tfunction pathFor(missionId: string): string {\n\t\treturn missionRecordPath(location, missionId);\n\t}\n}\n"]}