{"version":3,"file":"in-process-mission-executor.d.ts","sourceRoot":"","sources":["../../../src/core/mission-domain/in-process-mission-executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * In-Process Mission Executor (2.16.0).\n *\n * The real, in-process executor for the first-class mission contract. Where\n * `ProcessMissionExecutor` is the transitional adapter that drives a spawned\n * CLI, this executor drives a mission through the normal `createAgentSession()`\n * session path: a real agent session with a real model (e.g. the local Qwen\n * operator model), real tools, and the normal session stream. No child\n * process, no PID: the mission's structured identity (`missionId` +\n * `childSessionId`) is the only identity involved.\n *\n * Contract (same invariants as the rest of the domain):\n *   - A clean completion is NEVER classified SUCCEEDED without a verifier.\n *     Without one it is PARTIAL (\"execution completed but unverified\").\n *   - The handle exposes `executionId` and `cancel`, never an AgentSession.\n *   - `cancel` is idempotent and aborts the underlying session.\n *\n * Real-path configuration mirrors `QwenPlannerAdapter` (cwd/agentDir/auth/\n * registry/stream seams). The model is resolved from\n * `MissionRequest.modelPolicy` — verified, never defaulted: a mission without\n * a model policy, or whose policy is not registered, is a hard error. Tools\n * follow the execution mode (`execute` → coding tools, otherwise read-only\n * tools) and are narrowed by `MissionRequest.capabilities` when present.\n *\n * A `runner` seam replaces the whole session path for deterministic tests,\n * mirroring the `harness` seam of `ProcessMissionExecutor`.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type { StreamFn } from \"@apholdings/jensen-agent-core\";\nimport type { AssistantMessage, Model, TextContent } from \"@apholdings/jensen-ai\";\nimport { getAgentDir } from \"../../config.js\";\nimport type { AgentSession } from \"../agent-session.js\";\nimport { AuthStorage } from \"../auth-storage.js\";\nimport { defaultChildSessionDir } from \"../durable-child-session/index.js\";\nimport { governancePolicyFromEnv, resolveGovernanceModel } from \"../governance/evaluator.js\";\nimport type { GovernanceService } from \"../governance/service.js\";\nimport { ModelRegistry } from \"../model-registry.js\";\nimport { createAgentSession } from \"../sdk.js\";\nimport { SessionManager } from \"../session-manager.js\";\nimport { createCodingTools, createReadOnlyTools, type Tool } from \"../tools/index.js\";\nimport type { MissionExecutor, MissionLaunchOptions } from \"./mission-executor.js\";\nimport { createMissionHandle, type MissionHandle } from \"./mission-handle.js\";\nimport { type MissionRequest, validateMissionRequest } from \"./mission-request.js\";\nimport {\n\tclassifyExecutorOutcome,\n\tcreateMissionResult,\n\ttype ExecutorDiagnostics,\n\ttype ExecutorOutcome,\n\ttype MissionResult,\n\ttype StructuredFailure,\n} from \"./mission-result.js\";\nimport { MissionStateTracker } from \"./mission-state.js\";\n\n// =============================================================================\n// Runner seam\n// =============================================================================\n\n/**\n * Plain-data outcome of one in-process session run. Mirrors\n * `ExecutorOutcome` (no session object leaks) and adds presentation text and\n * the session-level error message.\n */\nexport interface InProcessMissionOutcome extends ExecutorOutcome {\n\t/** Final assistant text (presentation only, never the authority). */\n\toutputText?: string;\n\t/** Session-level error message when the run failed. */\n\tsessionError?: string;\n}\n\n/**\n * Executes one mission in-process and resolves to its plain-data outcome.\n * The default runner drives a real agent session; tests may inject a\n * deterministic runner.\n */\nexport type InProcessMissionRunner = (\n\trequest: MissionRequest,\n\tsignal?: AbortSignal,\n\texecutionId?: string,\n\tcorrelation?: { assignmentId?: string; sessionId?: string },\n) => Promise<InProcessMissionOutcome>;\n\n// =============================================================================\n// Verifier\n// =============================================================================\n\nexport interface InProcessMissionVerifierInput {\n\trequest: MissionRequest;\n\toutcome: InProcessMissionOutcome;\n}\n\nexport interface InProcessMissionVerification {\n\tverified: boolean;\n\tsummary?: string;\n\tcriterionIds?: string[];\n}\n\n/** Optional verifier that may promote a clean completion to verified SUCCEEDED. */\nexport type InProcessMissionVerifier = (input: InProcessMissionVerifierInput) => Promise<InProcessMissionVerification>;\n\n// =============================================================================\n// Prompt\n// =============================================================================\n\n/**\n * Deterministic execution prompt for a mission: identity, objective,\n * constraints, and acceptance criteria. Never PID- or process-derived.\n */\nexport function buildInProcessMissionPrompt(request: MissionRequest): string {\n\tconst lines: string[] = [\n\t\t`You are executing mission ${request.missionId} as agent \"${request.agent}\" (execution mode: ${request.executionMode}).`,\n\t];\n\tif (request.parentMissionId) lines.push(`Parent mission: ${request.parentMissionId} (depth ${request.depth}).`);\n\tlines.push(\"\", \"Objective:\", request.objective);\n\tif (request.constraints && request.constraints.length > 0)\n\t\tlines.push(\"\", \"Constraints:\", ...request.constraints.map((constraint) => `- ${constraint}`));\n\tif (request.acceptanceCriteria.length > 0)\n\t\tlines.push(\n\t\t\t\"\",\n\t\t\t\"Acceptance criteria:\",\n\t\t\t...request.acceptanceCriteria.map((criterion) => `- [${criterion.id}] ${criterion.description}`),\n\t\t);\n\tlines.push(\n\t\t\"\",\n\t\t\"Work autonomously within the stated objective and constraints. Finish when the objective is complete.\",\n\t);\n\treturn lines.join(\"\\n\");\n}\n\n// =============================================================================\n// Real session path\n// =============================================================================\n\n/** Tools for a mission: mode-based set, narrowed by declared capabilities. */\nfunction toolsForRequest(request: MissionRequest, cwd: string): Tool[] {\n\tconst base = request.executionMode === \"execute\" ? createCodingTools(cwd) : createReadOnlyTools(cwd);\n\tif (!request.capabilities || request.capabilities.length === 0) return base;\n\tconst allowed = new Set(request.capabilities);\n\treturn base.filter((tool) => allowed.has(tool.name));\n}\n\nfunction assistantText(message: AssistantMessage): string {\n\treturn message.content\n\t\t.filter((block): block is TextContent => block.type === \"text\")\n\t\t.map((block) => block.text)\n\t\t.join(\" \");\n}\n\nfunction lastAssistantMessage(messages: readonly unknown[]): AssistantMessage | undefined {\n\tfor (let index = messages.length - 1; index >= 0; index--) {\n\t\tconst message = messages[index];\n\t\tif (message && (message as { role?: unknown }).role === \"assistant\") return message as AssistantMessage;\n\t}\n\treturn undefined;\n}\n\n/**\n * Build the default runner: real agent session over the normal\n * `createAgentSession()` path. The model comes from `MissionRequest.modelPolicy`\n * (verified, never defaulted); the session is bound to the mission's durable\n * `childSessionId` when present (explicit resume restores that exact session).\n */\nexport function createInProcessMissionRunner(options: {\n\tcwd?: string;\n\tagentDir?: string;\n\tauthStorage?: AuthStorage;\n\tmodelRegistry?: ModelRegistry;\n\tsessionDir?: string;\n\t/** Existing durable session resolved by the worker for this child. */\n\tsessionManager?: SessionManager;\n\tstreamFn?: StreamFn;\n\t/** Durable Governance observer for actual model transitions. */\n\tgovernance?: GovernanceService;\n}): InProcessMissionRunner {\n\tconst cwd = options.cwd ?? process.cwd();\n\tconst agentDir = options.agentDir ?? getAgentDir();\n\tconst authStorage = options.authStorage ?? AuthStorage.create(`${agentDir}/auth.json`);\n\tconst modelRegistry = options.modelRegistry ?? new ModelRegistry(authStorage, `${agentDir}/models.json`);\n\tconst sessionDir = options.sessionDir ?? defaultChildSessionDir(agentDir);\n\tconst resolvedSessionManager = options.sessionManager;\n\n\treturn async (request, signal, executionId, correlation) => {\n\t\tif (signal?.aborted) return { exitCode: null, cancelled: true };\n\n\t\tconst policy = request.modelPolicy;\n\t\tif (!policy)\n\t\t\tthrow new Error(`MISSION_MODEL_POLICY_REQUIRED: mission ${request.missionId} declares no model policy`);\n\t\tconst governancePolicy = governancePolicyFromEnv();\n\t\tconst resolvedPolicy = resolveGovernanceModel(governancePolicy, policy);\n\t\tconst model: Model<any> | undefined = modelRegistry.find(\n\t\t\tresolvedPolicy.selected.provider,\n\t\t\tresolvedPolicy.selected.model,\n\t\t);\n\t\tif (!model)\n\t\t\tthrow new Error(\n\t\t\t\t`MISSION_MODEL_UNAVAILABLE: ${resolvedPolicy.selected.provider}/${resolvedPolicy.selected.model} is not registered in the model registry`,\n\t\t\t);\n\n\t\tconst missionCwd = request.workspaceScope?.cwd ?? cwd;\n\t\tconst sessionManager =\n\t\t\tresolvedSessionManager ??\n\t\t\t(request.childSessionId\n\t\t\t\t? SessionManager.createWithId(missionCwd, sessionDir, request.childSessionId)\n\t\t\t\t: SessionManager.inMemory(missionCwd));\n\t\tif (request.childSessionId && sessionManager.getSessionId() !== request.childSessionId)\n\t\t\tthrow new Error(\n\t\t\t\t`CHILD_SESSION_ID_MISMATCH: expected ${request.childSessionId}, got ${sessionManager.getSessionId()}`,\n\t\t\t);\n\n\t\tlet session: AgentSession | undefined;\n\t\tconst onAbort = () => {\n\t\t\tvoid session?.abort();\n\t\t};\n\t\tif (signal) signal.addEventListener(\"abort\", onAbort, { once: true });\n\t\ttry {\n\t\t\tconst created = await createAgentSession({\n\t\t\t\tcwd: missionCwd,\n\t\t\t\tagentDir,\n\t\t\t\tauthStorage,\n\t\t\t\tmodelRegistry,\n\t\t\t\tmodel,\n\t\t\t\ttools: toolsForRequest(request, missionCwd),\n\t\t\t\tsessionManager,\n\t\t\t\tstreamFn: options.streamFn,\n\t\t\t\tmissionId: request.missionId,\n\t\t\t\tassignmentId: correlation?.assignmentId ?? (request.context?.assignmentId as string | undefined),\n\t\t\t\texecutionId,\n\t\t\t});\n\t\t\tsession = created.session;\n\t\t\tawait created.session.prompt(buildInProcessMissionPrompt(request), { expandPromptTemplates: false });\n\t\t} catch (error) {\n\t\t\tif (signal?.aborted) return { exitCode: null, cancelled: true };\n\t\t\treturn { exitCode: 1, sessionError: error instanceof Error ? error.message : String(error) };\n\t\t} finally {\n\t\t\tif (signal) signal.removeEventListener(\"abort\", onAbort);\n\t\t}\n\t\tif (!session) return { exitCode: 1, sessionError: \"session was not created\" };\n\t\tconst finalMessage = lastAssistantMessage(session.messages);\n\t\tif (!finalMessage) return { exitCode: 1, sessionError: \"session produced no assistant message\" };\n\t\tconst outputText = assistantText(finalMessage);\n\t\tif (finalMessage.stopReason === \"aborted\") return { exitCode: null, cancelled: true, outputText };\n\t\tif (finalMessage.stopReason === \"error\")\n\t\t\treturn { exitCode: 1, sessionError: finalMessage.errorMessage ?? \"inference error\", outputText };\n\t\treturn { exitCode: 0, outputText };\n\t};\n}\n\n// =============================================================================\n// Executor\n// =============================================================================\n\nexport interface InProcessMissionExecutorOptions {\n\texecutorId?: string;\n\t/** Deterministic seam replacing the whole session path (mirrors the process executor's harness). */\n\trunner?: InProcessMissionRunner;\n\t/** Optional verifier that may promote a clean completion to SUCCEEDED. */\n\tverifier?: InProcessMissionVerifier;\n\t/** Execution id factory (default: UUID). Never a process id. */\n\texecutionIdFactory?: (request: MissionRequest) => string;\n\t/** Working directory default for the real session path. Default: process.cwd() */\n\tcwd?: string;\n\t/** Global config directory for the real session path. Default: getAgentDir() */\n\tagentDir?: string;\n\t/** Auth storage for the real session path. Default: AuthStorage.create(agentDir/auth.json) */\n\tauthStorage?: AuthStorage;\n\t/** Model registry for the real session path. Default: new ModelRegistry(authStorage, agentDir/models.json) */\n\tmodelRegistry?: ModelRegistry;\n\t/** Session dir for durable child sessions. Default: defaultChildSessionDir(agentDir) */\n\tsessionDir?: string;\n\t/** Existing durable session resolved by the worker for this child. */\n\tsessionManager?: SessionManager;\n\t/** Stream seam for the real session path (default: normal provider stream). */\n\tstreamFn?: StreamFn;\n}\n\ninterface ActiveMission {\n\trequest: MissionRequest;\n\texecutionId: string;\n\ttracker: MissionStateTracker;\n\tstartedAtMs: number;\n\tfinishedAtMs?: number;\n\tcontroller: AbortController;\n\toutcomePromise: Promise<InProcessMissionOutcome>;\n\tcancelled: boolean;\n}\n\nexport class InProcessMissionExecutor implements MissionExecutor {\n\treadonly executorId: string;\n\tprivate readonly _runner: InProcessMissionRunner;\n\tprivate readonly _verifier?: InProcessMissionVerifier;\n\tprivate readonly _executionIdFactory: (request: MissionRequest) => string;\n\tprivate readonly _active = new Map<string, ActiveMission>();\n\n\tconstructor(options: InProcessMissionExecutorOptions = {}) {\n\t\tthis.executorId = options.executorId ?? \"in-process\";\n\t\tthis._verifier = options.verifier;\n\t\tthis._executionIdFactory = options.executionIdFactory ?? (() => `exec_${randomUUID()}`);\n\t\tthis._runner =\n\t\t\toptions.runner ??\n\t\t\tcreateInProcessMissionRunner({\n\t\t\t\tcwd: options.cwd,\n\t\t\t\tagentDir: options.agentDir,\n\t\t\t\tauthStorage: options.authStorage,\n\t\t\t\tmodelRegistry: options.modelRegistry,\n\t\t\t\tsessionDir: options.sessionDir,\n\t\t\t\tsessionManager: options.sessionManager,\n\t\t\t\tstreamFn: options.streamFn,\n\t\t\t});\n\t}\n\n\tasync launch(request: MissionRequest, options: MissionLaunchOptions = {}): Promise<MissionHandle> {\n\t\tconst validation = validateMissionRequest(request);\n\t\tif (!validation.valid) {\n\t\t\tthrow new Error(`Invalid MissionRequest: ${validation.errors.join(\", \")}`);\n\t\t}\n\n\t\tconst executionId = options.executionId ?? this._executionIdFactory(request);\n\t\tconst controller = new AbortController();\n\t\tif (options.signal) {\n\t\t\tif (options.signal.aborted) controller.abort();\n\t\t\telse options.signal.addEventListener(\"abort\", () => controller.abort(), { once: true });\n\t\t}\n\n\t\tconst tracker = new MissionStateTracker(\"CREATED\");\n\t\ttracker.transition(\"QUEUED\");\n\t\ttracker.transition(\"RUNNING\");\n\n\t\tconst active: ActiveMission = {\n\t\t\trequest,\n\t\t\texecutionId,\n\t\t\ttracker,\n\t\t\tstartedAtMs: Date.now(),\n\t\t\tcontroller,\n\t\t\toutcomePromise: this._run(request, controller.signal, executionId, {\n\t\t\t\tassignmentId: options.assignmentId,\n\t\t\t\tsessionId: options.sessionId ?? request.childSessionId,\n\t\t\t}),\n\t\t\tcancelled: false,\n\t\t};\n\t\tthis._active.set(request.missionId, active);\n\n\t\treturn createMissionHandle({\n\t\t\tmissionId: request.missionId,\n\t\t\tparentMissionId: request.parentMissionId,\n\t\t\tdepth: request.depth,\n\t\t\texecutionId,\n\t\t\tstate: tracker.state,\n\t\t\tcreatedAtMs: request.createdAtMs,\n\t\t\tstartedAtMs: active.startedAtMs,\n\t\t\tcancel: (reason) => this._cancelActive(active, reason),\n\t\t});\n\t}\n\n\tasync awaitResult(handle: MissionHandle): Promise<MissionResult> {\n\t\tconst active = this._active.get(handle.missionId);\n\t\tif (!active) {\n\t\t\tthrow new Error(`Unknown mission: ${handle.missionId}`);\n\t\t}\n\n\t\tconst outcome = await active.outcomePromise;\n\t\tactive.finishedAtMs = Date.now();\n\n\t\tconst diagnostics: ExecutorDiagnostics = {\n\t\t\texecutorId: this.executorId,\n\t\t\tprocessExitCode: outcome.exitCode ?? undefined,\n\t\t\ttimedOut: outcome.timedOut,\n\t\t\tlaunchError: outcome.launchError,\n\t\t\tstderr: outcome.sessionError ? outcome.sessionError.slice(0, 16_000) : undefined,\n\t\t};\n\n\t\tconst { state, executionOutcome, failure, verification, completionDecision, outputText } = await this._classify(\n\t\t\tactive,\n\t\t\toutcome,\n\t\t);\n\n\t\tconst failures: StructuredFailure[] = [];\n\t\tif (failure) failures.push(failure);\n\n\t\treturn createMissionResult({\n\t\t\tmissionId: active.request.missionId,\n\t\t\tparentMissionId: active.request.parentMissionId,\n\t\t\tdepth: active.request.depth,\n\t\t\tstate,\n\t\t\texecutionOutcome,\n\t\t\toutputText,\n\t\t\tevidenceRefs: [],\n\t\t\tverification,\n\t\t\tcompletionDecision,\n\t\t\tfailures,\n\t\t\texecutorDiagnostics: diagnostics,\n\t\t\tstartedAtMs: active.startedAtMs,\n\t\t\tfinishedAtMs: active.finishedAtMs ?? Date.now(),\n\t\t});\n\t}\n\n\tasync cancel(handle: MissionHandle, reason?: string): Promise<void> {\n\t\tconst active = this._active.get(handle.missionId);\n\t\tif (!active) return;\n\t\tawait this._cancelActive(active, reason);\n\t}\n\n\tprivate _run(\n\t\trequest: MissionRequest,\n\t\tsignal: AbortSignal,\n\t\texecutionId: string,\n\t\tcorrelation: { assignmentId?: string; sessionId?: string },\n\t): Promise<InProcessMissionOutcome> {\n\t\t// A failing runner (session creation, model resolution, stream error)\n\t\t// is a launch failure, never an unhandled rejection.\n\t\treturn this._runner(request, signal, executionId, correlation).catch((error) => ({\n\t\t\texitCode: null,\n\t\t\tlaunchError: error instanceof Error ? error.message : String(error),\n\t\t}));\n\t}\n\n\tprivate async _cancelActive(active: ActiveMission, reason?: string): Promise<void> {\n\t\tactive.cancelled = true;\n\t\tactive.tracker.transition(\"CANCELLED\");\n\t\tactive.controller.abort(reason);\n\t}\n\n\tprivate async _classify(\n\t\tactive: ActiveMission,\n\t\toutcome: InProcessMissionOutcome,\n\t): Promise<{\n\t\tstate: MissionResult[\"state\"];\n\t\texecutionOutcome: MissionResult[\"executionOutcome\"];\n\t\tfailure?: StructuredFailure;\n\t\tverification: MissionResult[\"verification\"];\n\t\tcompletionDecision: MissionResult[\"completionDecision\"];\n\t\toutputText?: string;\n\t}> {\n\t\tconst outputText = outcome.outputText?.trim().slice(0, 16_000) || undefined;\n\n\t\tif (active.cancelled) {\n\t\t\treturn {\n\t\t\t\tstate: \"CANCELLED\",\n\t\t\t\texecutionOutcome: \"CANCELLED\",\n\t\t\t\tfailure: { category: \"CANCELLED\", message: \"mission cancelled by operator\" },\n\t\t\t\tverification: { status: \"unverified\" },\n\t\t\t\tcompletionDecision: \"unavailable\",\n\t\t\t\toutputText,\n\t\t\t};\n\t\t}\n\n\t\tconst base = classifyExecutorOutcome(outcome, { verified: false });\n\n\t\t// A verifier can promote a clean completion to verified SUCCEEDED.\n\t\tif (base.state === \"PARTIAL\" && this._verifier) {\n\t\t\tconst verificationResult = await this._verifier({ request: active.request, outcome });\n\t\t\tif (verificationResult.verified) {\n\t\t\t\tactive.tracker.transition(\"SUCCEEDED\");\n\t\t\t\treturn {\n\t\t\t\t\tstate: \"SUCCEEDED\",\n\t\t\t\t\texecutionOutcome: \"COMPLETED\",\n\t\t\t\t\tverification: {\n\t\t\t\t\t\tstatus: \"verified\",\n\t\t\t\t\t\tsummary: verificationResult.summary,\n\t\t\t\t\t\tcriterionIds: verificationResult.criterionIds,\n\t\t\t\t\t},\n\t\t\t\t\tcompletionDecision: \"accepted\",\n\t\t\t\t\toutputText,\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tstate: \"FAILED\",\n\t\t\t\texecutionOutcome: \"COMPLETED\",\n\t\t\t\tfailure: {\n\t\t\t\t\tcategory: \"VERIFICATION\",\n\t\t\t\t\tmessage: verificationResult.summary ?? \"verification failed\",\n\t\t\t\t},\n\t\t\t\tverification: { status: \"failed\", summary: verificationResult.summary },\n\t\t\t\tcompletionDecision: \"rejected\",\n\t\t\t\toutputText,\n\t\t\t};\n\t\t}\n\n\t\tactive.tracker.transition(base.state);\n\n\t\treturn {\n\t\t\tstate: base.state,\n\t\t\texecutionOutcome: base.executionOutcome,\n\t\t\tfailure: base.failure,\n\t\t\tverification: { status: \"unverified\", summary: base.reason },\n\t\t\tcompletionDecision: base.state === \"SUCCEEDED\" ? \"accepted\" : \"unavailable\",\n\t\t\toutputText,\n\t\t};\n\t}\n}\n\n/** Build an `InProcessMissionExecutor`. */\nexport function createInProcessMissionExecutor(\n\toptions: InProcessMissionExecutorOptions = {},\n): InProcessMissionExecutor {\n\treturn new InProcessMissionExecutor(options);\n}\n"]}