{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../src/core/capability-routing/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAuKH,wBAAgB,eAAe,IAAI,MAAM,CAKxC;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAoCzE","sourcesContent":["/**\n * Capability Routing — CLI (2.15.0).\n *\n * `jensen route preview|explain <MISSION_ID>`\n *\n * Deterministic, read-only routing preview. Never creates an Assignment, never\n * executes work, and never mutates a store. `preview` emits the stable DTO;\n * `explain` renders why each route is eligible/rejected. Inference routing is\n * out of scope.\n */\n\nimport chalk from \"chalk\";\nimport { getAgentDir } from \"../../config.js\";\nimport { AssignmentControlService } from \"../assignment/assignment-control-service.js\";\nimport type { MissionRequirements } from \"../assignment/assignment-types.js\";\nimport { createFileAssignmentStore } from \"../assignment/file-assignment-store.js\";\nimport { defaultChildSessionDir } from \"../durable-child-session/index.js\";\nimport { createFileExecutorRegistry, ExecutorControlService } from \"../executor-registry/index.js\";\nimport { createFileDurableMissionStore } from \"../mission-durable/index.js\";\nimport {\n\tcreateFileRemoteTargetRegistry,\n\tRemoteTargetRegistry,\n\tSshRemoteExecutionTransport,\n} from \"../remote-execution/index.js\";\nimport { createFileSchedulerStore, SchedulerControlService } from \"../scheduler/index.js\";\nimport { CapabilityRouter } from \"./capability-router.js\";\nimport type { RouteCandidate, RoutingEvaluation } from \"./route-types.js\";\n\nconst SUBCOMMANDS = new Set([\"preview\", \"explain\"]);\n\nfunction buildServices(): { router: CapabilityRouter; scheduler: SchedulerControlService } {\n\tconst missions = createFileDurableMissionStore();\n\tconst executors = new ExecutorControlService({ store: createFileExecutorRegistry() });\n\tconst assignments = new AssignmentControlService({\n\t\tstore: createFileAssignmentStore(),\n\t\tmissions,\n\t\texecutors,\n\t\tsessionDir: defaultChildSessionDir(getAgentDir()),\n\t});\n\tconst targets = new RemoteTargetRegistry({\n\t\tstore: createFileRemoteTargetRegistry(),\n\t\ttransport: new SshRemoteExecutionTransport(),\n\t});\n\tconst router = new CapabilityRouter({ executors, targets });\n\tconst scheduler = new SchedulerControlService({\n\t\tstore: createFileSchedulerStore(),\n\t\tmissions,\n\t\texecutors,\n\t\tassignments,\n\t});\n\treturn { router, scheduler };\n}\n\nfunction valueArgs(args: string[]): string[] {\n\treturn args.filter((a) => !a.startsWith(\"--\"));\n}\n\nfunction flag(args: string[], name: string): boolean {\n\treturn args.includes(name);\n}\n\nfunction flagValue(args: string[], name: string): string | undefined {\n\tconst idx = args.indexOf(name);\n\tif (idx === -1) return undefined;\n\treturn args[idx + 1];\n}\n\nfunction flagValues(args: string[], name: string): string[] {\n\tconst values: string[] = [];\n\tfor (let i = 0; i < args.length; i++) {\n\t\tif (args[i] === name && args[i + 1] !== undefined && !args[i + 1].startsWith(\"--\")) {\n\t\t\tvalues.push(args[i + 1]);\n\t\t}\n\t}\n\treturn values;\n}\n\nfunction requirementsFromFlags(args: string[]): MissionRequirements | undefined {\n\tconst requirements: MissionRequirements = {};\n\tconst os = flagValue(args, \"--os\");\n\tconst arch = flagValue(args, \"--arch\");\n\tconst mode = flagValue(args, \"--mode\");\n\tconst execution = flagValues(args, \"--execution\");\n\tconst providers = flagValues(args, \"--provider\");\n\tconst models = flagValues(args, \"--model\");\n\tconst tools = flagValues(args, \"--tool\");\n\tconst specialized = flagValues(args, \"--specialized\");\n\tconst extra = flagValues(args, \"--extra\");\n\tconst requiredLabels = flagValues(args, \"--label-required\");\n\tconst excludedLabels = flagValues(args, \"--label-excluded\");\n\tconst preferMode = flagValue(args, \"--prefer-mode\");\n\tconst preferExecutor = flagValue(args, \"--prefer-executor\");\n\tconst preferTarget = flagValue(args, \"--prefer-target\");\n\n\tif (os || arch) requirements.platform = { os, arch };\n\tif (mode === \"local\" || mode === \"remote\") requirements.executionMode = mode;\n\tif (execution.length > 0) requirements.execution = [...new Set(execution)].sort();\n\tif (providers.length > 0) requirements.providers = [...new Set(providers)].sort();\n\tif (models.length > 0) requirements.models = [...new Set(models)].sort();\n\tif (tools.length > 0) requirements.tools = [...new Set(tools)].sort();\n\tif (specialized.length > 0) requirements.specialized = [...new Set(specialized)].sort();\n\tif (extra.length > 0) requirements.extra = [...new Set(extra)].sort();\n\tif (requiredLabels.length > 0 || excludedLabels.length > 0) {\n\t\trequirements.labels = {\n\t\t\trequired: requiredLabels.length > 0 ? [...new Set(requiredLabels)].sort() : undefined,\n\t\t\texcluded: excludedLabels.length > 0 ? [...new Set(excludedLabels)].sort() : undefined,\n\t\t};\n\t}\n\tif (preferMode === \"local\" || preferMode === \"remote\" || preferExecutor || preferTarget) {\n\t\trequirements.preferences = {\n\t\t\texecutionMode: preferMode === \"local\" || preferMode === \"remote\" ? preferMode : undefined,\n\t\t\texecutorId: preferExecutor,\n\t\t\tremoteTargetId: preferTarget,\n\t\t};\n\t}\n\treturn Object.keys(requirements).length > 0 ? requirements : undefined;\n}\n\nfunction printJson(payload: unknown): void {\n\tprocess.stdout.write(`${JSON.stringify(payload, null, 2)}\\n`);\n}\n\nfunction codeOf(error: unknown): string | undefined {\n\tif (typeof error === \"object\" && error !== null && \"code\" in error) {\n\t\treturn String((error as { code: unknown }).code);\n\t}\n\treturn undefined;\n}\n\nfunction renderError(error: unknown): void {\n\tconst message = error instanceof Error ? error.message : String(error);\n\tconst code = codeOf(error);\n\tprocess.stderr.write(`${chalk.red(code ? `${code}: ${message}` : message)}\\n`);\n}\n\nfunction renderCandidate(candidate: RouteCandidate): void {\n\tconst marker = candidate.eligible\n\t\t? chalk.green(\"ELIGIBLE\")\n\t\t: candidate.status.startsWith(\"UNAVAILABLE\")\n\t\t\t? chalk.yellow(candidate.status)\n\t\t\t: chalk.red(candidate.status);\n\tconst mode = `${candidate.executionMode}${candidate.remoteTargetId ? ` (target=${candidate.remoteTargetId})` : \"\"}`;\n\tprocess.stdout.write(`  ${candidate.executorId}  [${marker}]  mode=${mode}`);\n\tif (candidate.platform) process.stdout.write(`  platform=${candidate.platform}/${candidate.arch ?? \"?\"}`);\n\tif (candidate.workerStatus !== \"ONLINE\") process.stdout.write(`  worker=${candidate.workerStatus}`);\n\tif (candidate.targetHealth) process.stdout.write(`  target=${candidate.targetHealth.status}`);\n\tprocess.stdout.write(\"\\n\");\n\tfor (const reason of candidate.rejectionReasons) {\n\t\tprocess.stdout.write(`    - ${reason}\\n`);\n\t}\n\tif (candidate.preferenceReasons.length > 0) {\n\t\tprocess.stdout.write(\n\t\t\t`    prefer (score=${candidate.preferenceScore}): ${candidate.preferenceReasons.join(\"; \")}\\n`,\n\t\t);\n\t}\n}\n\nfunction renderEvaluation(evaluation: RoutingEvaluation): void {\n\tconst req = evaluation.requirements;\n\tprocess.stdout.write(`ROUTING PREVIEW${evaluation.missionId ? ` (mission=${evaluation.missionId})` : \"\"}\\n`);\n\tprocess.stdout.write(`  eligible=${evaluation.eligibleCount}/${evaluation.candidates.length}\\n`);\n\tif (req.platform?.os) process.stdout.write(`  platform.os: ${req.platform.os}\\n`);\n\tif (req.platform?.arch) process.stdout.write(`  platform.arch: ${req.platform.arch}\\n`);\n\tif (req.executionMode) process.stdout.write(`  executionMode: ${req.executionMode}\\n`);\n\tif (req.execution?.length) process.stdout.write(`  execution: ${req.execution.join(\", \")}\\n`);\n\tif (req.specialized?.length) process.stdout.write(`  specialized: ${req.specialized.join(\", \")}\\n`);\n\tif (req.preferences) process.stdout.write(`  preferences: ${JSON.stringify(req.preferences)}\\n`);\n\tfor (const candidate of evaluation.candidates) renderCandidate(candidate);\n\tfor (const corrupt of evaluation.corrupt) {\n\t\tprocess.stdout.write(chalk.yellow(`  ${corrupt.executorId} [CORRUPT] ${corrupt.diagnostic}\\n`));\n\t}\n\tfor (const corrupt of evaluation.targetCorrupt) {\n\t\tprocess.stdout.write(chalk.yellow(`  target ${corrupt.targetId} [CORRUPT] ${corrupt.diagnostic}\\n`));\n\t}\n}\n\nexport function printRouteUsage(): string {\n\treturn [\n\t\t\"  route preview <MISSION_ID> [--json] [--os OS] [--arch A] [--mode local|remote] [--execution C]... [--specialized S]... [--prefer-mode local|remote] [--prefer-executor E] [--prefer-target T]\",\n\t\t\"  route explain <MISSION_ID> [--json] [same flags]\",\n\t].join(\"\\n\");\n}\n\nexport async function handleRouteCommand(args: string[]): Promise<boolean> {\n\tif (args[0] !== \"route\") return false;\n\tconst sub = args[1];\n\tif (!sub || !SUBCOMMANDS.has(sub)) return false;\n\n\tconst missionId = valueArgs(args)[2];\n\tif (!missionId) {\n\t\tprocess.stderr.write(\"missing mission id\\n\");\n\t\tprocess.exitCode = 1;\n\t\treturn true;\n\t}\n\n\tconst json = flag(args, \"--json\");\n\tconst { router, scheduler } = buildServices();\n\n\ttry {\n\t\tlet requirements = requirementsFromFlags(args);\n\t\tif (requirements === undefined) {\n\t\t\t// Reuse the scheduling intent's structured requirements when present.\n\t\t\ttry {\n\t\t\t\tconst intent = await scheduler.getIntentForMission(missionId);\n\t\t\t\trequirements = intent.requirements ?? {};\n\t\t\t} catch {\n\t\t\t\trequirements = {};\n\t\t\t}\n\t\t}\n\n\t\tconst evaluation = await router.evaluate({ requirements, missionId });\n\t\tif (json) printJson(evaluation);\n\t\telse renderEvaluation(evaluation);\n\t\treturn true;\n\t} catch (error) {\n\t\trenderError(error);\n\t\tprocess.exitCode = 1;\n\t\treturn true;\n\t}\n}\n"]}