{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../src/core/scheduler/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAmMH,wBAAgB,mBAAmB,IAAI,MAAM,CAS5C;AAED,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CA+E7E","sourcesContent":["/**\n * Scheduler Foundation — CLI (2.12.0).\n *\n * `jensen scheduler list|show|enqueue|cancel|run|preview`\n *\n * Machine-readable `--json` exposes stable DTOs; human output is a compact\n * key: value block. `run` produces durable assignments; `preview` is a read-only\n * dry run. Neither executes missions.\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 { createFileSchedulerStore } from \"./file-scheduler-store.js\";\nimport { SchedulerControlService } from \"./scheduler-control-service.js\";\nimport {\n\ttype EnqueueSchedulingIntentOutcome,\n\tintentIdForMission,\n\tisSchedulingIntentState,\n\ttype SchedulingIntentDetail,\n\ttype SchedulingIntentListResult,\n\ttype SchedulingIntentState,\n\ttype SchedulingPolicy,\n\ttype SchedulingTickResult,\n} from \"./scheduler-types.js\";\n\nconst SUBCOMMANDS = new Set([\"list\", \"show\", \"enqueue\", \"cancel\", \"run\", \"preview\"]);\n\nfunction buildService(policy?: SchedulingPolicy): 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\treturn new SchedulerControlService({\n\t\tstore: createFileSchedulerStore(),\n\t\tmissions,\n\t\texecutors,\n\t\tassignments,\n\t\tpolicy,\n\t});\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 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 requirementsFromFlags(args: string[]): MissionRequirements | undefined {\n\tconst requirements: MissionRequirements = {};\n\tconst os = flagValue(args, \"--os\");\n\tconst arch = flagValue(args, \"--arch\");\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\n\tif (os || arch) requirements.platform = { os, arch };\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\n\treturn Object.keys(requirements).length > 0 ? requirements : undefined;\n}\n\nfunction policyFromArgs(args: string[]): SchedulingPolicy | undefined {\n\tconst mode = flagValue(args, \"--policy\");\n\tif (mode === \"least-assigned\") return { mode: \"least-assigned\" };\n\tif (mode === \"first-fit\") return { mode: \"first-fit\" };\n\treturn undefined;\n}\n\nfunction renderList(result: SchedulingIntentListResult): void {\n\tif (result.entries.length === 0 && result.corrupt.length === 0) {\n\t\tprocess.stdout.write(\"(no scheduling intents)\\n\");\n\t\treturn;\n\t}\n\tfor (const entry of result.entries) {\n\t\tprocess.stdout.write(\n\t\t\t[\n\t\t\t\t`${entry.intentId}  [${entry.state}]`,\n\t\t\t\t` mission=${entry.missionId}`,\n\t\t\t\t` priority=${entry.priority}`,\n\t\t\t\tentry.assignmentId ? ` assignment=${entry.assignmentId}` : \"\",\n\t\t\t\tentry.unschedulableReason ? ` reason=${entry.unschedulableReason}` : \"\",\n\t\t\t].join(\"\"),\n\t\t);\n\t\tprocess.stdout.write(\"\\n\");\n\t}\n\tfor (const corrupt of result.corrupt) {\n\t\tprocess.stdout.write(chalk.yellow(`${corrupt.intentId}  [CORRUPT] ${corrupt.diagnostic}\\n`));\n\t}\n}\n\nfunction renderDetail(detail: SchedulingIntentDetail): void {\n\tprocess.stdout.write(`SCHEDULING INTENT\\n`);\n\tprocess.stdout.write(`  intent: ${detail.intentId}\\n`);\n\tprocess.stdout.write(`  mission: ${detail.missionId}\\n`);\n\tprocess.stdout.write(`  state: ${detail.state}\\n`);\n\tprocess.stdout.write(`  priority: ${detail.priority}\\n`);\n\tif (detail.assignmentId) process.stdout.write(`  assignment: ${detail.assignmentId}\\n`);\n\tif (detail.unschedulableReason) process.stdout.write(`  reason: ${detail.unschedulableReason}\\n`);\n\tif (detail.requirements) {\n\t\tconst req = detail.requirements;\n\t\tif (req.platform?.os) process.stdout.write(`  platform.os: ${req.platform.os}\\n`);\n\t\tif (req.platform?.arch) process.stdout.write(`  platform.arch: ${req.platform.arch}\\n`);\n\t\tif (req.execution?.length) process.stdout.write(`  execution: ${req.execution.join(\", \")}\\n`);\n\t\tif (req.providers?.length) process.stdout.write(`  providers: ${req.providers.join(\", \")}\\n`);\n\t\tif (req.models?.length) process.stdout.write(`  models: ${req.models.join(\", \")}\\n`);\n\t\tif (req.tools?.length) process.stdout.write(`  tools: ${req.tools.join(\", \")}\\n`);\n\t\tif (req.specialized?.length) process.stdout.write(`  specialized: ${req.specialized.join(\", \")}\\n`);\n\t\tif (req.labels?.required?.length) process.stdout.write(`  label.required: ${req.labels.required.join(\", \")}\\n`);\n\t\tif (req.labels?.excluded?.length) process.stdout.write(`  label.excluded: ${req.labels.excluded.join(\", \")}\\n`);\n\t}\n}\n\nfunction renderEnqueue(outcome: EnqueueSchedulingIntentOutcome): void {\n\tprocess.stdout.write(`${outcome.status} ${outcome.intentId} mission=${outcome.missionId} state=${outcome.state}\\n`);\n}\n\nfunction renderTick(result: SchedulingTickResult): void {\n\tprocess.stdout.write(\n\t\t`${result.dryRun ? \"preview\" : \"run\"} ${result.runId} policy=${result.policy.mode} assigned=${result.intentsAssigned} unschedulable=${result.intentsUnschedulable} assignmentsCreated=${result.assignmentsCreated}\\n`,\n\t);\n\tfor (const decision of result.decisions) {\n\t\tconst chosen = decision.eligibility.find((entry) => entry.chosen);\n\t\tprocess.stdout.write(\n\t\t\t[\n\t\t\t\t`  ${decision.intentId} ${decision.decision}`,\n\t\t\t\tdecision.executorId ? ` executor=${decision.executorId}` : \"\",\n\t\t\t\tdecision.assignmentId ? ` assignment=${decision.assignmentId}` : \"\",\n\t\t\t\tdecision.reason ? ` reason=${decision.reason}` : \"\",\n\t\t\t\tchosen ? ` chosen=${chosen.executorId}` : \"\",\n\t\t\t].join(\"\"),\n\t\t);\n\t\tprocess.stdout.write(\"\\n\");\n\t}\n\tfor (const corrupt of result.corrupt) {\n\t\tprocess.stdout.write(chalk.yellow(`  ${corrupt.intentId} [CORRUPT] ${corrupt.diagnostic}\\n`));\n\t}\n}\n\n// =============================================================================\n// Handler\n// =============================================================================\n\nexport function printSchedulerUsage(): string {\n\treturn [\n\t\t\"  scheduler list [--json] [--state S] [--limit N]\",\n\t\t\"  scheduler show <MISSION_ID_OR_INTENT_ID> [--json]\",\n\t\t\"  scheduler enqueue <MISSION_ID> [--json] [--priority N] [--os OS] [--arch A] [--execution C]... [--provider P]... [--model M]... [--tool T]... [--specialized S]... [--extra X]... [--label-required L]... [--label-excluded L]...\",\n\t\t\"  scheduler cancel <MISSION_ID> [--json]\",\n\t\t\"  scheduler preview [--json] [--policy first-fit|least-assigned]\",\n\t\t\"  scheduler run [--json] [--policy first-fit|least-assigned]\",\n\t].join(\"\\n\");\n}\n\nexport async function handleSchedulerCommand(args: string[]): Promise<boolean> {\n\tif (args[0] !== \"scheduler\") return false;\n\tconst sub = args[1];\n\tif (!sub || !SUBCOMMANDS.has(sub)) return false;\n\n\tconst json = flag(args, \"--json\");\n\tconst service = buildService(policyFromArgs(args));\n\n\ttry {\n\t\tswitch (sub) {\n\t\t\tcase \"list\": {\n\t\t\t\tconst stateArg = flagValue(args, \"--state\");\n\t\t\t\tconst state: SchedulingIntentState | undefined =\n\t\t\t\t\tstateArg && isSchedulingIntentState(stateArg) ? stateArg : undefined;\n\t\t\t\tconst limitArg = flagValue(args, \"--limit\");\n\t\t\t\tconst result = await service.listIntents({\n\t\t\t\t\tfilter: { state, missionId: flagValue(args, \"--mission\") },\n\t\t\t\t\tlimit: limitArg !== undefined ? Number(limitArg) : undefined,\n\t\t\t\t});\n\t\t\t\tif (json) printJson(result);\n\t\t\t\telse renderList(result);\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tcase \"show\": {\n\t\t\t\tconst raw = valueArgs(args)[2];\n\t\t\t\tif (!raw) return missingId();\n\t\t\t\tconst intentId = raw.startsWith(\"intent_\") ? raw : intentIdForMission(raw);\n\t\t\t\tconst detail = await service.getIntent(intentId);\n\t\t\t\tif (json) printJson(detail);\n\t\t\t\telse renderDetail(detail);\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tcase \"enqueue\": {\n\t\t\t\tconst missionId = valueArgs(args)[2];\n\t\t\t\tif (!missionId) return missingId();\n\t\t\t\tconst priorityArg = flagValue(args, \"--priority\");\n\t\t\t\tconst priority = priorityArg !== undefined ? Number(priorityArg) : undefined;\n\t\t\t\tconst outcome = await service.enqueueIntent(missionId, {\n\t\t\t\t\trequirements: requirementsFromFlags(args),\n\t\t\t\t\tpriority: Number.isSafeInteger(priority ?? 0) ? priority : undefined,\n\t\t\t\t});\n\t\t\t\tif (json) printJson(outcome);\n\t\t\t\telse renderEnqueue(outcome);\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tcase \"cancel\": {\n\t\t\t\tconst missionId = valueArgs(args)[2];\n\t\t\t\tif (!missionId) return missingId();\n\t\t\t\tconst record = await service.cancelIntent(missionId);\n\t\t\t\tif (json) printJson(record);\n\t\t\t\telse process.stdout.write(`cancelled ${record.intentId} state=${record.state}\\n`);\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tcase \"preview\": {\n\t\t\t\tconst result = await service.previewTick();\n\t\t\t\tif (json) printJson(result);\n\t\t\t\telse renderTick(result);\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tcase \"run\": {\n\t\t\t\tconst result = await service.runTick();\n\t\t\t\tif (json) printJson(result);\n\t\t\t\telse renderTick(result);\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t\treturn false;\n\t\t}\n\t} catch (error) {\n\t\trenderError(error);\n\t\tprocess.exitCode = 1;\n\t\treturn true;\n\t}\n}\n\nfunction missingId(): boolean {\n\tprocess.stderr.write(\"missing mission id or intent id\\n\");\n\tprocess.exitCode = 1;\n\treturn true;\n}\n"]}