{"version":3,"file":"cordisHost.cjs","names":["Context","DOOM_TOOL_OVERRIDES_SERVICE","createDoomToolOverridesService","DOOM_CONTEXT_CONTRIBUTIONS_SERVICE","createDoomContextContributionsService"],"sources":["../../../src/adapters/pi/cordisHost.ts"],"sourcesContent":["import { Context, type Fiber } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext, SessionStartEvent } from '@earendil-works/pi-coding-agent';\nimport { DOOM_CONTEXT_CONTRIBUTIONS_SERVICE } from '../../schemas/contextContributions.ts';\nimport { DOOM_TOOL_OVERRIDES_SERVICE } from '../../schemas/toolOverrides.ts';\nimport { createDoomContextContributionsService } from '../../services/contextContributions.ts';\nimport { createDoomToolOverridesService } from '../../services/toolOverrides.ts';\n\nexport const DOOM_CORDIS_HOST_ABI_VERSION = 1 as const;\nexport const DOOM_CORDIS_HOST_QUERY_CHANNEL = 'doom:cordis:host:v1:query';\nexport const DOOM_CORDIS_HOST_REQUIRED_ENV = 'DOOMPI_CORDIS_HOST_REQUIRED';\nexport const DOOM_CORDIS_RUNTIME_SERVICE = 'doom/runtime';\nexport const DOOM_CORDIS_SESSION_SERVICE = 'doom/session';\n\nconst ENABLED_FLAG = '1';\nconst HOST_PROTOCOL = 'doom.cordis.host';\nconst DEFAULT_HOST_SOURCE = '@agimon-ai/doompi/cordis-host';\n\nexport type DoomCordisHostMode = 'composed' | 'standalone';\n\nexport interface DoomCordisRuntimeService {\n  readonly abiVersion: typeof DOOM_CORDIS_HOST_ABI_VERSION;\n  readonly hostId: string;\n  readonly generation: string;\n  readonly mode: DoomCordisHostMode;\n}\n\nexport interface DoomCordisSessionService {\n  readonly sessionId: string;\n  readonly generation: string;\n  readonly reason: SessionStartEvent['reason'];\n  readonly context: ExtensionContext;\n}\n\nexport interface DoomCordisHostConnection {\n  readonly root: Context;\n  readonly runtime: DoomCordisRuntimeService;\n  /** Releases this consumer's lease. It never disposes a composed host. */\n  dispose(): Promise<void>;\n}\n\nexport interface DoomCordisHostController {\n  readonly root: Context;\n  readonly runtime: DoomCordisRuntimeService;\n  /** Recursively disposes the session and application plugin tree. */\n  shutdown(): Promise<void>;\n}\n\nexport interface ConnectDoomCordisHostOptions {\n  readonly environment?: Readonly<Record<string, string | undefined>>;\n  /** Host/finalizer entries disable fallback so a malformed composition fails closed. */\n  readonly allowStandalone?: boolean;\n}\n\nexport interface InstallDoomCordisHostOptions {\n  readonly mode: DoomCordisHostMode;\n  readonly source?: string;\n}\n\ndeclare module '@deepseek-ai/cordis' {\n  interface Context {\n    'doom/runtime': DoomCordisRuntimeService;\n    'doom/session': DoomCordisSessionService;\n  }\n}\n\ninterface HostQuery {\n  readonly protocol: typeof HOST_PROTOCOL;\n  readonly abiVersion: typeof DOOM_CORDIS_HOST_ABI_VERSION;\n  readonly kind: 'query';\n  readonly requestId: string;\n  readonly source: string;\n  readonly accept: (response: unknown) => void;\n}\n\ninterface HostResponder {\n  readonly protocol: typeof HOST_PROTOCOL;\n  readonly abiVersion: typeof DOOM_CORDIS_HOST_ABI_VERSION;\n  readonly hostId: string;\n  readonly root: Context;\n  readonly runtime: DoomCordisRuntimeService;\n  readonly ready: Promise<void>;\n  acquire(): DoomCordisHostConnection;\n  shutdown(): Promise<void>;\n}\n\nfunction isRecord(value: unknown): value is Record<PropertyKey, unknown> {\n  return typeof value === 'object' && value !== null;\n}\n\nfunction asHostQuery(value: unknown): HostQuery | undefined {\n  if (!isRecord(value)) return undefined;\n  if (\n    value.protocol !== HOST_PROTOCOL ||\n    value.abiVersion !== DOOM_CORDIS_HOST_ABI_VERSION ||\n    value.kind !== 'query' ||\n    typeof value.requestId !== 'string' ||\n    value.requestId.length === 0 ||\n    typeof value.source !== 'string' ||\n    value.source.length === 0 ||\n    typeof value.accept !== 'function'\n  ) {\n    return undefined;\n  }\n  return value as unknown as HostQuery;\n}\n\nfunction asHostResponder(value: unknown): HostResponder {\n  if (!isRecord(value)) throw new Error('The Doom Cordis host returned an invalid discovery response.');\n  if (value.protocol !== HOST_PROTOCOL) {\n    throw new Error('The Doom Cordis host returned an invalid protocol identifier.');\n  }\n  if (value.abiVersion !== DOOM_CORDIS_HOST_ABI_VERSION) {\n    throw new Error(\n      `The Doom Cordis host ABI is incompatible: expected ${String(DOOM_CORDIS_HOST_ABI_VERSION)}, received ${String(value.abiVersion)}.`,\n    );\n  }\n  if (typeof value.hostId !== 'string' || value.hostId.length === 0) {\n    throw new Error('The Doom Cordis host returned an invalid host identifier.');\n  }\n  if (!Context.is(value.root)) {\n    throw new Error('The Doom Cordis host returned a value that is not a Cordis Context.');\n  }\n  if (\n    !isRecord(value.runtime) ||\n    value.runtime.abiVersion !== DOOM_CORDIS_HOST_ABI_VERSION ||\n    value.runtime.hostId !== value.hostId ||\n    typeof value.acquire !== 'function' ||\n    typeof value.shutdown !== 'function' ||\n    !isRecord(value.ready) ||\n    typeof value.ready.then !== 'function'\n  ) {\n    throw new Error('The Doom Cordis host returned incomplete runtime metadata.');\n  }\n  return value as unknown as HostResponder;\n}\n\nfunction discoverHosts(pi: Pick<ExtensionAPI, 'events'>, source: string): HostResponder[] {\n  const responses: HostResponder[] = [];\n  const request: HostQuery = {\n    protocol: HOST_PROTOCOL,\n    abiVersion: DOOM_CORDIS_HOST_ABI_VERSION,\n    kind: 'query',\n    requestId: `${source}:${crypto.randomUUID()}`,\n    source,\n    accept(response) {\n      responses.push(asHostResponder(response));\n    },\n  };\n  pi.events.emit(DOOM_CORDIS_HOST_QUERY_CHANNEL, request);\n  return responses;\n}\n\nfunction exactlyOneHost(responses: readonly HostResponder[], source: string): HostResponder | undefined {\n  if (responses.length <= 1) return responses[0];\n  const hostIds = responses.map(({ hostId }) => hostId).join(', ');\n  throw new Error(`Multiple Doom Cordis hosts answered ${source}: ${hostIds}.`);\n}\n\nfunction runtimeProvider(ctx: Context, service: DoomCordisRuntimeService): void {\n  ctx.provide(DOOM_CORDIS_RUNTIME_SERVICE, service);\n  ctx.provide(DOOM_TOOL_OVERRIDES_SERVICE, createDoomToolOverridesService(service.generation));\n}\n\nfunction sessionProvider(ctx: Context, service: DoomCordisSessionService): void {\n  ctx.provide(DOOM_CORDIS_SESSION_SERVICE, service);\n  ctx.provide(DOOM_CONTEXT_CONTRIBUTIONS_SERVICE, createDoomContextContributionsService(service.generation));\n}\n\n/**\n * Installs the sole Cordis application root for one Pi extension runner.\n *\n * The versioned EventBus query is intentional: Pi may hand independently\n * loaded factories different ExtensionAPI wrapper objects, while their event\n * buses still address the same runner.\n */\nexport async function installDoomCordisHost(\n  pi: ExtensionAPI,\n  options: InstallDoomCordisHostOptions,\n): Promise<DoomCordisHostController> {\n  const source = options.source ?? DEFAULT_HOST_SOURCE;\n  const existing = exactlyOneHost(discoverHosts(pi, source), source);\n  if (existing) throw new Error(`A Doom Cordis host is already installed: ${existing.hostId}.`);\n\n  // This is the one intentional Context constructor for DoomPi extension code.\n  const root = new Context();\n  const runtime: DoomCordisRuntimeService = Object.freeze({\n    abiVersion: DOOM_CORDIS_HOST_ABI_VERSION,\n    hostId: `doom-cordis:${crypto.randomUUID()}`,\n    generation: `doom-runtime:${crypto.randomUUID()}`,\n    mode: options.mode,\n  });\n  const runtimeFiber = root.plugin(runtimeProvider, runtime);\n  const ready = runtimeFiber.await().then(() => undefined);\n  let sessionFiber: Fiber | undefined;\n  let sessionGeneration = 0;\n  let sessionQueue = Promise.resolve();\n  let leaseCount = 0;\n  let shutdownPromise: Promise<void> | undefined;\n  let responder: HostResponder;\n  let disposeQuerySubscription = (): void => undefined;\n\n  const controller: DoomCordisHostController = {\n    root,\n    runtime,\n    shutdown() {\n      shutdownPromise ??= (async () => {\n        disposeQuerySubscription();\n        await ready.catch(() => undefined);\n        await sessionQueue.catch(() => undefined);\n        await sessionFiber?.dispose();\n        sessionFiber = undefined;\n        await root.fiber.dispose();\n      })();\n      return shutdownPromise;\n    },\n  };\n\n  responder = {\n    protocol: HOST_PROTOCOL,\n    abiVersion: DOOM_CORDIS_HOST_ABI_VERSION,\n    hostId: runtime.hostId,\n    root,\n    runtime,\n    ready,\n    acquire() {\n      if (shutdownPromise) throw new Error(`The Doom Cordis host is shutting down: ${runtime.hostId}.`);\n      leaseCount += 1;\n      let released = false;\n      return {\n        root,\n        runtime,\n        async dispose() {\n          if (released) return;\n          released = true;\n          leaseCount -= 1;\n          if (options.mode === 'standalone' && leaseCount === 0) await controller.shutdown();\n        },\n      };\n    },\n    shutdown: () => controller.shutdown(),\n  };\n  try {\n    disposeQuerySubscription = pi.events.on(DOOM_CORDIS_HOST_QUERY_CHANNEL, (value) => {\n      const query = asHostQuery(value);\n      if (query) query.accept(responder);\n    });\n    pi.on('session_start', (event, context) => {\n      sessionQueue = sessionQueue\n        .catch(() => undefined)\n        .then(async () => {\n          // Pi owns its listener table and can retain this callback across a\n          // replacement. A start delivered after finalization belongs to the\n          // stale graph and must not revive or fail the next one.\n          if (shutdownPromise) return;\n          await sessionFiber?.dispose();\n          sessionGeneration += 1;\n          const service: DoomCordisSessionService = Object.freeze({\n            sessionId: context.sessionManager.getSessionId(),\n            generation: `${runtime.generation}:${String(sessionGeneration)}`,\n            reason: event.reason,\n            context,\n          });\n          sessionFiber = root.plugin(sessionProvider, service);\n          await sessionFiber.await();\n        });\n      return sessionQueue;\n    });\n    if (options.mode === 'standalone') {\n      pi.on('session_shutdown', () => {\n        disposeQuerySubscription();\n      });\n    }\n    await ready;\n  } catch (error) {\n    await controller.shutdown();\n    throw error;\n  }\n  return controller;\n}\n\n/** Resolves the composed host, or installs one shared standalone fallback. */\nexport async function connectDoomCordisHost(\n  pi: ExtensionAPI,\n  source: string,\n  options: ConnectDoomCordisHostOptions = {},\n): Promise<DoomCordisHostConnection> {\n  let responder = exactlyOneHost(discoverHosts(pi, source), source);\n  if (!responder) {\n    const environment = options.environment ?? process.env;\n    const fallbackAllowed = options.allowStandalone ?? environment[DOOM_CORDIS_HOST_REQUIRED_ENV] !== ENABLED_FLAG;\n    if (!fallbackAllowed) {\n      throw new Error('The composed Doom Cordis host is unavailable. Ensure cordisHost is the first extension.');\n    }\n    await installDoomCordisHost(pi, { mode: 'standalone', source: `${source}:standalone-host` });\n    responder = exactlyOneHost(discoverHosts(pi, source), source);\n  }\n  if (!responder) throw new Error('The Doom Cordis standalone host could not be installed.');\n  await responder.ready;\n  return responder.acquire();\n}\n\n/** Ends the host discovered on this runner. Repeated finalization is a no-op. */\nexport async function finalizeDoomCordisHost(pi: Pick<ExtensionAPI, 'events'>, source: string): Promise<void> {\n  const responder = exactlyOneHost(discoverHosts(pi, source), source);\n  await responder?.shutdown();\n}\n"],"mappings":"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"}