{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../src/core/assignment/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA4JH,wBAAgB,oBAAoB,IAAI,MAAM,CAW7C;AAED,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CA0I9E","sourcesContent":["/**\n * Assignment Foundation — CLI (2.11.0).\n *\n * `jensen assignment list|show|create|release|reassign|compatibility|accept|start`\n *\n * Machine-readable `--json` exposes stable DTOs; human output is a compact\n * key: value block. There is no scheduler command.\n */\n\nimport chalk from \"chalk\";\nimport { getAgentDir } from \"../../config.js\";\nimport { defaultChildSessionDir } from \"../durable-child-session/index.js\";\nimport type { ExecutorRuntimeProof } from \"../executor-registry/executor-registry-types.js\";\nimport { createFileExecutorRegistry, ExecutorControlService } from \"../executor-registry/index.js\";\nimport { createFileDurableMissionStore } from \"../mission-durable/index.js\";\nimport { AssignmentControlService } from \"./assignment-control-service.js\";\nimport type {\n\tAssignMissionOutcome,\n\tAssignmentDetail,\n\tAssignmentListResult,\n\tMissionRequirements,\n} from \"./assignment-types.js\";\nimport { type AssignmentState, isAssignmentState } from \"./assignment-types.js\";\nimport { createFileAssignmentStore } from \"./file-assignment-store.js\";\n\nconst SUBCOMMANDS = new Set([\"list\", \"show\", \"create\", \"release\", \"reassign\", \"compatibility\", \"accept\", \"start\"]);\n\nfunction buildService(): AssignmentControlService {\n\treturn new AssignmentControlService({\n\t\tstore: createFileAssignmentStore(),\n\t\tmissions: createFileDurableMissionStore(),\n\t\texecutors: new ExecutorControlService({ store: createFileExecutorRegistry() }),\n\t\tsessionDir: defaultChildSessionDir(getAgentDir()),\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 renderList(result: AssignmentListResult): void {\n\tif (result.entries.length === 0 && result.corrupt.length === 0) {\n\t\tprocess.stdout.write(\"(no assignments)\\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.assignmentId}  [${entry.state}]`,\n\t\t\t\t` mission=${entry.missionId}`,\n\t\t\t\t` executor=${entry.executorId}`,\n\t\t\t\tentry.current ? \" current\" : \"\",\n\t\t\t\tentry.consumedByExecutionId ? ` execution=${entry.consumedByExecutionId}` : \"\",\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.assignmentId}  [CORRUPT] ${corrupt.diagnostic}\\n`));\n\t}\n}\n\nfunction renderDetail(detail: AssignmentDetail): void {\n\tprocess.stdout.write(`ASSIGNMENT\\n`);\n\tprocess.stdout.write(`  id: ${detail.assignmentId}\\n`);\n\tprocess.stdout.write(`  mission: ${detail.missionId}\\n`);\n\tprocess.stdout.write(`  executor: ${detail.executorId}\\n`);\n\tprocess.stdout.write(`  state: ${detail.state}\\n`);\n\tprocess.stdout.write(`  current: ${detail.current ? \"yes\" : \"no\"}\\n`);\n\tif (detail.assignedBy) process.stdout.write(`  assignedBy: ${detail.assignedBy}\\n`);\n\tif (detail.acceptedAtMs) process.stdout.write(`  accepted: ${detail.acceptedAtMs}\\n`);\n\tif (detail.executionStartedAtMs) process.stdout.write(`  executionStarted: ${detail.executionStartedAtMs}\\n`);\n\tif (detail.completedAtMs) process.stdout.write(`  completed: ${detail.completedAtMs}\\n`);\n\tif (detail.releasedAtMs) process.stdout.write(`  released: ${detail.releasedAtMs}\\n`);\n\tif (detail.supersededByAssignmentId) process.stdout.write(`  supersededBy: ${detail.supersededByAssignmentId}\\n`);\n\tif (detail.consumedByAttemptId) process.stdout.write(`  attempt: ${detail.consumedByAttemptId}\\n`);\n\tif (detail.consumedByExecutionId) process.stdout.write(`  execution: ${detail.consumedByExecutionId}\\n`);\n\tif (detail.terminalMissionState) process.stdout.write(`  terminalMission: ${detail.terminalMissionState}\\n`);\n\tif (detail.executionOwnerIdentity) {\n\t\tconst owner = detail.executionOwnerIdentity;\n\t\tprocess.stdout.write(\n\t\t\t`  owner: ${owner.executorId} ${owner.runtimeInstanceId} epoch=${owner.runtimeEpoch} leaseOwner=${owner.ownerId}\\n`,\n\t\t);\n\t}\n}\n\nfunction renderAssign(outcome: AssignMissionOutcome): void {\n\tprocess.stdout.write(\n\t\t`assigned ${outcome.assignmentId} mission=${outcome.missionId} executor=${outcome.executorId} state=${outcome.state}\\n`,\n\t);\n}\n\n// =============================================================================\n// Handler\n// =============================================================================\n\nexport function printAssignmentUsage(): string {\n\treturn [\n\t\t\"  assignment list [--json] [--mission M] [--executor E] [--state S] [--current|--not-current] [--limit N]\",\n\t\t\"  assignment show <ASSIGNMENT_ID> [--json]\",\n\t\t\"  assignment create --mission M --executor E [--json] [--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\"  assignment release <ASSIGNMENT_ID> [--json]\",\n\t\t\"  assignment reassign <ASSIGNMENT_ID> --executor E [--json]\",\n\t\t\"  assignment compatibility --mission M --executor E [--json]\",\n\t\t\"  assignment accept <ASSIGNMENT_ID> --instance I --epoch N [--json]\",\n\t\t\"  assignment start <ASSIGNMENT_ID> --instance I --epoch N [--json]\",\n\t].join(\"\\n\");\n}\n\nexport async function handleAssignmentCommand(args: string[]): Promise<boolean> {\n\tif (args[0] !== \"assignment\") 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();\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: AssignmentState | undefined = stateArg && isAssignmentState(stateArg) ? stateArg : undefined;\n\t\t\t\tconst limitArg = flagValue(args, \"--limit\");\n\t\t\t\tconst result = await service.listAssignments({\n\t\t\t\t\tfilter: {\n\t\t\t\t\t\tmissionId: flagValue(args, \"--mission\"),\n\t\t\t\t\t\texecutorId: flagValue(args, \"--executor\"),\n\t\t\t\t\t\tstate,\n\t\t\t\t\t\tcurrent: flag(args, \"--current\") ? true : flag(args, \"--not-current\") ? false : undefined,\n\t\t\t\t\t},\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 assignmentId = valueArgs(args)[2];\n\t\t\t\tif (!assignmentId) return missingId(\"assignment id\");\n\t\t\t\tconst detail = await service.getAssignment(assignmentId);\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 \"create\": {\n\t\t\t\tconst missionId = flagValue(args, \"--mission\");\n\t\t\t\tconst executorId = flagValue(args, \"--executor\");\n\t\t\t\tif (!missionId || !executorId) {\n\t\t\t\t\tprocess.stderr.write(\"create requires --mission and --executor\\n\");\n\t\t\t\t\tprocess.exitCode = 1;\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tconst outcome = await service.assignMission({\n\t\t\t\t\tmissionId,\n\t\t\t\t\texecutorId,\n\t\t\t\t\trequirements: requirementsFromFlags(args),\n\t\t\t\t\tassignedBy: flagValue(args, \"--assigned-by\"),\n\t\t\t\t});\n\t\t\t\tif (json) printJson(outcome);\n\t\t\t\telse renderAssign(outcome);\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tcase \"release\": {\n\t\t\t\tconst assignmentId = valueArgs(args)[2];\n\t\t\t\tif (!assignmentId) return missingId(\"assignment id\");\n\t\t\t\tconst record = await service.releaseAssignment(assignmentId);\n\t\t\t\tif (json) printJson(record);\n\t\t\t\telse process.stdout.write(`released ${record.assignmentId} state=${record.state}\\n`);\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tcase \"reassign\": {\n\t\t\t\tconst assignmentId = valueArgs(args)[2];\n\t\t\t\tconst executorId = flagValue(args, \"--executor\");\n\t\t\t\tif (!assignmentId || !executorId) {\n\t\t\t\t\tprocess.stderr.write(\"reassign requires <ASSIGNMENT_ID> and --executor\\n\");\n\t\t\t\t\tprocess.exitCode = 1;\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tconst current = await service.getAssignment(assignmentId);\n\t\t\t\tconst outcome = await service.reassignMission(current.missionId, executorId, {\n\t\t\t\t\trequirements: requirementsFromFlags(args),\n\t\t\t\t\tassignedBy: flagValue(args, \"--assigned-by\"),\n\t\t\t\t});\n\t\t\t\tif (json) printJson(outcome);\n\t\t\t\telse renderAssign(outcome);\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tcase \"compatibility\": {\n\t\t\t\tconst missionId = flagValue(args, \"--mission\");\n\t\t\t\tconst executorId = flagValue(args, \"--executor\");\n\t\t\t\tif (!missionId || !executorId) {\n\t\t\t\t\tprocess.stderr.write(\"compatibility requires --mission and --executor\\n\");\n\t\t\t\t\tprocess.exitCode = 1;\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tconst result = await service.evaluateCompatibility(missionId, executorId, requirementsFromFlags(args));\n\t\t\t\tif (json) printJson(result);\n\t\t\t\telse {\n\t\t\t\t\tprocess.stdout.write(\n\t\t\t\t\t\t`${result.missionId} -> ${result.executorId} compatible=${result.assignability.compatible} assignable=${result.assignability.assignable} status=${result.assignability.status}\\n`,\n\t\t\t\t\t);\n\t\t\t\t\tif (result.assignability.compatibility.unsatisfied.length > 0) {\n\t\t\t\t\t\tfor (const item of result.assignability.compatibility.unsatisfied) {\n\t\t\t\t\t\t\tprocess.stdout.write(`  unsatisfied: ${item.kind} = ${item.requirement}\\n`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tcase \"accept\": {\n\t\t\t\tconst assignmentId = valueArgs(args)[2];\n\t\t\t\tconst proof = proofFromArgs(args, assignmentId);\n\t\t\t\tif (!assignmentId || !proof) return missingProof();\n\t\t\t\tconst outcome = await service.acceptAssignment(assignmentId, proof);\n\t\t\t\tif (json) printJson(outcome);\n\t\t\t\telse process.stdout.write(`accepted ${outcome.assignmentId} state=${outcome.state}\\n`);\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tcase \"start\": {\n\t\t\t\tconst assignmentId = valueArgs(args)[2];\n\t\t\t\tconst proof = proofFromArgs(args, assignmentId);\n\t\t\t\tif (!assignmentId || !proof) return missingProof();\n\t\t\t\tconst outcome = await startViaControl(service, assignmentId, proof);\n\t\t\t\tif (json) printJson(outcome);\n\t\t\t\telse {\n\t\t\t\t\tprocess.stdout.write(\n\t\t\t\t\t\t`started ${outcome.assignmentId} mission=${outcome.missionId} assignmentState=${outcome.assignmentState} missionState=${outcome.missionState} success=${outcome.success}\\n`,\n\t\t\t\t\t);\n\t\t\t\t}\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(what: string): boolean {\n\tprocess.stderr.write(`missing ${what}\\n`);\n\tprocess.exitCode = 1;\n\treturn true;\n}\n\nfunction missingProof(): boolean {\n\tprocess.stderr.write(\"missing runtime proof (--instance and --epoch are required)\\n\");\n\tprocess.exitCode = 1;\n\treturn true;\n}\n\nfunction proofFromArgs(args: string[], executorIdFallback?: string): ExecutorRuntimeProof | undefined {\n\tconst instance = flagValue(args, \"--instance\");\n\tconst epochArg = flagValue(args, \"--epoch\");\n\tif (!instance || epochArg === undefined) return undefined;\n\tconst epoch = Number(epochArg);\n\tif (!Number.isSafeInteger(epoch) || epoch < 1) return undefined;\n\treturn { executorId: executorIdFallback ?? \"\", runtimeInstanceId: instance, runtimeEpoch: epoch };\n}\n\n/** Build the concrete child CLI launch and drive the assigned start. */\nasync function startViaControl(\n\tservice: AssignmentControlService,\n\tassignmentId: string,\n\tproof: ExecutorRuntimeProof,\n): Promise<Awaited<ReturnType<AssignmentControlService[\"startAssignedMission\"]>>> {\n\tconst current = await service.getAssignment(assignmentId);\n\tconst cliEntry = process.argv[1];\n\tconst command = process.execPath;\n\tconst prefixArgs = [...process.execArgv, cliEntry];\n\tconst sessionDir = defaultChildSessionDir(getAgentDir());\n\n\treturn service.startAssignedMission(\n\t\tassignmentId,\n\t\t{ executorId: current.executorId, runtimeInstanceId: proof.runtimeInstanceId, runtimeEpoch: proof.runtimeEpoch },\n\t\t{\n\t\t\tbuildResumeLaunch: ({ request, resumePrompt, childSessionId }) => {\n\t\t\t\tconst launchArgs = [\n\t\t\t\t\t...prefixArgs,\n\t\t\t\t\t\"--mode\",\n\t\t\t\t\t\"json\",\n\t\t\t\t\t\"-p\",\n\t\t\t\t\t\"--child-mission\",\n\t\t\t\t\tcurrent.missionId,\n\t\t\t\t\t\"--session-id\",\n\t\t\t\t\tchildSessionId,\n\t\t\t\t\t\"--session-dir\",\n\t\t\t\t\tsessionDir,\n\t\t\t\t];\n\t\t\t\tif (request.modelPolicy) {\n\t\t\t\t\tlaunchArgs.push(\"--provider\", request.modelPolicy.provider, \"--model\", request.modelPolicy.model);\n\t\t\t\t}\n\t\t\t\tif (request.capabilities && request.capabilities.length > 0) {\n\t\t\t\t\tlaunchArgs.push(\"--tools\", request.capabilities.join(\",\"));\n\t\t\t\t}\n\t\t\t\tlaunchArgs.push(resumePrompt);\n\t\t\t\treturn {\n\t\t\t\t\tcommand,\n\t\t\t\t\targs: launchArgs,\n\t\t\t\t\tcwd: request.workspaceScope?.cwd ?? process.cwd(),\n\t\t\t\t\tenv: { JENSEN_MISSION_ID: request.missionId, JENSEN_ASSIGNMENT_ID: assignmentId },\n\t\t\t\t};\n\t\t\t},\n\t\t},\n\t);\n}\n"]}