{"version":3,"file":"cliRunnerEntry.mjs","names":[],"sources":["../../../src/services/statusWriter/index.ts","../../../src/bin/cliRunner.ts"],"sourcesContent":["/**\n * Coalesced writer for a run's `status.json`.\n *\n * WHY THIS EXISTS:\n * In the predecessor (`doom-pi-subagents/src/runs/background/subagent-runner.ts`)\n * `status.json` was rewritten in full on every child event, and every rewrite\n * `structuredClone`d the whole run graph first. On a fan-out run that makes the\n * hot path quadratic: N events cost O(N) clones of an O(N)-sized graph. Its\n * `recentTools` and `recentOutput` arrays were also appended to and never\n * trimmed, so a long run's status file, and the cost of cloning it, grew\n * without bound.\n *\n * DESIGN PATTERNS:\n * - Coalescing: `update()` mutates the in-memory status and arms a trailing\n *   ~75ms timer. A burst of events inside that window costs one clone and one\n *   write, not one per event\n * - The clone is gated by a dirty counter, not by the timer firing. A flush\n *   with nothing dirty since the last one performs no clone and no write at\n *   all, which is the specific fix for the quadratic behaviour above\n * - `updateSync()` bypasses the timer for transitions that must not be lost if\n *   the process dies right after: terminal run status, and chain step\n *   transitions. Everything else can wait out the coalescing window\n * - `recentTools` and `recentOutput` are capped at 50 entries, oldest dropped,\n *   on every append, so neither the in-memory status nor the cloned snapshot\n *   grows across a long run\n *\n * SINGLE WRITER CONTRACT (read this before adding another write site):\n * `CoalescedStatusWriter` is the ONLY thing in this package that may write\n * `status.json`. Not \"the preferred way\" - the only way. The predecessor had\n * four unsynchronised writers to the same file, which is exactly how a run's\n * displayed status ends up torn, stale, or overwritten by a slower writer\n * racing a faster one; coalescing a single in-memory owner is what makes that\n * class of bug structurally impossible instead of merely unlikely.\n *\n * Anything else that wants to influence a run's displayed status - pause a\n * step, request a steer, ask a run to stop, report progress from a different\n * process than the one that called `open()` - must NOT open or write\n * `status.json` directly. It writes a sidecar/intent file instead, and the\n * process holding this writer observes that file and folds it into its own\n * `update()`. Readers for those intent files do not exist yet; that is a gap\n * to fill, not a license to reach for `writeAtomicJson(statusPath, ...)`\n * somewhere else. If you are about to write `status.json` from outside this\n * class, stop - that is the bug this module exists to remove.\n *\n * AVOID:\n * - Reading back `status.json` from disk to decide what to write next; the\n *   in-memory status held by `open()` is the only source of truth this class\n *   trusts\n * - Adding a second writer of `status.json` anywhere else in the package,\n *   including \"just this once\" debug or recovery code\n */\n\nimport * as path from 'node:path';\n\nimport { writeAtomicJson } from '../atomicJson';\nimport { scopeRunsDir, type SessionScope } from '../sessionPaths';\n\n/** How long a burst of updates may be buffered before a trailing flush. */\nconst DEFAULT_FLUSH_INTERVAL_MS = 75;\n\n/** Oldest-dropped cap for `recentTools` and `recentOutput`. */\nconst MAX_RECENT_ENTRIES = 50;\n\n/**\n * The one status filename.\n *\n * Exported because the reconciler and the steering reader both have to find\n * this file without going through the writer. Two modules matching a literal\n * by eye is how a rename silently orphans a reader.\n */\nexport const STATUS_FILE_NAME = 'status.json';\n\n/**\n * The minimum shape this writer needs to know about. Everything else in a\n * concrete run's status is opaque to it: capping `recentTools` and\n * `recentOutput` is the one piece of run semantics this module owns, because\n * it is the specific fix for the unbounded-growth bug above.\n */\nexport interface StatusWithRecentEntries {\n  recentTools?: unknown[];\n  recentOutput?: unknown[];\n}\n\nexport interface CoalescedStatusWriterContract<TStatus extends StatusWithRecentEntries = StatusWithRecentEntries> {\n  /**\n   * Start tracking a run's status and write it once, synchronously, so the\n   * file exists before the caller does anything else.\n   *\n   * Calling this again, on the same or a different `runId`, is a reset, not\n   * an error: it discards whatever was previously tracked, including any\n   * mutation buffered by `update()` that had not flushed yet, cancels the\n   * pending coalescing timer, and starts a fresh session against the new\n   * status. This is deliberate so a runner that retries a bootstrap step can\n   * just call `open()` again rather than having to detect and recover from a\n   * half-initialized writer.\n   */\n  open(scope: SessionScope, runId: string, initialStatus: TStatus): void;\n  /** Apply an in-memory mutation and let the trailing timer coalesce the write. */\n  update(mutator: (status: TStatus) => void): void;\n  /** Apply an in-memory mutation and flush synchronously, for transitions that must survive a crash. */\n  updateSync(mutator: (status: TStatus) => void): void;\n  /** Append one entry to `recentTools`, capped at 50, coalesced like `update()`. */\n  appendTool(entry: unknown): void;\n  /** Append one entry to `recentOutput`, capped at 50, coalesced like `update()`. */\n  appendOutput(entry: unknown): void;\n  /** Cancel any pending timer and flush whatever is dirty. Safe to call more than once. */\n  close(): void;\n}\n\nexport class CoalescedStatusWriter<\n  TStatus extends StatusWithRecentEntries = StatusWithRecentEntries,\n> implements CoalescedStatusWriterContract<TStatus> {\n  /**\n   * Runtime tuning seam for tests, kept out of the dependency constructor. A\n   * test subclass overrides this to avoid waiting\n   * out the real window when it does exercise the timer directly instead of\n   * using fake timers.\n   */\n  protected readonly flushIntervalMs: number = DEFAULT_FLUSH_INTERVAL_MS;\n\n  private statusPath: string | undefined;\n  private status: TStatus | undefined;\n  private dirtyCount = 0;\n  private flushTimer: NodeJS.Timeout | undefined;\n\n  open(scope: SessionScope, runId: string, initialStatus: TStatus): void {\n    // A second open() is a reset: cancel whatever this instance was\n    // previously tracking (including an unflushed dirty mutation) rather\n    // than merging with it. See the doc comment on the interface method.\n    if (this.flushTimer) {\n      clearTimeout(this.flushTimer);\n      this.flushTimer = undefined;\n    }\n    this.statusPath = path.join(scopeRunsDir(scope), runId, STATUS_FILE_NAME);\n    this.status = initialStatus;\n    this.dirtyCount = 0;\n    // The initial write is not a coalescing candidate: callers rely on the\n    // file existing as soon as `open()` returns, so it always goes straight\n    // to disk rather than waiting for a timer.\n    writeAtomicJson(this.statusPath, this.clone(initialStatus));\n  }\n\n  update(mutator: (status: TStatus) => void): void {\n    mutator(this.requireStatus());\n    this.dirtyCount++;\n    this.scheduleFlush();\n  }\n\n  updateSync(mutator: (status: TStatus) => void): void {\n    mutator(this.requireStatus());\n    this.dirtyCount++;\n    this.flush();\n  }\n\n  appendTool(entry: unknown): void {\n    this.update((status) => appendCapped(status, 'recentTools', entry));\n  }\n\n  appendOutput(entry: unknown): void {\n    this.update((status) => appendCapped(status, 'recentOutput', entry));\n  }\n\n  close(): void {\n    this.flush();\n  }\n\n  /**\n   * The clone-and-write step. Protected so a test can override `clone()`\n   * alone and assert it was never called across a run of no-op flushes,\n   * which is the direct, observable proof that the dirty-counter gate works.\n   */\n  protected clone<T>(value: T): T {\n    return structuredClone(value);\n  }\n\n  private flush(): void {\n    if (this.flushTimer) {\n      clearTimeout(this.flushTimer);\n      this.flushTimer = undefined;\n    }\n    if (this.dirtyCount === 0) return;\n    const status = this.status;\n    const statusPath = this.statusPath;\n    if (!status || !statusPath) return;\n    const snapshot = this.clone(status);\n    this.dirtyCount = 0;\n    writeAtomicJson(statusPath, snapshot);\n  }\n\n  private scheduleFlush(): void {\n    if (this.flushTimer) return;\n    const timer = setTimeout(() => this.flush(), this.flushIntervalMs);\n    // A pending flush timer must never be the reason the process stays alive.\n    timer.unref?.();\n    this.flushTimer = timer;\n  }\n\n  private requireStatus(): TStatus {\n    if (!this.status) throw new Error('CoalescedStatusWriter used before open().');\n    return this.status;\n  }\n}\n\n/** Push `entry` onto `status[field]`, dropping the oldest until at most 50 remain. */\nfunction appendCapped(status: StatusWithRecentEntries, field: 'recentTools' | 'recentOutput', entry: unknown): void {\n  const list = status[field] ?? [];\n  list.push(entry);\n  if (list.length > MAX_RECENT_ENTRIES) list.splice(0, list.length - MAX_RECENT_ENTRIES);\n  status[field] = list;\n}\n","/**\n * Detached child process for an explicitly selected external agent CLI.\n *\n * Launch, readiness, control, status, and completion use one bounded Node IPC\n * protocol. Durable status and result files are output records only. They are\n * never read to discover live state or coordinate the parent and child.\n */\n\nimport { spawn } from 'node:child_process';\nimport * as fs from 'node:fs';\n\nimport { createDoomTelemetry, type DoomTelemetry } from '@agimon-ai/doompi-telemetry';\n\nimport type { AsyncRunStatus } from '../services/asyncExecution';\nimport { writeAtomicJson, writePrivateAtomicJson } from '../services/atomicJson';\nimport { CLAUDE_FABLE_PROFILE, cleanupClaudeFableLaunch, parseClaudeFableOutput } from '../services/claudeFableProfile';\nimport type { CliLaunchConfig } from '../services/cliLaunchConfig';\nimport {\n  EXTERNAL_IPC_CHANNEL,\n  EXTERNAL_IPC_VERSION,\n  parseExternalParentMessage,\n  sendRunnerMessage,\n  type ExternalRunProjection,\n  type ExternalRunnerMessage,\n} from '../services/externalProcessIpc';\nimport { adoptSessionScopeFromEnv, type SessionScope } from '../services/sessionPaths';\nimport { CoalescedStatusWriter } from '../services/statusWriter';\nimport { SUBAGENT_ROOT_SESSION_ENV, SUBAGENT_RUN_ID_ENV } from '../types/environment';\n\nconst MAX_RESULT_BYTES = 64 * 1024;\nconst MAX_STDERR_BYTES = 8 * 1024;\nconst LAUNCH_TIMEOUT_MS = 15_000;\nconst PARENT_LOST_MESSAGE = 'Parent process was lost.';\nconst FORCE_KILL_SIGNAL: NodeJS.Signals = 'SIGKILL';\nconst STARTUP_ENVIRONMENT: Readonly<Record<string, string | undefined>> = Object.freeze({ ...process.env });\n\nfunction errorMessage(error: unknown): string {\n  return error instanceof Error ? error.message : String(error);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n  return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction validateLaunchConfig(value: Record<string, unknown>, runId: string): CliLaunchConfig {\n  if (\n    value.runId !== runId ||\n    typeof value.agent !== 'string' ||\n    typeof value.task !== 'string' ||\n    typeof value.runtime !== 'string' ||\n    typeof value.command !== 'string' ||\n    !Array.isArray(value.args) ||\n    !value.args.every((entry) => typeof entry === 'string') ||\n    typeof value.cwd !== 'string' ||\n    !isRecord(value.env) ||\n    typeof value.resultPath !== 'string'\n  ) {\n    throw new Error(`CLI launch config for run '${runId}' is invalid.`);\n  }\n  return value as unknown as CliLaunchConfig;\n}\n\nasync function receiveLaunchConfig(scope: SessionScope, runId: string): Promise<CliLaunchConfig> {\n  return await new Promise<CliLaunchConfig>((resolve, reject) => {\n    const finish = (error?: Error, config?: CliLaunchConfig): void => {\n      clearTimeout(timer);\n      process.off('message', onMessage);\n      process.off('disconnect', onDisconnect);\n      if (error) reject(error);\n      else resolve(config!);\n    };\n    const onDisconnect = (): void => finish(new Error(PARENT_LOST_MESSAGE));\n    const onMessage = (raw: unknown): void => {\n      try {\n        const message = parseExternalParentMessage(raw, { runId, scopeKey: scope.scopeKey });\n        if (!message || message.kind !== 'launch') return;\n        finish(undefined, validateLaunchConfig(message.config, runId));\n      } catch (error) {\n        finish(error instanceof Error ? error : new Error(String(error)));\n      }\n    };\n    const timer = setTimeout(\n      () => finish(new Error(`Timed out waiting for CLI launch config for run '${runId}'.`)),\n      LAUNCH_TIMEOUT_MS,\n    );\n    timer.unref();\n    process.on('message', onMessage);\n    process.once('disconnect', onDisconnect);\n  });\n}\n\nfunction appendBounded(buffer: string, chunk: string, limit: number): string {\n  let combined = buffer + chunk;\n  while (Buffer.byteLength(combined, 'utf8') > limit && combined.length > 0) {\n    const excess = Buffer.byteLength(combined, 'utf8') - limit;\n    combined = combined.slice(Math.max(1, Math.ceil(excess / 2)));\n  }\n  return combined;\n}\n\nlet childTelemetry: DoomTelemetry | undefined;\n\nasync function finishTelemetry(event: string, attributes: Record<string, unknown>, error?: unknown): Promise<void> {\n  const telemetry = childTelemetry;\n  childTelemetry = undefined;\n  if (!telemetry) return;\n  if (error === undefined) await telemetry.recordEvent(event, attributes);\n  else await telemetry.recordError(event, error, attributes);\n  await telemetry.flush();\n  await telemetry.shutdown();\n}\n\nfunction projection(\n  config: CliLaunchConfig,\n  state: ExternalRunProjection['state'],\n  startedAt: number,\n  fields: Partial<Pick<ExternalRunProjection, 'summary' | 'error'>> = {},\n): ExternalRunProjection {\n  return {\n    runId: config.runId,\n    agent: config.agent,\n    task: config.sensitiveTask ? '' : config.task,\n    runtime: config.runtime,\n    state,\n    startedAt,\n    updatedAt: Date.now(),\n    cwd: config.cwd,\n    ...fields,\n  };\n}\n\nfunction resultFor(\n  config: CliLaunchConfig,\n  success: boolean,\n  summary: string,\n  exit?: { code: number | null; signal: NodeJS.Signals | null },\n): Record<string, unknown> {\n  return {\n    runId: config.runId,\n    ...(config.operationId ? { operationId: config.operationId } : {}),\n    ...(config.internal ? { internal: true } : {}),\n    agent: config.agent,\n    runtime: config.runtime,\n    success,\n    summary,\n    ...(exit?.code === null || exit?.code === undefined ? {} : { exitCode: exit.code }),\n    ...(exit?.signal ? { signal: exit.signal } : {}),\n  };\n}\n\nfunction runnerMessage(\n  scope: SessionScope,\n  runId: string,\n  message:\n    | { kind: 'ready' }\n    | { kind: 'status'; status: ExternalRunProjection }\n    | { kind: 'result'; result: Record<string, unknown> }\n    | { kind: 'error'; error: string }\n    | { kind: 'ack'; requestId: string; state: 'delivered' | 'failed'; message: string },\n): ExternalRunnerMessage {\n  const base = {\n    channel: EXTERNAL_IPC_CHANNEL,\n    version: EXTERNAL_IPC_VERSION,\n    direction: 'runner',\n    runId,\n    scopeKey: scope.scopeKey,\n  } as const;\n  if (message.kind === 'ack') return { ...base, ...message, command: 'steer' };\n  return { ...base, ...message };\n}\n\nfunction send(scope: SessionScope, runId: string, message: Parameters<typeof runnerMessage>[2]): Promise<void> {\n  return sendRunnerMessage(runnerMessage(scope, runId, message));\n}\n\nasync function run(): Promise<void> {\n  const startedAt = Date.now();\n  const runId = STARTUP_ENVIRONMENT[SUBAGENT_RUN_ID_ENV]?.trim();\n  if (!runId) throw new Error(`${SUBAGENT_RUN_ID_ENV} is required.`);\n  const scope = adoptSessionScopeFromEnv(STARTUP_ENVIRONMENT);\n  if (!scope) {\n    throw new Error(`${SUBAGENT_ROOT_SESSION_ENV} is required: a child cannot resolve its session scope without it.`);\n  }\n  const config = await receiveLaunchConfig(scope, runId);\n\n  childTelemetry = createDoomTelemetry({\n    serviceName: 'doom-team-cli-child',\n    packageName: '@agimon-ai/doompi-team',\n    env: config.env,\n    enableLogs: true,\n    enableTraces: true,\n  });\n\n  const statusWriter = new CoalescedStatusWriter<AsyncRunStatus>();\n  statusWriter.open(scope, runId, {\n    version: 1,\n    runId,\n    ...(config.operationId ? { operationId: config.operationId } : {}),\n    agent: config.agent,\n    ...(config.sensitiveTask ? {} : { task: config.task }),\n    cwd: config.cwd,\n    runtime: config.runtime,\n    state: 'queued',\n    startedAt,\n    lastUpdate: startedAt,\n  });\n\n  await childTelemetry.recordEvent('doom_team.child_started', {\n    runtime: config.runtime,\n    'agent.name': config.agent,\n    outcome: 'started',\n  });\n\n  const profileLaunch = config.profile === CLAUDE_FABLE_PROFILE;\n  if (config.profile !== undefined && !profileLaunch) throw new Error('Untrusted external profile.');\n  if (profileLaunch && (!config.stdinPath || !config.profileResultPath)) {\n    throw new Error('Fable profile launch config is incomplete.');\n  }\n\n  const stdinFd = profileLaunch ? fs.openSync(config.stdinPath!, 'r') : undefined;\n  let child: ReturnType<typeof spawn>;\n  try {\n    child = spawn(config.command, config.args, {\n      cwd: config.cwd,\n      env: config.env,\n      stdio: [stdinFd ?? 'ignore', 'pipe', 'pipe'],\n      detached: true,\n      windowsHide: true,\n    });\n  } finally {\n    if (stdinFd !== undefined) fs.closeSync(stdinFd);\n  }\n\n  const cleanupProfile = (removeResult: boolean): void => {\n    if (!profileLaunch) return;\n    cleanupClaudeFableLaunch({ cleanupPaths: config.cleanupPaths ?? [] });\n    if (removeResult && config.profileResultPath) fs.rmSync(config.profileResultPath, { force: true });\n  };\n\n  let requestedSignal: NodeJS.Signals | undefined;\n  let finalizing = false;\n  const stopVendor = (signal: NodeJS.Signals): void => {\n    requestedSignal = signal;\n    if (!child.pid) return;\n    try {\n      process.kill(-child.pid, signal);\n    } catch (error) {\n      process.stderr.write(`[doom-team cli runner] Could not signal vendor process: ${errorMessage(error)}\\n`);\n    }\n  };\n  const onSigterm = (): void => stopVendor('SIGTERM');\n  const onSigint = (): void => stopVendor('SIGINT');\n  process.once('SIGTERM', onSigterm);\n  process.once('SIGINT', onSigint);\n\n  const onControl = (raw: unknown): void => {\n    const message = parseExternalParentMessage(raw, { runId, scopeKey: scope.scopeKey });\n    if (!message || message.kind !== 'control') return;\n    if (message.command === 'steer') {\n      void send(scope, runId, {\n        kind: 'ack',\n        requestId: message.requestId,\n        state: 'failed',\n        message: `Runtime '${config.runtime}' does not support live steering.`,\n      });\n      return;\n    }\n    stopVendor(message.command === 'interrupt' ? 'SIGINT' : 'SIGTERM');\n  };\n  process.on('message', onControl);\n\n  const onParentLost = (): void => {\n    if (finalizing) return;\n    finalizing = true;\n    stopVendor(FORCE_KILL_SIGNAL);\n    const endedAt = Date.now();\n    statusWriter.updateSync((status) => {\n      status.state = 'stopped';\n      status.error = PARENT_LOST_MESSAGE;\n      status.lastUpdate = endedAt;\n      status.endedAt = endedAt;\n    });\n    writeAtomicJson(config.resultPath, {\n      ...resultFor(config, false, PARENT_LOST_MESSAGE, { code: null, signal: FORCE_KILL_SIGNAL }),\n      state: 'stopped',\n      ...(config.internal ? { internal: true } : {}),\n    });\n    statusWriter.close();\n    cleanupProfile(true);\n    void finishTelemetry('doom_team.child_finished', {\n      runtime: config.runtime,\n      'agent.name': config.agent,\n      duration_ms: Date.now() - startedAt,\n      outcome: 'parent_lost',\n    }).finally(() => process.exit(0));\n  };\n  process.once('disconnect', onParentLost);\n\n  try {\n    await new Promise<void>((resolve, reject) => {\n      child.once('spawn', resolve);\n      child.once('error', reject);\n    });\n    const running = projection(config, 'running', startedAt);\n    statusWriter.updateSync((status) => {\n      status.state = 'running';\n      status.activityState = 'working';\n      status.lastUpdate = Date.now();\n    });\n    await send(scope, runId, { kind: 'status', status: running });\n    await send(scope, runId, { kind: 'ready' });\n  } catch (error) {\n    finalizing = true;\n    const message = errorMessage(error);\n    const endedAt = Date.now();\n    const result = {\n      ...resultFor(config, false, profileLaunch ? 'Fable Claude launch failed.' : message),\n      state: 'failed',\n    };\n    statusWriter.updateSync((status) => {\n      status.state = 'failed';\n      status.error = message;\n      status.lastUpdate = endedAt;\n      status.endedAt = endedAt;\n    });\n    writeAtomicJson(config.resultPath, { ...result, ...(config.internal ? { internal: true } : {}) });\n    await send(scope, runId, { kind: 'error', error: message });\n    await send(scope, runId, { kind: 'result', result });\n    statusWriter.close();\n    cleanupProfile(true);\n    await finishTelemetry(\n      'doom_team.child_failed',\n      { runtime: config.runtime, 'agent.name': config.agent, duration_ms: Date.now() - startedAt, outcome: 'failed' },\n      error,\n    );\n    process.exit(1);\n  }\n\n  let stdout = '';\n  let stderr = '';\n  child.stdout?.on('data', (chunk: Buffer) => {\n    stdout = appendBounded(stdout, chunk.toString('utf8'), MAX_RESULT_BYTES);\n  });\n  child.stderr?.on('data', (chunk: Buffer) => {\n    stderr = appendBounded(stderr, chunk.toString('utf8'), MAX_STDERR_BYTES);\n  });\n\n  const exit = await new Promise<{ code: number | null; signal: NodeJS.Signals | null; error?: Error }>((resolve) => {\n    child.on('error', (error) => resolve({ code: null, signal: null, error }));\n    child.on('close', (code, signal) => resolve({ code, signal }));\n  });\n  finalizing = true;\n  process.off('message', onControl);\n  process.off('disconnect', onParentLost);\n  process.off('SIGTERM', onSigterm);\n  process.off('SIGINT', onSigint);\n\n  const stopped = requestedSignal !== undefined;\n  let profileError: Error | undefined;\n  if (profileLaunch && !stopped && exit.error === undefined && exit.code === 0) {\n    try {\n      const parsed = parseClaudeFableOutput(stdout);\n      writePrivateAtomicJson(config.profileResultPath!, { text: parsed.text, outputBytes: parsed.outputBytes });\n    } catch (error) {\n      profileError = error instanceof Error ? error : new Error(String(error));\n    }\n  }\n\n  const success = !stopped && exit.error === undefined && exit.code === 0 && profileError === undefined;\n  const failureDetail = profileLaunch\n    ? (profileError?.message ?? 'Fable Claude launch failed.')\n    : stderr.trim() || `${config.runtime} exited with ${exit.signal ?? `code ${exit.code ?? 'unknown'}`}.`;\n  const summary = stopped\n    ? `Stopped by ${requestedSignal}.`\n    : success\n      ? profileLaunch\n        ? 'Fable profile completed.'\n        : stdout.trim()\n      : (exit.error?.message ?? failureDetail);\n  const endedAt = Date.now();\n  const state = stopped ? ('stopped' as const) : success ? ('completed' as const) : ('failed' as const);\n  const result = { ...resultFor(config, success, summary, exit), state };\n\n  statusWriter.updateSync((status) => {\n    status.state = state;\n    status.activityState = 'finalizing';\n    status.summary = summary;\n    if (!success) status.error = summary;\n    status.lastUpdate = endedAt;\n    status.endedAt = endedAt;\n  });\n  writeAtomicJson(config.resultPath, { ...result, ...(config.internal ? { internal: true } : {}) });\n  await send(scope, runId, {\n    kind: 'status',\n    status: projection(config, state, startedAt, {\n      summary,\n      ...(!success ? { error: summary } : {}),\n    }),\n  });\n  await send(scope, runId, { kind: 'result', result });\n\n  statusWriter.close();\n  cleanupProfile(!success);\n  await finishTelemetry('doom_team.child_finished', {\n    runtime: config.runtime,\n    'agent.name': config.agent,\n    duration_ms: Date.now() - startedAt,\n    'runner.stdout_bytes': Buffer.byteLength(stdout, 'utf8'),\n    'runner.stderr_bytes': Buffer.byteLength(stderr, 'utf8'),\n    'runner.exit_code': exit.code ?? 0,\n    outcome: state,\n  });\n  process.exit(0);\n}\n\nvoid run().catch(async (error: unknown) => {\n  const runId = STARTUP_ENVIRONMENT[SUBAGENT_RUN_ID_ENV]?.trim();\n  const scope = adoptSessionScopeFromEnv(STARTUP_ENVIRONMENT);\n  if (runId && scope && process.connected) {\n    try {\n      await send(scope, runId, { kind: 'error', error: errorMessage(error) });\n    } catch {\n      // The parent channel is already gone, so stderr is the only remaining diagnostic.\n    }\n  }\n  await finishTelemetry('doom_team.child_failed', { outcome: 'failed' }, error);\n  process.stderr.write(`[doom-team cli runner] ${errorMessage(error)}\\n`);\n  process.exitCode = 1;\n});\n"],"mappings":"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"}