{"version":3,"file":"adapter.mjs","names":["#options","#assertLive","#state","#loadPromise","#start","#loadReject","#retire","#disposePromise","#dispose","#process","#protocol","#call","#epoch","#detach","#request","#settle","#onFrame","#listeners","#maybeSettle","#readImage","#defaultFs","#cleanupPath","#rid","#beginStop"],"sources":["../../../../src/batteries/generation/local_diffusion/adapter.ts"],"sourcesContent":["/** Node-only local diffusion generation adapter.\n * @module @nhtio/adk/batteries/generation/local_diffusion/adapter\n */\nimport { toBytes } from '../_shared'\nimport { isError } from '@nhtio/adk/guards'\nimport { validateOptions } from './validation'\nimport { emitLifecycle } from '../../llm/chat_common/lifecycle'\nimport { decodeBase64, encodeBase64 } from '../../../lib/helpers/base64'\nimport {\n  DEFAULT_PROTOCOL,\n  buildEditCommand,\n  buildGenerateCommand,\n  buildShutdownCommand,\n  buildStopCommand,\n  createFrameReader,\n} from './protocol'\nimport {\n  E_LOCAL_DIFFUSION_ABORTED,\n  E_LOCAL_DIFFUSION_BACKEND_ERROR,\n  E_LOCAL_DIFFUSION_BUSY,\n  E_LOCAL_DIFFUSION_DISPOSED,\n  E_LOCAL_DIFFUSION_MALFORMED_FRAME,\n  E_LOCAL_DIFFUSION_REQUEST_TIMEOUT,\n  E_LOCAL_DIFFUSION_STARTUP_TIMEOUT,\n} from './exceptions'\nimport type { GenerationImageInput } from '../_shared'\nimport type { GeneratedMediaOutput } from '../openai/types'\nimport type { ParsedFrame, ProtocolConfig } from './protocol'\nimport type {\n  DiffusionBackendProcess,\n  DiffusionFsLike,\n  LocalDiffusionCallOptions,\n  LocalDiffusionGenerationAdapterOptions,\n} from './types'\n\nconst BATTERY = 'local_diffusion_generation' as const\nconst DEFAULT_MAX_DECODED_BYTES = 50 * 1024 * 1024\n\ntype Deferred<T> = { promise: Promise<T>; resolve(value: T): void; reject(error: unknown): void }\n\n/** How a request ended, so {@link LocalDiffusionGenerationAdapter} settles it exactly once. */\ntype Outcome = { kind: 'success' } | { kind: 'error'; error: unknown } | { kind: 'aborted' }\n\n/** The rejection error for a non-success outcome (an aborted outcome maps to the typed abort error). */\nconst outcomeError = (outcome: Exclude<Outcome, { kind: 'success' }>): unknown =>\n  outcome.kind === 'aborted' ? new E_LOCAL_DIFFUSION_ABORTED() : outcome.error\n\n/** Mutable state for the single in-flight request. */\ntype RequestState = {\n  /** The monotonic request id fenced against backend frames. */\n  rid: number\n  /** The caller-facing settlement. */\n  done: Deferred<GeneratedMediaOutput[]>\n  /** Accumulated image outputs. */\n  outputs: GeneratedMediaOutput[]\n  /** Count of in-flight image reads not yet settled. */\n  pending: number\n  /** The recorded terminal outcome, set once; the request settles when `pending` reaches 0. */\n  outcome: Outcome | undefined\n  /** Whether the caller's promise has been settled (may precede full termination on abort/timeout). */\n  settled: boolean\n  /** Whether full terminal settlement (slot release + one lifecycle event) has already run. */\n  terminated: boolean\n  /** The abort/timeout grace timer holding the slot in `stopping`, if any. */\n  stopTimer: ReturnType<typeof setTimeout> | undefined\n  /** The request-timeout timer, if any. */\n  requestTimer: ReturnType<typeof setTimeout> | undefined\n  /** Detaches the caller's abort listener, if any. */\n  detachSignal: (() => void) | undefined\n}\n\nconst deferred = <T>(): Deferred<T> => {\n  let resolve!: (value: T) => void\n  let reject!: (error: unknown) => void\n  const promise = new Promise<T>((res, rej) => {\n    resolve = res\n    reject = rej\n  })\n  return { promise, resolve, reject }\n}\n\n/**\n * Whether `candidate` resolves to a location strictly inside `dir` — real containment (resolves `..`\n * and rejects sibling-prefix escapes like `/output2`), not a raw string `startsWith`.\n */\nconst isContained = (dir: string, candidate: string, sep: string): boolean => {\n  const base = dir.endsWith(sep) ? dir.slice(0, -sep.length) : dir\n  return candidate === base || candidate.startsWith(base + sep)\n}\n\n/** Local diffusion adapter backed by a line-protocol subprocess. */\nexport class LocalDiffusionGenerationAdapter {\n  readonly #options: LocalDiffusionGenerationAdapterOptions\n  #process: DiffusionBackendProcess | undefined\n  #epoch = 0\n  #state: 'idle' | 'starting' | 'ready' | 'running' | 'stopping' | 'disposed' = 'idle'\n  #loadPromise: Promise<void> | undefined\n  #loadReject: ((error: unknown) => void) | undefined\n  #request: RequestState | undefined\n  #rid = 0\n  #disposePromise: Promise<void> | undefined\n  #listeners: Array<{\n    target: { off(event: string, listener: (...args: any[]) => void): void }\n    event: string\n    fn: (...args: any[]) => void\n  }> = []\n\n  /** Whether the current host exposes Node's process runtime. */\n  public static isAvailable(): boolean {\n    return typeof process !== 'undefined' && Boolean(process.versions?.node)\n  }\n\n  /** Construct and validate an adapter. */\n  constructor(options: unknown) {\n    this.#options = validateOptions(options)\n  }\n\n  /** Whether this instance can run, honoring its injected probe. */\n  public isAvailable(): boolean {\n    return (this.#options.isAvailable ?? LocalDiffusionGenerationAdapter.isAvailable)()\n  }\n\n  /** Start the backend and wait for its ready event. */\n  public async preload(): Promise<void> {\n    this.#assertLive()\n    if (this.#state === 'ready' || this.#state === 'running' || this.#state === 'stopping') return\n    if (this.#loadPromise) return this.#loadPromise\n    const loadPromise = this.#start()\n    this.#loadPromise = loadPromise\n    loadPromise\n      .catch(() => undefined)\n      .finally(() => {\n        // Only clear if this exact load is still the current one (a reset/dispose may have replaced it).\n        if (this.#loadPromise === loadPromise) {\n          this.#loadPromise = undefined\n          this.#loadReject = undefined\n        }\n      })\n    return loadPromise\n  }\n\n  /** Synchronously terminate the current process and invalidate all continuations. */\n  public reset(): void {\n    if (this.#state === 'disposed') return\n    this.#retire(new E_LOCAL_DIFFUSION_BACKEND_ERROR(['adapter reset']))\n    this.#state = 'idle'\n  }\n\n  /** Gracefully stop the backend, then force terminate it. Terminal and idempotent. */\n  public dispose(): Promise<void> {\n    if (this.#disposePromise) return this.#disposePromise\n    this.#disposePromise = this.#dispose()\n    return this.#disposePromise\n  }\n\n  async #dispose(): Promise<void> {\n    const process = this.#process\n    // Move to the terminal state SYNCHRONOUSLY so any concurrent call sees `disposed` (not `ready`)\n    // and every in-flight continuation is fenced by the epoch bump inside #retire. Defer the kill:\n    // the two-phase teardown sends `__shutdown__`, waits `disposeGraceMs`, then force-kills once below.\n    this.#retire(new E_LOCAL_DIFFUSION_DISPOSED(), false)\n    this.#state = 'disposed'\n    if (process && process.stdin) {\n      try {\n        process.stdin.write(buildShutdownCommand(this.#protocol()))\n      } catch {\n        /* teardown — the process may already be gone */\n      }\n      await new Promise<void>((resolve) => {\n        const timer = setTimeout(resolve, this.#options.disposeGraceMs ?? 5000)\n        const finish = (): void => {\n          clearTimeout(timer)\n          process.off('close', finish)\n          resolve()\n        }\n        process.on('close', finish)\n      })\n    }\n    process?.kill()\n  }\n\n  /** Async-disposal protocol alias. */\n  public async [Symbol.asyncDispose](): Promise<void> {\n    await this.dispose()\n  }\n\n  /** Generate images from text. */\n  public async generate(\n    prompt: string,\n    opts?: LocalDiffusionCallOptions\n  ): Promise<GeneratedMediaOutput[]> {\n    return this.#call('generate', prompt, undefined, opts)\n  }\n\n  /** Edit images with text guidance. */\n  public async edit(\n    inputs: GenerationImageInput | GenerationImageInput[],\n    prompt: string,\n    opts?: LocalDiffusionCallOptions\n  ): Promise<GeneratedMediaOutput[]> {\n    const normalized = await Promise.all((Array.isArray(inputs) ? inputs : [inputs]).map(toBytes))\n    return this.#call('edit', prompt, normalized, opts)\n  }\n\n  #assertLive(): void {\n    if (this.#state === 'disposed' || this.#disposePromise) throw new E_LOCAL_DIFFUSION_DISPOSED()\n  }\n\n  #protocol(): ProtocolConfig {\n    const o = this.#options\n    return {\n      ...DEFAULT_PROTOCOL,\n      ...(o.protocol ?? {}),\n      commandPrefix: o.commandPrefix ?? o.protocol?.commandPrefix ?? DEFAULT_PROTOCOL.commandPrefix,\n      eventPrefix: o.eventPrefix ?? o.protocol?.eventPrefix ?? DEFAULT_PROTOCOL.eventPrefix,\n      ops: { ...DEFAULT_PROTOCOL.ops, ...o.protocol?.ops, ...o.ops },\n      control: { ...DEFAULT_PROTOCOL.control, ...o.protocol?.control, ...o.control },\n      events: { ...DEFAULT_PROTOCOL.events, ...o.protocol?.events, ...o.events },\n    }\n  }\n\n  /**\n   * Tear down the current process + request atomically: bump the epoch (fencing every outstanding\n   * continuation), detach listeners, reject an in-flight preload, record the request's terminal\n   * outcome (which settles once its pending reads drain), and drop cached handles. Kills the child\n   * unless `kill` is false — `dispose()` defers the kill until after its graceful-shutdown wait.\n   * Leaves `#state` for the caller to set (`idle` for reset, `disposed` for dispose).\n   */\n  #retire(error: unknown, kill = true): void {\n    this.#epoch += 1\n    this.#detach()\n    this.#loadReject?.(error)\n    this.#loadReject = undefined\n    this.#loadPromise = undefined\n    const request = this.#request\n    if (request) this.#settle(request, { kind: 'error', error })\n    if (kill) this.#process?.kill()\n    this.#process = undefined\n  }\n\n  async #start(): Promise<void> {\n    this.#assertLive()\n    if (!this.isAvailable())\n      throw new E_LOCAL_DIFFUSION_BACKEND_ERROR(['Node process is unavailable'])\n    const epoch = ++this.#epoch\n    const startupTimeoutMs = this.#options.startupTimeoutMs ?? 30_000\n    this.#state = 'starting'\n    const spawner =\n      this.#options.spawn ??\n      (async (ctx): Promise<DiffusionBackendProcess> => {\n        const { spawn } = await import('node:child_process')\n        const child = spawn(ctx.command, ctx.args, {\n          stdio: ['pipe', 'pipe', 'pipe'],\n        }) as unknown as DiffusionBackendProcess\n        if (!child.stdin || !child.stdout)\n          throw new E_LOCAL_DIFFUSION_BACKEND_ERROR(['backend stdin/stdout unavailable'])\n        return child\n      })\n    let process: DiffusionBackendProcess\n    try {\n      process = await spawner({\n        command: this.#options.command,\n        args: this.#options.args ?? [],\n        model: this.#options.model,\n      })\n    } catch (error) {\n      throw new E_LOCAL_DIFFUSION_BACKEND_ERROR([isError(error) ? error.message : String(error)])\n    }\n    if (epoch !== this.#epoch || (this.#state as string) === 'disposed') {\n      process.kill()\n      throw new E_LOCAL_DIFFUSION_BACKEND_ERROR(['startup superseded'])\n    }\n    if (!process.stdin || !process.stdout)\n      throw new E_LOCAL_DIFFUSION_BACKEND_ERROR(['backend stdin/stdout unavailable'])\n    this.#process = process\n    const wait = deferred<void>()\n    this.#loadReject = wait.reject\n    const reader = createFrameReader({\n      maxLineBytes: this.#options.maxLineBytes,\n      config: this.#protocol(),\n      onFrame: (frame) => this.#onFrame(frame, epoch, wait),\n    })\n    const onData = (chunk: Uint8Array): void => {\n      if (epoch === this.#epoch) reader.push(chunk)\n    }\n    const onStreamEnd = (): void => {\n      if (epoch !== this.#epoch) return\n      // Flush any trailing partial line, THEN treat the closed stdout as a terminal transport failure:\n      // a backend that closes its stdout can produce no further frames, so a startup or in-flight\n      // request would otherwise hang forever (permanent BUSY). failure() retires + settles it.\n      reader.end()\n      failure('backend stdout closed', null, null)\n    }\n    const failure = (detail: string, code: number | null, signal: NodeJS.Signals | null): void => {\n      if (epoch !== this.#epoch) return\n      const error =\n        this.#state === 'starting'\n          ? new E_LOCAL_DIFFUSION_STARTUP_TIMEOUT([startupTimeoutMs])\n          : new E_LOCAL_DIFFUSION_BACKEND_ERROR([detail, code ?? undefined, signal ?? undefined])\n      // Fully retire the process so no stale `#request`/`#process`/`ready` survives. Detach + kill the\n      // child (a startup-timeout child may still be alive) and bump the epoch so any late frame from\n      // it — including `ready` — is fenced out.\n      wait.reject(error)\n      this.#detach()\n      this.#loadReject = undefined\n      this.#loadPromise = undefined\n      const request = this.#request\n      if (request) this.#settle(request, { kind: 'error', error })\n      this.#process?.kill()\n      this.#process = undefined\n      this.#epoch += 1\n      this.#state = 'idle'\n    }\n    const onError = (): void => failure('backend error', null, null)\n    const onExit = (code: number | null, signal: NodeJS.Signals | null): void =>\n      failure('backend exited', code, signal)\n    const onStdoutError = (): void => failure('backend stdout error', null, null)\n    process.stdout.on('data', onData)\n    process.stdout.on('end', onStreamEnd)\n    process.stdout.on('error', onStdoutError)\n    process.on('error', onError)\n    process.on('exit', onExit)\n    process.on('close', onExit)\n    this.#listeners.push(\n      { target: process.stdout, event: 'data', fn: onData },\n      { target: process.stdout, event: 'end', fn: onStreamEnd },\n      { target: process.stdout, event: 'error', fn: onStdoutError },\n      { target: process, event: 'error', fn: onError },\n      { target: process, event: 'exit', fn: onExit },\n      { target: process, event: 'close', fn: onExit }\n    )\n    await new Promise<void>((resolve, reject) => {\n      const timer = setTimeout(() => {\n        // Route the timeout through failure() so the still-running child is killed + detached + fenced\n        // (state → idle, epoch bumped) — a late `ready` from the timed-out child cannot revive it.\n        failure('startup timed out', null, null)\n      }, startupTimeoutMs)\n      wait.promise.then(\n        () => {\n          clearTimeout(timer)\n          resolve()\n        },\n        (error) => {\n          clearTimeout(timer)\n          reject(error)\n        }\n      )\n    })\n    if (epoch !== this.#epoch) throw new E_LOCAL_DIFFUSION_BACKEND_ERROR(['startup superseded'])\n  }\n\n  #onFrame(frame: ParsedFrame, epoch: number, load: Deferred<void>): void {\n    if (epoch !== this.#epoch) return\n    if (frame.kind === 'modelLoad') {\n      emitLifecycle(this.#options, BATTERY, this.#options.model, 'loading', {\n        progress: frame.progress,\n      })\n      return\n    }\n    if (frame.kind === 'ready') {\n      // Only a pending startup transitions to ready; a duplicate/unsolicited `ready` during\n      // running/stopping must not reopen the slot or overwrite the active request.\n      if (this.#state !== 'starting') return\n      this.#state = 'ready'\n      emitLifecycle(this.#options, BATTERY, this.#options.model, 'ready')\n      load.resolve(undefined)\n      return\n    }\n    const request = this.#request\n    if (frame.kind === 'malformed' || frame.kind === 'protocolError') {\n      // Record the terminal outcome but hold the slot until pending image reads drain (like `done`).\n      if (request) {\n        if (!request.outcome)\n          request.outcome = {\n            kind: 'error',\n            error: new E_LOCAL_DIFFUSION_MALFORMED_FRAME([frame.detail]),\n          }\n        this.#maybeSettle(request)\n      }\n      return\n    }\n    // Every remaining frame kind carries a rid; fence it against the current request.\n    if (!request || !('rid' in frame) || frame.rid !== request.rid) return\n    if (frame.kind === 'progress') {\n      emitLifecycle(this.#options, BATTERY, this.#options.model, 'generating', {\n        progress: frame.progress,\n      })\n      return\n    }\n    if (frame.kind === 'image') {\n      request.pending += 1\n      void this.#readImage(frame.payload)\n        .then((output) => {\n          if (this.#request === request) request.outputs.push(output)\n        })\n        .catch((error) => {\n          // Fence against a retired/superseded request: only the current request records an outcome.\n          if (this.#request === request && !request.outcome)\n            request.outcome = { kind: 'error', error }\n        })\n        .finally(() => {\n          request.pending -= 1\n          this.#maybeSettle(request)\n        })\n      return\n    }\n    if (frame.kind === 'error') {\n      if (!request.outcome)\n        request.outcome = {\n          kind: 'error',\n          error: new E_LOCAL_DIFFUSION_BACKEND_ERROR([frame.message]),\n        }\n      this.#maybeSettle(request)\n      return\n    }\n    if (frame.kind === 'done') {\n      if (!request.outcome) request.outcome = { kind: 'success' }\n      this.#maybeSettle(request)\n    }\n  }\n\n  /** Settle `request` once its recorded terminal outcome is set AND all image reads have drained. */\n  #maybeSettle(request: RequestState): void {\n    if (request.outcome && request.pending === 0) this.#settle(request, request.outcome)\n  }\n\n  async #readImage(payload: {\n    path?: string\n    b64?: string\n    mimeType: string\n  }): Promise<GeneratedMediaOutput> {\n    const maxDecoded = this.#options.maxDecodedBytes ?? DEFAULT_MAX_DECODED_BYTES\n    let bytes: Uint8Array\n    let filename: string | undefined\n    if (payload.b64 !== undefined) {\n      if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(payload.b64))\n        throw new E_LOCAL_DIFFUSION_MALFORMED_FRAME(['invalid base64'])\n      // Bound memory BEFORE allocating: decoded length ≈ 3/4 of the base64 char count.\n      const approxDecoded = Math.floor((payload.b64.length * 3) / 4)\n      if (approxDecoded > maxDecoded)\n        throw new E_LOCAL_DIFFUSION_MALFORMED_FRAME(['inline image exceeds maxDecodedBytes'])\n      bytes = decodeBase64(payload.b64)\n    } else if (payload.path !== undefined) {\n      const requestPath = payload.path\n      const fs = this.#options.fs ?? (await this.#defaultFs())\n      try {\n        bytes = await fs.readFile(requestPath)\n      } finally {\n        await this.#cleanupPath(fs, requestPath)\n      }\n      filename = requestPath.split(/[\\\\/]/).pop()\n    } else throw new E_LOCAL_DIFFUSION_MALFORMED_FRAME(['image has no data'])\n    if (\n      bytes.length === 0 ||\n      bytes.length > maxDecoded ||\n      !/^image\\/(png|jpeg|jpg|webp|gif|avif)$/i.test(payload.mimeType)\n    )\n      throw new E_LOCAL_DIFFUSION_MALFORMED_FRAME(['invalid image output'])\n    return { kind: 'image', mimeType: payload.mimeType, bytes, ...(filename ? { filename } : {}) }\n  }\n\n  /**\n   * Delete a backend-written result file ONLY when its LEXICALLY-resolved path is strictly inside\n   * `outputDir` (resolves `..`, rejects sibling-prefix escapes). Containment is lexical, not\n   * realpath-based: a symlink placed inside `outputDir` that points elsewhere is NOT followed-and-checked\n   * — this cleanup trusts that the consumer's own `outputDir` does not contain adversarial symlinks.\n   * Cleanup failure is diagnostic-only and never fails the generation.\n   */\n  async #cleanupPath(fs: DiffusionFsLike, filePath: string): Promise<void> {\n    const outputDir = this.#options.outputDir\n    if (!outputDir) return\n    try {\n      const path = await import('node:path')\n      const resolvedDir = path.resolve(outputDir)\n      const resolvedPath = path.resolve(filePath)\n      if (!isContained(resolvedDir, resolvedPath, path.sep)) return\n      await fs.unlink(filePath)\n    } catch {\n      /* diagnostic-only cleanup — a failed unlink never fails the generation */\n    }\n  }\n\n  async #defaultFs(): Promise<DiffusionFsLike> {\n    const fs = await import('node:fs/promises')\n    return {\n      readFile: (path) => fs.readFile(path),\n      unlink: (path) => fs.unlink(path).then(() => undefined),\n    }\n  }\n\n  async #call(\n    kind: 'generate' | 'edit',\n    prompt: string,\n    inputs?: Array<{ bytes: Uint8Array; mimeType?: string }>,\n    opts?: LocalDiffusionCallOptions\n  ): Promise<GeneratedMediaOutput[]> {\n    this.#assertLive()\n    if (this.#state === 'running' || this.#state === 'stopping') throw new E_LOCAL_DIFFUSION_BUSY()\n    await this.preload()\n    this.#assertLive()\n    if (this.#state !== 'ready') throw new E_LOCAL_DIFFUSION_BUSY()\n    const o = this.#options\n    const rid = this.#rid++\n    const request: RequestState = {\n      rid,\n      done: deferred<GeneratedMediaOutput[]>(),\n      outputs: [],\n      pending: 0,\n      outcome: undefined,\n      settled: false,\n      terminated: false,\n      stopTimer: undefined,\n      requestTimer: undefined,\n      detachSignal: undefined,\n    }\n    this.#request = request\n    this.#state = 'running'\n    emitLifecycle(o, BATTERY, o.model, 'generating')\n    const args: Record<string, unknown> = {\n      prompt,\n      ...(kind === 'edit'\n        ? {\n            images: (inputs ?? []).map((input) => ({\n              b64: encodeBase64(input.bytes),\n              ...(input.mimeType ? { mimeType: input.mimeType } : {}),\n            })),\n          }\n        : {}),\n      ...Object.fromEntries(\n        Object.entries({\n          negativePrompt: opts?.negativePrompt ?? o.negativePrompt,\n          steps: opts?.steps ?? o.steps,\n          cfgScale: opts?.cfgScale ?? o.cfgScale,\n          sampler: opts?.sampler ?? o.sampler,\n          seed: opts?.seed ?? o.seed,\n          width: opts?.width ?? o.width,\n          height: opts?.height ?? o.height,\n        }).filter(([, value]) => value !== undefined)\n      ),\n    }\n    try {\n      this.#process?.stdin?.write(\n        kind === 'edit'\n          ? buildEditCommand(rid, args, this.#protocol())\n          : buildGenerateCommand(rid, args, this.#protocol())\n      )\n    } catch (error) {\n      this.#settle(request, {\n        kind: 'error',\n        error: new E_LOCAL_DIFFUSION_BACKEND_ERROR([\n          isError(error) ? error.message : String(error),\n        ]),\n      })\n      return request.done.promise\n    }\n    if (opts?.signal) {\n      const signal = opts.signal\n      const abort = (): void => this.#beginStop(request, { kind: 'aborted' })\n      if (signal.aborted) abort()\n      else {\n        signal.addEventListener('abort', abort, { once: true })\n        request.detachSignal = () => signal.removeEventListener('abort', abort)\n      }\n    }\n    if (o.requestTimeoutMs)\n      request.requestTimer = setTimeout(\n        () =>\n          this.#beginStop(request, {\n            kind: 'error',\n            error: new E_LOCAL_DIFFUSION_REQUEST_TIMEOUT([o.requestTimeoutMs as number]),\n          }),\n        o.requestTimeoutMs\n      )\n    return request.done.promise\n  }\n\n  /**\n   * Begin a best-effort stop of `request` (abort or request-timeout): reject the caller now with the\n   * terminal `outcome`, but HOLD the single-flight slot in `stopping` (writing advisory `__stop__`)\n   * until the cancelled rid's terminal frame arrives OR `abortGraceMs` elapses → kill + respawn.\n   */\n  #beginStop(request: RequestState, outcome: Exclude<Outcome, { kind: 'success' }>): void {\n    if (this.#request !== request || this.#state !== 'running') return\n    this.#state = 'stopping'\n    try {\n      this.#process?.stdin?.write(buildStopCommand(request.rid, this.#protocol()))\n    } catch {\n      /* advisory — the backend may ignore or never receive __stop__ */\n    }\n    // Record the terminal outcome + reject the caller immediately, but keep the slot held: the request\n    // is not cleared until its reads drain (settle) or the grace timer fires.\n    if (!request.outcome) request.outcome = outcome\n    if (!request.settled) request.done.reject(outcomeError(outcome))\n    request.settled = true\n    request.detachSignal?.()\n    request.detachSignal = undefined\n    if (request.requestTimer) clearTimeout(request.requestTimer)\n    request.requestTimer = undefined\n    request.stopTimer = setTimeout(() => {\n      if (this.#request === request) this.reset()\n    }, this.#options.abortGraceMs ?? 5000)\n  }\n\n  /**\n   * Fully settle `request` exactly once: clear its timers, release the single-flight slot (only if it\n   * is still the current request), settle the caller's promise if not already done, and emit exactly\n   * ONE terminal lifecycle event. Idempotent — a second call (e.g. a drained pending read after an\n   * earlier retirement) is a no-op, so no duplicate lifecycle/promise settlement can occur.\n   */\n  #settle(request: RequestState, outcome: Outcome): void {\n    if (request.terminated) return\n    request.terminated = true\n    if (request.stopTimer) clearTimeout(request.stopTimer)\n    request.stopTimer = undefined\n    if (request.requestTimer) clearTimeout(request.requestTimer)\n    request.requestTimer = undefined\n    request.detachSignal?.()\n    request.detachSignal = undefined\n    if (this.#request === request) {\n      this.#request = undefined\n      if (this.#state === 'running' || this.#state === 'stopping') this.#state = 'ready'\n    }\n    if (!request.settled) {\n      request.settled = true\n      if (outcome.kind === 'success') request.done.resolve(request.outputs)\n      else request.done.reject(outcomeError(outcome))\n    }\n    // The lifecycle event mirrors the true outcome, never a spurious `complete` after a failure/abort.\n    if (outcome.kind === 'success')\n      emitLifecycle(this.#options, BATTERY, this.#options.model, 'complete')\n    else\n      emitLifecycle(this.#options, BATTERY, this.#options.model, 'error', {\n        error: outcomeError(outcome),\n      })\n  }\n\n  #detach(): void {\n    for (const listener of this.#listeners) listener.target.off(listener.event, listener.fn)\n    this.#listeners = []\n  }\n}\n"],"mappings":"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"}