| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import { randomUUID } from 'node:crypto'; |
| import { mkdir, realpath, stat, readFile } from 'node:fs/promises'; |
| import { isAbsolute, resolve } from 'node:path'; |
| import type { SessionEvent } from '@maka/core/events'; |
| import type { PermissionMode } from '@maka/core/permission'; |
| import type { CreateSessionInput, UserMessageInput } from '@maka/core/runtime-inputs'; |
| import type { SessionSummary } from '@maka/core/session'; |
| import type { RuntimeEvent } from '@maka/core/runtime-event'; |
| import { redactSecrets } from '@maka/core/redaction'; |
| import { assertSessionBundleRootLayout } from '@maka/storage/session-bundle-policy'; |
| import { projectSessionCatalogSummary, readRuntimeHostSessions } from '@maka/runtime-host/client'; |
| import { connectRuntimeHostCli, resolveRuntimeHostCliTarget } from './runtime-host-cli-context.js'; |
| import { createRuntimeHostRunContext } from './runtime-host-run-command.js'; |
| import type { MakaRunOutcome } from './run-command-core.js'; |
| import { sessionEventSandboxBoundaryFailureReason } from './sandbox-boundary-failure.js'; |
| |
| const PROTOCOL = 'maka.activation' as const; |
| const SCHEMA_VERSION = 1 as const; |
| const MAX_RUNTIME_EVENTS = 256; |
| const MAX_RUNTIME_EVENT_BYTES = 64 * 1024; |
| const MAX_OUTPUT_BYTES = 256 * 1024; |
| const ACTIVATION_STIMULUS_TYPES = new Set(['message', 'schedule', 'system']); |
| |
| export type MakaActivationStatus = 'completed' | 'blocked' | 'retryable_failure' | 'fatal_failure'; |
| |
| export type MakaActivationRequiredAction = 'grant_permission' | 'retry_activation'; |
| |
| export interface MakaActivationOptions { |
| stateRoot: string; |
| workspaceRoot: string; |
| configRoot: string; |
| input: string; |
| timeoutMs?: number; |
| maxSteps?: number; |
| /** |
| * `ask` was excluded here while `execute` existed, but the two compiled to |
| * the same workspace-write profile and the same confirmation behavior — the |
| * exclusion separated the names, not the boundaries. With `execute` gone, |
| * `ask` is that boundary, and an activation asking for it gets exactly what |
| * `--permission-mode execute` always gave it. |
| */ |
| permissionMode?: PermissionMode; |
| connection?: string; |
| model?: string; |
| } |
| |
| export type ParseMakaActivateArgsResult = |
| | { kind: 'activate'; options: MakaActivationOptions } |
| | { kind: 'help'; text: string } |
| | { kind: 'error'; message: string }; |
| |
| export interface ActivationStimulus { |
| type: string; |
| payload: unknown; |
| } |
| |
| export interface MakaActivationRequest { |
| schemaVersion: 1; |
| activationId: string; |
| cloudSessionId: string; |
| makaSessionId?: string; |
| stimulus: ActivationStimulus; |
| } |
| |
| export interface MakaActivationContext { |
| runtime: MakaActivationRuntime; |
| target: { |
| connection: { |
| slug: string; |
| name?: string; |
| providerType?: string; |
| enabled?: boolean; |
| defaultModel?: string; |
| }; |
| apiKey?: string; |
| model: string; |
| }; |
| cwd?: string; |
| close(): Promise<void>; |
| } |
| |
| export interface MakaActivationRuntime { |
| createSession(input: CreateSessionInput): Promise<SessionSummary>; |
| listSessions?(): Promise<SessionSummary[]>; |
| resumeLatest?(sessionId: string): Promise<AsyncIterable<SessionEvent> | null>; |
| sendMessage(sessionId: string, input: UserMessageInput): AsyncIterable<SessionEvent>; |
| respondToSandboxBoundary( |
| sessionId: string, |
| response: { requestId: string; decision: 'deny' }, |
| ): Promise<void>; |
| stopSession(sessionId: string, input?: { source?: 'stop_button' }): Promise<void>; |
| } |
| |
| export interface MakaActivationDeps { |
| createContext(input: MakaActivationContextInput): Promise<MakaActivationContext>; |
| listSessions(stateRoot: string): Promise<SessionSummary[]>; |
| workspaceRoot(): string; |
| processCwd(): string; |
| stdinIsTTY(): boolean; |
| readStdin(): Promise<string>; |
| readFile(path: string): Promise<string>; |
| writeStdout(text: string): void; |
| writeStderr(text: string): void; |
| onSigint(handler: () => void): () => void; |
| setTimer(handler: () => void, ms: number): unknown; |
| clearTimer(timer: unknown): void; |
| newId(): string; |
| canonicalDirectory?(path: string): Promise<string>; |
| } |
| |
| export interface MakaActivationContextInput { |
| readonly workspaceRoot: string; |
| readonly stateRoot: string; |
| readonly configRoot: string; |
| readonly cwd: string; |
| readonly requestedConnectionSlug?: string; |
| readonly requestedModel?: string; |
| readonly sessionCwdOverride?: { readonly sessionId: string; readonly cwd: string }; |
| readonly maxSteps?: number; |
| readonly runOutcomeObserver?: (outcome: MakaRunOutcome) => void | Promise<void>; |
| } |
| |
| interface ActivationStartLine { |
| protocol: typeof PROTOCOL; |
| schemaVersion: typeof SCHEMA_VERSION; |
| type: 'start'; |
| activationId: string; |
| cloudSessionId: string; |
| makaSessionId?: string; |
| workspaceRoot: string; |
| } |
| |
| interface ActivationRuntimeEventLine { |
| protocol: typeof PROTOCOL; |
| schemaVersion: typeof SCHEMA_VERSION; |
| type: 'runtime_event'; |
| activationId: string; |
| cloudSessionId: string; |
| makaSessionId?: string; |
| event: RuntimeEvent; |
| truncated?: boolean; |
| } |
| |
| interface ActivationOutcomeLine { |
| protocol: typeof PROTOCOL; |
| schemaVersion: typeof SCHEMA_VERSION; |
| type: 'outcome'; |
| activationId: string; |
| cloudSessionId: string; |
| makaSessionId?: string; |
| status: MakaActivationStatus; |
| reason?: string; |
| requiredAction?: MakaActivationRequiredAction; |
| response?: unknown; |
| } |
| |
| export function parseMakaActivateArgs(argv: readonly string[]): ParseMakaActivateArgsResult { |
| const values = new Map<string, string>(); |
| const valueFlags = new Set([ |
| 'state-root', |
| 'workspace-root', |
| 'config-root', |
| 'input', |
| 'timeout', |
| 'max-steps', |
| 'permission-mode', |
| 'connection', |
| 'model', |
| ]); |
| for (let index = 0; index < argv.length; index += 1) { |
| const arg = argv[index]!; |
| if (arg === '--help' || arg === '-h') return { kind: 'help', text: makaActivateHelpText() }; |
| if (!arg.startsWith('--') || !valueFlags.has(arg.slice(2))) { |
| return { kind: 'error', message: `unexpected argument: ${arg}` }; |
| } |
| const name = arg.slice(2); |
| if (values.has(name)) return { kind: 'error', message: `option repeated: ${arg}` }; |
| const value = argv[index + 1]; |
| if (value === undefined || (value.startsWith('--') && value !== '-')) { |
| return { kind: 'error', message: `option ${arg} needs a value` }; |
| } |
| values.set(name, value); |
| index += 1; |
| } |
| |
| const stateRoot = values.get('state-root'); |
| const workspaceRoot = values.get('workspace-root'); |
| const configRoot = values.get('config-root'); |
| if (!stateRoot || !workspaceRoot || !configRoot) { |
| return { |
| kind: 'error', |
| message: 'activate requires --state-root, --workspace-root, and --config-root', |
| }; |
| } |
| for (const [name, value] of [ |
| ['state-root', stateRoot], |
| ['workspace-root', workspaceRoot], |
| ['config-root', configRoot], |
| ] as const) { |
| if (!isAbsolute(value)) return { kind: 'error', message: `--${name} must be an absolute path` }; |
| } |
| |
| const timeout = values.get('timeout'); |
| const timeoutSeconds = timeout === undefined ? undefined : Number(timeout); |
| if (timeoutSeconds !== undefined && (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0)) { |
| return { kind: 'error', message: '--timeout must be a positive number of seconds' }; |
| } |
| const maxSteps = values.get('max-steps'); |
| const parsedMaxSteps = maxSteps === undefined ? undefined : Number(maxSteps); |
| if (parsedMaxSteps !== undefined && (!Number.isInteger(parsedMaxSteps) || parsedMaxSteps < 1)) { |
| return { kind: 'error', message: '--max-steps must be a positive integer' }; |
| } |
| // `execute` stays accepted as an alias for `ask`: this is a public |
| // subcommand whose callers live outside this repo, and the two named the |
| // same boundary for as long as both existed. It is not offered in the error |
| // message, so nothing new learns to send it. |
| const requestedPermissionMode = values.get('permission-mode'); |
| const permissionMode = requestedPermissionMode === 'execute' ? 'ask' : requestedPermissionMode; |
| if ( |
| permissionMode !== undefined && |
| permissionMode !== 'explore' && |
| permissionMode !== 'ask' && |
| permissionMode !== 'bypass' |
| ) { |
| return { kind: 'error', message: '--permission-mode must be explore, ask, or bypass' }; |
| } |
| return { |
| kind: 'activate', |
| options: { |
| stateRoot, |
| workspaceRoot, |
| configRoot, |
| input: values.get('input') ?? '-', |
| ...(timeoutSeconds === undefined ? {} : { timeoutMs: Math.ceil(timeoutSeconds * 1000) }), |
| ...(parsedMaxSteps === undefined ? {} : { maxSteps: parsedMaxSteps }), |
| ...(permissionMode === undefined ? {} : { permissionMode }), |
| ...(values.get('connection') === undefined ? {} : { connection: values.get('connection') }), |
| ...(values.get('model') === undefined ? {} : { model: values.get('model') }), |
| }, |
| }; |
| } |
| |
| export function decodeActivationRequest(value: unknown): MakaActivationRequest { |
| if (!isRecord(value)) throw new Error('activation request must be a JSON object'); |
| const allowed = new Set([ |
| 'schemaVersion', |
| 'activationId', |
| 'cloudSessionId', |
| 'makaSessionId', |
| 'stimulus', |
| ]); |
| for (const key of Object.keys(value)) { |
| if (!allowed.has(key)) throw new Error(`unsupported activation field: ${key}`); |
| } |
| if (value.schemaVersion !== SCHEMA_VERSION) throw new Error('unsupported activation schema'); |
| for (const key of ['activationId', 'cloudSessionId'] as const) { |
| if (typeof value[key] !== 'string' || value[key].trim() === '') { |
| throw new Error(`activation ${key} must be a non-empty string`); |
| } |
| } |
| if ( |
| value.makaSessionId !== undefined && |
| (typeof value.makaSessionId !== 'string' || value.makaSessionId.trim() === '') |
| ) { |
| throw new Error('activation makaSessionId must be a non-empty string'); |
| } |
| if ( |
| !isRecord(value.stimulus) || |
| typeof value.stimulus.type !== 'string' || |
| value.stimulus.type.trim() === '' || |
| !ACTIVATION_STIMULUS_TYPES.has(value.stimulus.type) |
| ) { |
| throw new Error('activation stimulus must include a type'); |
| } |
| return { |
| schemaVersion: SCHEMA_VERSION, |
| activationId: value.activationId, |
| cloudSessionId: value.cloudSessionId, |
| ...(value.makaSessionId === undefined ? {} : { makaSessionId: value.makaSessionId }), |
| stimulus: { type: value.stimulus.type, payload: value.stimulus.payload }, |
| }; |
| } |
| |
| export async function runMakaActivationCli( |
| argv: readonly string[], |
| overrides: Partial<MakaActivationDeps> = {}, |
| ): Promise<number> { |
| const deps = { ...defaultMakaActivationDeps(), ...overrides }; |
| const parsed = parseMakaActivateArgs(argv); |
| if (parsed.kind === 'help') { |
| deps.writeStdout(`${parsed.text}\n`); |
| return 0; |
| } |
| if (parsed.kind === 'error') { |
| deps.writeStderr(`maka activate: ${parsed.message}\n`); |
| return 2; |
| } |
| |
| const options = parsed.options; |
| let request: MakaActivationRequest; |
| try { |
| const raw = options.input === '-' ? await deps.readStdin() : await deps.readFile(options.input); |
| request = decodeActivationRequest(JSON.parse(raw)); |
| } catch (error) { |
| const malformedRequest: MakaActivationRequest = { |
| schemaVersion: SCHEMA_VERSION, |
| activationId: deps.newId(), |
| cloudSessionId: 'unknown', |
| stimulus: { type: 'system', payload: {} }, |
| }; |
| const write = lineWriter(deps.writeStdout); |
| write({ |
| protocol: PROTOCOL, |
| schemaVersion: SCHEMA_VERSION, |
| type: 'start', |
| activationId: malformedRequest.activationId, |
| cloudSessionId: malformedRequest.cloudSessionId, |
| workspaceRoot: resolve(options.workspaceRoot), |
| } satisfies ActivationStartLine); |
| deps.writeStderr(`maka activate: ${safeErrorMessage(error)}\n`); |
| return emitOutcome( |
| deps.writeStdout, |
| malformedRequest, |
| undefined, |
| 'fatal_failure', |
| 'malformed_input', |
| ); |
| } |
| |
| const write = lineWriter(deps.writeStdout); |
| write({ |
| protocol: PROTOCOL, |
| schemaVersion: SCHEMA_VERSION, |
| type: 'start', |
| activationId: request.activationId, |
| cloudSessionId: request.cloudSessionId, |
| ...(request.makaSessionId ? { makaSessionId: request.makaSessionId } : {}), |
| workspaceRoot: resolve(options.workspaceRoot), |
| } satisfies ActivationStartLine); |
| |
| let roots: ValidatedRoots; |
| try { |
| roots = await validateRoots(options); |
| } catch (error) { |
| emitOutcome(deps.writeStdout, request, undefined, 'fatal_failure', 'unsafe_roots'); |
| deps.writeStderr(`maka activate: ${safeErrorMessage(error)}\n`); |
| return 2; |
| } |
| |
| let sessions: SessionSummary[] = []; |
| let existing: SessionSummary | undefined; |
| try { |
| sessions = await deps.listSessions(roots.stateRoot); |
| existing = request.makaSessionId |
| ? sessions.find((session) => session.id === request.makaSessionId) |
| : undefined; |
| if (request.makaSessionId && !existing) { |
| return await finishWithoutContext(deps, request, write, 'fatal_failure', 'session_not_found'); |
| } |
| if (existing && (!existing.cwd || !(await sameDirectory(existing.cwd, roots.workspaceRoot)))) { |
| return await finishWithoutContext( |
| deps, |
| request, |
| write, |
| 'fatal_failure', |
| 'session_cwd_mismatch', |
| ); |
| } |
| if (existing && (existing.status === 'blocked' || existing.status === 'waiting_for_user')) { |
| const blockedReason = existing.blockedReason ?? 'unknown'; |
| if (blockedReason !== 'permission_required') { |
| return await finishWithoutContext( |
| deps, |
| request, |
| write, |
| 'retryable_failure', |
| blockedReason, |
| existing.id, |
| 'retry_activation', |
| ); |
| } |
| return await finishWithoutContext( |
| deps, |
| request, |
| write, |
| 'blocked', |
| blockedReason, |
| existing.id, |
| 'grant_permission', |
| ); |
| } |
| } catch (error) { |
| return await finishWithoutContext( |
| deps, |
| request, |
| write, |
| 'fatal_failure', |
| 'session_lookup_failed', |
| ); |
| } |
| |
| let context: MakaActivationContext | undefined; |
| let session: SessionSummary | undefined = existing; |
| let invocation: MakaRunOutcome | undefined; |
| let streamBoundaryFailure = false; |
| let timedOut = false; |
| let interrupted = false; |
| let streamFailed = false; |
| let stopPromise: Promise<void> | undefined; |
| let eventCount = 0; |
| let eventBytes = 0; |
| let eventTruncated = false; |
| let terminalWritten = false; |
| const writeRuntimeEvent = (event: RuntimeEvent): void => { |
| if (terminalWritten || eventTruncated || eventCount >= MAX_RUNTIME_EVENTS) { |
| eventTruncated = true; |
| return; |
| } |
| const safeEvent = redactJson(event) as RuntimeEvent; |
| const eventLine = { |
| protocol: PROTOCOL, |
| schemaVersion: SCHEMA_VERSION, |
| type: 'runtime_event', |
| activationId: request.activationId, |
| cloudSessionId: request.cloudSessionId, |
| ...(session?.id ? { makaSessionId: session.id } : {}), |
| event: safeEvent, |
| } satisfies ActivationRuntimeEventLine; |
| const bytes = Buffer.byteLength(JSON.stringify(eventLine), 'utf8'); |
| if (eventBytes + bytes > MAX_RUNTIME_EVENT_BYTES) { |
| eventTruncated = true; |
| return; |
| } |
| eventBytes += bytes; |
| eventCount += 1; |
| write(eventLine); |
| }; |
| try { |
| context = await deps.createContext({ |
| workspaceRoot: roots.workspaceRoot, |
| stateRoot: roots.stateRoot, |
| configRoot: roots.configRoot, |
| cwd: roots.workspaceRoot, |
| ...(existing || options.connection |
| ? { requestedConnectionSlug: existing?.llmConnectionSlug ?? options.connection } |
| : {}), |
| ...(existing || options.model ? { requestedModel: existing?.model ?? options.model } : {}), |
| ...(existing |
| ? { sessionCwdOverride: { sessionId: existing.id, cwd: roots.workspaceRoot } } |
| : {}), |
| ...(options.maxSteps === undefined ? {} : { maxSteps: options.maxSteps }), |
| runOutcomeObserver: (result) => { |
| invocation = result; |
| }, |
| }); |
| if (!session) { |
| session = await context.runtime.createSession({ |
| cwd: roots.workspaceRoot, |
| name: `Cloud activation ${request.activationId}`.slice(0, 80), |
| llmConnectionSlug: context.target.connection.slug, |
| model: context.target.model, |
| permissionMode: options.permissionMode ?? 'explore', |
| }); |
| } |
| const stop = (): void => { |
| if (stopPromise || !session) return; |
| stopPromise = context!.runtime.stopSession(session.id, { source: 'stop_button' }); |
| void stopPromise.catch(() => {}); |
| }; |
| const removeSigint = deps.onSigint(() => { |
| interrupted = true; |
| stop(); |
| }); |
| let resolveTimeout: (() => void) | undefined; |
| const timeoutSignal = |
| options.timeoutMs === undefined |
| ? undefined |
| : new Promise<void>((resolve) => { |
| resolveTimeout = resolve; |
| }); |
| const timer = |
| options.timeoutMs === undefined |
| ? undefined |
| : deps.setTimer(() => { |
| timedOut = true; |
| stop(); |
| resolveTimeout?.(); |
| }, options.timeoutMs); |
| try { |
| const text = activationStimulusText(request.stimulus); |
| const stream = await activationStream( |
| context.runtime, |
| session.id, |
| { turnId: deps.newId(), text }, |
| existing !== undefined, |
| ); |
| const drain = (async () => { |
| for await (const event of stream) { |
| if (sessionEventSandboxBoundaryFailureReason(event)) streamBoundaryFailure = true; |
| writeRuntimeEvent(sessionEventToRuntimeEvent(event, session!.id)); |
| if (event.type === 'sandbox_boundary_request') { |
| await context!.runtime.respondToSandboxBoundary(session!.id, { |
| requestId: event.requestId, |
| decision: 'deny', |
| }); |
| } |
| } |
| })(); |
| if (timeoutSignal) await Promise.race([drain, timeoutSignal]); |
| else await drain; |
| await stopPromise; |
| } catch (error) { |
| streamFailed = true; |
| await stopPromise?.catch(() => undefined); |
| if (!timedOut && !interrupted) |
| deps.writeStderr(`maka activate: ${safeErrorMessage(error)}\n`); |
| } finally { |
| removeSigint(); |
| if (timer !== undefined) deps.clearTimer(timer); |
| await context.close(); |
| } |
| } catch (error) { |
| if (context) await context.close().catch(() => undefined); |
| deps.writeStderr(`maka activate: ${safeErrorMessage(error)}\n`); |
| return emitOutcome(deps.writeStdout, request, session?.id, 'fatal_failure', 'bootstrap_failed'); |
| } |
| |
| if (eventTruncated && !terminalWritten) { |
| write({ |
| protocol: PROTOCOL, |
| schemaVersion: SCHEMA_VERSION, |
| type: 'runtime_event', |
| activationId: request.activationId, |
| cloudSessionId: request.cloudSessionId, |
| ...(session?.id ? { makaSessionId: session.id } : {}), |
| event: { truncated: true } as unknown as RuntimeEvent, |
| truncated: true, |
| }); |
| } |
| const finish = ( |
| status: MakaActivationStatus, |
| reason?: string, |
| response?: unknown, |
| requiredAction?: MakaActivationRequiredAction, |
| ): number => { |
| if (terminalWritten) |
| return status === 'completed' |
| ? 0 |
| : status === 'blocked' |
| ? 3 |
| : status === 'retryable_failure' |
| ? 4 |
| : 2; |
| terminalWritten = true; |
| return emitOutcome( |
| deps.writeStdout, |
| request, |
| session?.id, |
| status, |
| reason, |
| response, |
| requiredAction, |
| ); |
| }; |
| if (interrupted) return finish('retryable_failure', 'interrupted'); |
| if (timedOut) return finish('retryable_failure', 'timeout'); |
| if (streamFailed) return finish('retryable_failure', 'runtime_error'); |
| if (invocation?.failure?.class === 'permission_denied') { |
| return finish('blocked', 'permission_denied', undefined, 'grant_permission'); |
| } |
| if (streamBoundaryFailure || invocation?.sandboxBoundary === 'unresolved') { |
| return finish('blocked', 'permission_required', undefined, 'grant_permission'); |
| } |
| if (!invocation) return finish('fatal_failure', 'missing_invocation'); |
| if (invocation.status === 'completed' && invocation.finalOutput !== undefined) { |
| return finish('completed', undefined, invocation.finalOutput); |
| } |
| if (invocation.status === 'completed') return finish('fatal_failure', 'missing_response'); |
| const failureClass = invocation.failure?.class ?? 'runtime_failure'; |
| const blocked = failureClass === 'permission_denied' || failureClass === 'permission_required'; |
| return finish( |
| blocked ? 'blocked' : 'retryable_failure', |
| blocked ? failureClass : 'runtime_failure', |
| undefined, |
| blocked ? 'grant_permission' : undefined, |
| ); |
| } |
| |
| async function finishWithoutContext( |
| deps: MakaActivationDeps, |
| request: MakaActivationRequest, |
| write: (line: object) => void, |
| status: MakaActivationStatus, |
| reason: string, |
| sessionId?: string, |
| requiredAction?: MakaActivationRequiredAction, |
| ): Promise<number> { |
| return emitOutcome( |
| deps.writeStdout, |
| request, |
| sessionId, |
| status, |
| reason, |
| undefined, |
| requiredAction, |
| ); |
| } |
| |
| function emitOutcome( |
| output: (text: string) => void, |
| request: MakaActivationRequest, |
| sessionId: string | undefined, |
| status: MakaActivationStatus, |
| reason?: string, |
| response?: unknown, |
| requiredAction?: MakaActivationRequiredAction, |
| ): number { |
| const outcome: ActivationOutcomeLine = { |
| protocol: PROTOCOL, |
| schemaVersion: SCHEMA_VERSION, |
| type: 'outcome', |
| activationId: request.activationId, |
| cloudSessionId: request.cloudSessionId, |
| ...(sessionId ? { makaSessionId: sessionId } : {}), |
| status, |
| ...(reason ? { reason } : {}), |
| ...(requiredAction ? { requiredAction } : {}), |
| ...(response === undefined ? {} : { response: boundedRedactedJson(response) }), |
| }; |
| output(`${JSON.stringify(redactJson(outcome))}\n`); |
| return status === 'completed' |
| ? 0 |
| : status === 'blocked' |
| ? 3 |
| : status === 'retryable_failure' |
| ? 4 |
| : 2; |
| } |
| |
| function boundedRedactedJson(value: unknown): unknown { |
| const redacted = redactJson(value); |
| const serialized = JSON.stringify(redacted); |
| if (serialized === undefined || Buffer.byteLength(serialized, 'utf8') <= MAX_OUTPUT_BYTES) { |
| return redacted; |
| } |
| return redactSecrets(serialized).slice(0, MAX_OUTPUT_BYTES); |
| } |
| |
| function lineWriter(output: (text: string) => void): (line: object) => void { |
| let emittedOutcome = false; |
| return (line) => { |
| if ((line as { type?: string }).type === 'outcome') { |
| if (emittedOutcome) return; |
| emittedOutcome = true; |
| } |
| output(`${JSON.stringify(redactJson(line))}\n`); |
| }; |
| } |
| |
| interface ValidatedRoots { |
| stateRoot: string; |
| workspaceRoot: string; |
| configRoot: string; |
| } |
| |
| async function validateRoots(options: MakaActivationOptions): Promise<ValidatedRoots> { |
| await assertActivationRootLayout(options); |
| const workspaceRoot = await canonicalExistingDirectory(options.workspaceRoot); |
| const stateRoot = await canonicalCreateDirectory(options.stateRoot); |
| const configRoot = await canonicalCreateDirectory(options.configRoot); |
| await assertActivationRootLayout({ stateRoot, workspaceRoot, configRoot }); |
| return { stateRoot, workspaceRoot, configRoot }; |
| } |
| |
| async function assertActivationRootLayout( |
| roots: Pick<MakaActivationOptions, 'stateRoot' | 'workspaceRoot' | 'configRoot'>, |
| ): Promise<void> { |
| await Promise.all([ |
| assertSessionBundleRootLayout({ |
| stateRoot: roots.stateRoot, |
| configRoot: roots.workspaceRoot, |
| }), |
| assertSessionBundleRootLayout({ |
| stateRoot: roots.stateRoot, |
| configRoot: roots.configRoot, |
| }), |
| assertSessionBundleRootLayout({ |
| stateRoot: roots.workspaceRoot, |
| configRoot: roots.configRoot, |
| }), |
| ]); |
| } |
| |
| async function canonicalExistingDirectory(path: string): Promise<string> { |
| const canonical = await realpath(resolve(path)); |
| const info = await stat(canonical); |
| if (!info.isDirectory()) throw new Error(`root is not a directory: ${path}`); |
| return canonical; |
| } |
| |
| async function canonicalCreateDirectory(path: string): Promise<string> { |
| await mkdir(resolve(path), { recursive: true }); |
| return canonicalExistingDirectory(path); |
| } |
| |
| async function sameDirectory(left: string, right: string): Promise<boolean> { |
| try { |
| return (await canonicalExistingDirectory(left)) === right; |
| } catch { |
| return resolve(left) === resolve(right); |
| } |
| } |
| |
| function activationStimulusText(stimulus: ActivationStimulus): string { |
| const payload = stimulus.payload; |
| if (!isRecord(payload)) return JSON.stringify({ type: stimulus.type, payload }); |
| for (const key of ['text', 'prompt', 'message'] as const) { |
| if (typeof payload[key] === 'string' && payload[key].trim()) return payload[key]; |
| } |
| return JSON.stringify({ type: stimulus.type, payload }); |
| } |
| |
| async function activationStream( |
| runtime: MakaActivationRuntime, |
| sessionId: string, |
| input: UserMessageInput, |
| allowSafeBoundaryResume: boolean, |
| ): Promise<AsyncIterable<SessionEvent>> { |
| if (allowSafeBoundaryResume && runtime.resumeLatest) { |
| const resumed = await runtime.resumeLatest(sessionId); |
| if (resumed) return resumed; |
| } |
| return runtime.sendMessage(sessionId, input); |
| } |
| |
| function sessionEventToRuntimeEvent(event: SessionEvent, sessionId: string): RuntimeEvent { |
| return { |
| id: event.id, |
| invocationId: `activation-${sessionId}`, |
| runId: event.turnId, |
| sessionId, |
| turnId: event.turnId, |
| ts: event.ts, |
| partial: false, |
| role: 'tool', |
| author: 'system', |
| content: { kind: 'text', text: event.type }, |
| } as RuntimeEvent; |
| } |
| |
| function redactJson(value: unknown): unknown { |
| try { |
| const serialized = JSON.stringify(value); |
| return serialized === undefined ? value : JSON.parse(redactSecrets(serialized)); |
| } catch { |
| return '[redacted]'; |
| } |
| } |
| |
| function isRecord(value: unknown): value is Record<string, any> { |
| return typeof value === 'object' && value !== null && !Array.isArray(value); |
| } |
| |
| function safeErrorMessage(error: unknown): string { |
| const message = error instanceof Error ? error.message : String(error); |
| return redactSecrets(message).slice(0, 500); |
| } |
| |
| function makaActivateHelpText(): string { |
| return [ |
| 'Usage: maka activate --state-root <path> --workspace-root <path> --config-root <path>', |
| '', |
| 'Read a versioned activation request from stdin or --input <file> and emit JSONL.', |
| '', |
| 'Options:', |
| ' --state-root <path> Session-owned state directory (required)', |
| ' --workspace-root <path> Activation workspace directory (required)', |
| ' --config-root <path> Configuration directory (required)', |
| ' --input <path> JSON request file, or - for stdin (default: -)', |
| ' --timeout <seconds> Invocation timeout', |
| ' --max-steps <count> Tool-step cap', |
| ' --permission-mode <mode> explore|ask|bypass', |
| ' --connection <slug> Model connection override', |
| ' --model <id> Model override', |
| ].join('\n'); |
| } |
| |
| function defaultMakaActivationDeps(): MakaActivationDeps { |
| return { |
| createContext: createRuntimeHostActivationContext, |
| listSessions: listRuntimeHostActivationSessions, |
| workspaceRoot: () => resolve(process.cwd()), |
| processCwd: () => process.cwd(), |
| stdinIsTTY: () => process.stdin.isTTY === true, |
| readStdin: readProcessStdin, |
| readFile: async (path) => readFile(path, 'utf8'), |
| writeStdout: (text) => process.stdout.write(text), |
| writeStderr: (text) => process.stderr.write(text), |
| onSigint: (handler) => { |
| process.on('SIGINT', handler); |
| return () => process.off('SIGINT', handler); |
| }, |
| setTimer: (handler, ms) => { |
| const timer = setTimeout(handler, ms); |
| timer.unref(); |
| return timer; |
| }, |
| clearTimer: (timer) => clearTimeout(timer as ReturnType<typeof setTimeout>), |
| newId: randomUUID, |
| }; |
| } |
| |
| async function createRuntimeHostActivationContext( |
| input: MakaActivationContextInput, |
| ): Promise<MakaActivationContext> { |
| const connected = await connectRuntimeHostCli({ |
| rootPath: input.stateRoot, |
| }); |
| try { |
| const target = resolveRuntimeHostCliTarget(connected.catalog, { |
| ...(input.requestedConnectionSlug ? { connectionSlug: input.requestedConnectionSlug } : {}), |
| ...(input.requestedModel ? { model: input.requestedModel } : {}), |
| }); |
| const runContext = createRuntimeHostRunContext(connected.connection, connected.catalog, { |
| workspaceRoot: input.stateRoot, |
| cwd: input.cwd, |
| requestedConnectionSlug: target.connection.slug, |
| requestedModel: target.model, |
| ...(input.sessionCwdOverride ? { sessionCwdOverride: { ...input.sessionCwdOverride } } : {}), |
| ...(input.maxSteps !== undefined ? { maxSteps: input.maxSteps } : {}), |
| ...(input.runOutcomeObserver ? { runOutcomeObserver: input.runOutcomeObserver } : {}), |
| }); |
| return { |
| runtime: runContext.runtime, |
| target: { |
| connection: { |
| slug: target.connection.slug, |
| name: target.connection.name, |
| providerType: target.connection.providerType, |
| enabled: target.connection.enabled, |
| defaultModel: target.model, |
| }, |
| model: target.model, |
| }, |
| cwd: input.cwd, |
| close: async () => { |
| await runContext.close(); |
| await connected.close(); |
| }, |
| }; |
| } catch (error) { |
| await connected.close().catch(() => undefined); |
| throw error; |
| } |
| } |
| |
| async function listRuntimeHostActivationSessions(stateRoot: string): Promise<SessionSummary[]> { |
| const connected = await connectRuntimeHostCli({ |
| rootPath: stateRoot, |
| }); |
| try { |
| return (await readRuntimeHostSessions(connected.connection)).flatMap((session) => |
| 'kind' in session ? [] : [projectSessionCatalogSummary(session)], |
| ); |
| } finally { |
| await connected.close(); |
| } |
| } |
| |
| async function readProcessStdin(): Promise<string> { |
| const chunks: Buffer[] = []; |
| for await (const chunk of process.stdin) |
| chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); |
| return Buffer.concat(chunks).toString('utf8'); |
| } |