{"version":3,"file":"index.mjs","names":[],"sources":["../src/engine.ts","../src/index.ts"],"sourcesContent":["// The pi2dsh engine: ONE installed copy of the bridge that mounts every Pi\n// package the user has added to their DSH profile.\n//\n//   dsh plugin --profile p add pi2dsh              ← the engine (this plugin)\n//   dsh plugin --profile p add @kassing/pi-vision  ← plain npm dependency\n//   dsh plugin --profile p add pi-vision-tool      ← plain npm dependency\n//\n// DSH's plugin manager records only packages that declare `dsh.bundle` as\n// profile layers; everything else stays an ordinary dependency (\"a plain\n// library is fine\"). The engine reads the profile manifest's DIRECT\n// dependencies — every entry there was an explicit `dsh plugin add` — and\n// mounts each package that identifies as a Pi package, all through one\n// bridge instance: one model ledger, one command space, one upgrade unit.\n//\n// Discovery is manifest-driven, never a node_modules scan (the lesson from\n// Prettier 3 dropping directory-based plugin discovery): the dependency list\n// is the user's explicit intent, and package identification uses the same\n// Pi markers `resolvePiPackage` has always used (the `pi` manifest field,\n// with Pi's directory conventions as fallback).\n\nimport { readFile } from 'node:fs/promises'\nimport { existsSync, realpathSync } from 'node:fs'\nimport { dirname, join, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport type { Context } from '@deepseek-ai/cordis'\nimport { applyPreparedPiHost, preparePiHost, type PreparedPiHostPackage } from './host.js'\nimport { getSharedChildExtensionCatalog, registerChildExtensionCatalog, registerVisionCompanions, runtimeInternals } from './runtime.js'\nimport { getAgentDir, providePiExtensionDiscovery } from './compat/pi-coding-agent.js'\nimport { resolvePiPackage } from './source.js'\n\nexport interface EngineConfig {\n  /** Mount exactly these packages (skips discovery). */\n  packages?: string[]\n  /** Never mount these packages even when discovered. */\n  exclude?: string[]\n  /**\n   * Image-admission companion routes. Default: AUTOMATIC — every text-only\n   * llm route gets a \\`<route>-vision\\` companion that admits pasted images\n   * (a mounted vision extension analyzes them; without one the image is\n   * materialized to a file any image-capable tool can read). \\`false\\`\n   * turns companions off; an explicit \\`{ <route>: [modelIds] }\\` narrows.\n   */\n  visionCompanions?: false | Record<string, readonly string[]>\n  /**\n   * Serve the browser presentation surfaces (the `/pi2dsh` state route the\n   * web renderer polls, and with it the `mode: 'tui'` claim on the web\n   * composition). Default: OFF — the engine alone projects nothing into the\n   * browser, so a web session behaves exactly like the headless CLI (Pi's\n   * own rpc mode; packages degrade gracefully on their own). The dsh-work-x\n   * suite turns this on and carries the actual renderer in its client\n   * bundle; any other product layer that ships a renderer can do the same.\n   */\n  browserPresentation?: boolean\n  /**\n   * Serve DSH-native subagents with the profile's Pi packages (default: OFF —\n   * such children run as plain DSH agents, today's behavior). Children the Pi\n   * subagent bridge mints are NEVER covered: they already receive the creator's\n   * own per-spawn loader mount and are recognized by the `pi2dsh-sub-` session-id\n   * prefix.\n   */\n  serveNativeSubagents?: boolean\n}\n\n/** Locate the DSH profile root: the nearest ancestor holding cordis.yml. */\nexport function findProfileRoot(start: string): string | undefined {\n  let dir = start\n  for (;;) {\n    if (existsSync(join(dir, 'cordis.yml')) && existsSync(join(dir, 'package.json'))) return dir\n    const parent = dirname(dir)\n    if (parent === dir) return undefined\n    dir = parent\n  }\n}\n\ninterface DiscoveredPackage {\n  name: string\n  dir: string\n  /** Resolution anchor when the package is carried by a suite (its members\n   * are the suite's dependencies, unreachable from the profile root under\n   * pnpm's isolated layout). */\n  anchor?: string\n}\n\ninterface ProfileManifest {\n  dependencies?: Record<string, string>\n  dsh?: { profile?: { bundles?: string[] } }\n}\n\nasync function readProfileManifest(profileRoot: string): Promise<ProfileManifest> {\n  return JSON.parse(await readFile(join(profileRoot, 'package.json'), 'utf8')) as ProfileManifest\n}\n\nfunction resolveDependencyDir(profileRoot: string, name: string): string | undefined {\n  // Direct dependencies of the profile live under its node_modules by name\n  // (pnpm links them there); resolving by path needs no exports gymnastics.\n  const dir = join(profileRoot, 'node_modules', name)\n  return existsSync(join(dir, 'package.json')) ? dir : undefined\n}\n\n/**\n * The profile's direct dependencies that identify as Pi packages: not the\n * engine itself, not a `dsh.bundle` (those are DSH plugins, not Pi\n * packages), carrying either Pi's `pi` manifest field or extension sources\n * under Pi's directory conventions.\n */\nexport async function discoverProfilePiPackages(\n  profileRoot: string,\n  options: { exclude?: string[], warn?: (message: string) => void } = {},\n): Promise<DiscoveredPackage[]> {\n  const warn = options.warn ?? (() => {})\n  const excluded = new Set(options.exclude ?? [])\n  const manifest = await readProfileManifest(profileRoot)\n  const discovered: DiscoveredPackage[] = []\n  for (const name of Object.keys(manifest.dependencies ?? {})) {\n    if (name === 'pi2dsh' || excluded.has(name)) continue\n    const dir = resolveDependencyDir(profileRoot, name)\n    if (dir === undefined) {\n      warn(`[pi2dsh engine] dependency ${JSON.stringify(name)} is not installed under the profile; skipping`)\n      continue\n    }\n    let packageJson: Record<string, unknown>\n    try {\n      packageJson = JSON.parse(await readFile(join(dir, 'package.json'), 'utf8')) as Record<string, unknown>\n    } catch (error) {\n      warn(`[pi2dsh engine] cannot read ${name}/package.json (${error instanceof Error ? error.message : String(error)}); skipping`)\n      continue\n    }\n    // A suite: `pi2dsh: { suite: [names...] }` mounts the listed Pi packages\n    // as if the user had added each one. The list is an explicit manifest —\n    // the same discovery covenant as the profile dependency list, one hop\n    // deeper — and the members are the suite's own dependencies, so each\n    // resolves anchored at the suite package (pnpm keeps transitive\n    // dependencies out of the profile root). One level only, no recursion.\n    const suite = (packageJson.pi2dsh as { suite?: unknown } | undefined)?.suite\n    if (Array.isArray(suite)) {\n      // The anchor must be the suite's REAL directory: the profile's\n      // node_modules entry is a pnpm symlink into .pnpm, and Node resolution\n      // walks up from the anchor's literal path — only the realpath has the\n      // suite's own dependencies as .pnpm neighbours.\n      let anchorDir = dir\n      try {\n        anchorDir = realpathSync(dir)\n      } catch { /* an unreadable dir keeps the literal path and fails loud in prepare */ }\n      for (const member of suite) {\n        if (typeof member !== 'string' || member.length === 0) continue\n        if (member === 'pi2dsh' || excluded.has(member)) continue\n        discovered.push({ name: member, dir, anchor: join(anchorDir, 'package.json') })\n      }\n      continue\n    }\n    // A dsh.bundle-declaring package is a DSH plugin layer, never a Pi package.\n    if ((packageJson.dsh as { bundle?: unknown } | undefined)?.bundle !== undefined) continue\n    if (packageJson.pi !== undefined && typeof packageJson.pi === 'object') {\n      discovered.push({ name, dir })\n      continue\n    }\n    // No `pi` field: fall back to Pi's directory conventions via the same\n    // resolver every other mount path uses (a package with zero extension\n    // sources is a plain library and stays unmounted).\n    try {\n      const pkg = await resolvePiPackage(dir)\n      try {\n        if (pkg.resources.extensions.length > 0) discovered.push({ name, dir })\n      } finally {\n        await pkg.dispose()\n      }\n    } catch {\n      // Not resolvable as a Pi package — a plain library.\n    }\n  }\n  // One mount per name. A package that is both a direct dependency and a\n  // suite member mounts as the DIRECT dependency (the user's own explicit\n  // add, resolved from the profile root) — the suite copy yields.\n  const byName = new Map<string, DiscoveredPackage>()\n  for (const pkg of discovered) {\n    const existing = byName.get(pkg.name)\n    if (existing === undefined || (existing.anchor !== undefined && pkg.anchor === undefined)) {\n      byName.set(pkg.name, pkg)\n    }\n  }\n  return [...byName.values()]\n}\n\ninterface AgentScopedMount {\n  ready: Promise<void>\n  /** Set when the mount failed; the agent then runs as a plain DSH agent. */\n  failure?: string\n}\n\n/** The loose shapes of the official DSH seams this file consumes. */\ninterface AgentLike {\n  ctx: Context\n}\ninterface EngineHostContext {\n  /** Un-injected reflection access (the ctx.get('loader') idiom): the engine\n   * must not hard-depend on the agent registry — compositions without one\n   * (bare test hosts) simply have no agents to mount for. */\n  get?(name: string): unknown\n  tools?: { schemas?(scope: unknown): Array<{ name: string }> }\n  on(name: string, listener: (...args: never[]) => unknown): () => void\n}\ninterface AgentRegistryLike {\n  roots?(): AgentLike[]\n}\n\n/**\n * The single mount path on every DSH surface: one Pi runtime per root Agent.\n *\n * Pi instantiates its extensions once per session; DSH's twin of that scope is\n * the Agent with its public `agent.ctx` (\"contributions are agent-local,\n * unwind on disposal\"). Mounting is driven purely by official core seams, so\n * the same semantics hold on the TUI, web, headless, ACP and config-declared\n * agents alike:\n *\n *   - `agent/created` fires on every publication path before the loop can run\n *     a first turn; it eagerly starts that agent's mount.\n *   - `system-prompt/assemble` (awaited waterfall, runs in every step's\n *     preStep) gates the assembly on the mount and patches the pre-waterfall\n *     tools snapshot from the official scoped projection `tools.schemas()` —\n *     stock DSH collects `assembly.tools` before dispatching the waterfall,\n *     so a mount that lands during the wait would otherwise miss turn one.\n *   - `tools/pre-execute` (awaited waterfall) closes the same window for\n *     direct executions that bypass assembly.\n *\n * `agent/session-start` deliberately is NOT the trigger: DSH documents it as a\n * veto-less notification that cannot gate startup. The waterfalls above are\n * the officially awaited seams, and the exact pattern of registering onto a\n * foreign agent's ctx from a lifecycle event is what DSH's own schedule\n * plugin ships.\n *\n * A mount failure never takes the Agent down: the agent keeps running as a\n * plain DSH agent, the failure is reported loudly once, and only the Pi\n * packages are missing — the capability-gap discipline, not a rollback.\n */\nexport function installAgentScopedMounts(\n  ctx: Context,\n  preparedPackages: Promise<readonly PreparedPiHostPackage[]>,\n  report: { warn(message: string): void },\n): void {\n  const host = ctx as unknown as EngineHostContext\n  const mounts = new WeakMap<object, AgentScopedMount>()\n\n  const registry = (): AgentRegistryLike | undefined => {\n    try {\n      return host.get?.('agents') as AgentRegistryLike | undefined\n    } catch {\n      return undefined\n    }\n  }\n\n  const isRootAgent = (agent: object): boolean => {\n    // Subagents keep Pi's own sub-session semantics through the session\n    // bridge; only runtime roots receive a Pi runtime (the same distinction\n    // DSH's schedule plugin draws via agents.roots()).\n    const agents = registry()\n    const roots = agents?.roots\n    if (typeof roots !== 'function') return true\n    try {\n      return (roots.call(agents) as unknown[]).includes(agent)\n    } catch {\n      return true\n    }\n  }\n\n  const ensureMount = (agent: AgentLike): AgentScopedMount => {\n    let mount = mounts.get(agent)\n    if (mount === undefined) {\n      const started: AgentScopedMount = { ready: Promise.resolve() }\n      started.ready = preparedPackages.then(async prepared => {\n        if (prepared.length === 0) return\n        try {\n          await applyPreparedPiHost(agent.ctx, prepared, agent as unknown as Record<string, unknown>)\n        } catch (error) {\n          // Loud, once per agent; the gates resolve so the plain DSH agent\n          // keeps working. Faking success is forbidden — the message names\n          // what is missing.\n          started.failure = error instanceof Error ? error.message : String(error)\n          report.warn(`[pi2dsh engine] Pi packages failed to mount for this agent; it continues without them: ${started.failure}`)\n        }\n      })\n      mounts.set(agent, started)\n      mount = started\n    }\n    return mount\n  }\n\n  // Eager start on publication. `agent/created` reaches host-level listeners\n  // on every create/resume/config path, before the loop can open a turn.\n  host.on('agent/created', ((payload: { agent: AgentLike }) => {\n    if (isRootAgent(payload.agent)) ensureMount(payload.agent)\n  }) as never)\n\n  // Correctness boundary #1: no prompt assembly for a root agent proceeds\n  // before its Pi runtime is mounted, and the tools snapshot taken before\n  // this waterfall is reconciled against the official scoped projection.\n  host.on('system-prompt/assemble', (async (\n    assembly: { tools: Array<{ name: string }> },\n    context: { agent?: AgentLike },\n    next: () => Promise<unknown>,\n  ) => {\n    const agent = context.agent\n    if (agent !== undefined && isRootAgent(agent)) {\n      await ensureMount(agent).ready\n      const schemas = host.tools?.schemas\n      if (typeof schemas === 'function') {\n        const present = new Set(assembly.tools.map(tool => tool.name))\n        for (const schema of schemas.call(host.tools, agent)) {\n          if (!present.has(schema.name)) assembly.tools.push(schema)\n        }\n      }\n    }\n    return next()\n  }) as never)\n\n  // Correctness boundary #2: tool execution for a root agent waits for the\n  // same mount (serially awaited by the tool runtime before dispatch).\n  host.on('tools/pre-execute', (async (\n    exec: { agent?: AgentLike },\n    next: () => Promise<unknown>,\n  ) => {\n    if (exec.agent !== undefined && isRootAgent(exec.agent)) await ensureMount(exec.agent).ready\n    return next()\n  }) as never)\n\n  // Backfill: agents published before this plugin finished loading (a surface\n  // that skips the official await-the-loader pattern DSH's headless runner\n  // uses). Their next assembly still passes the gates above.\n  void preparedPackages.then(() => {\n    const agents = registry()\n    const roots = agents?.roots\n    if (typeof roots !== 'function') return\n    for (const agent of roots.call(agents)) ensureMount(agent)\n  })\n}\n\n/** Session-id prefix minted by the Pi subagent bridge (src/subagent-bridge.ts). */\nexport const BRIDGE_CHILD_SESSION_ID_PREFIX = 'pi2dsh-sub-'\n\n/**\n * Optional coverage for DSH-native subagents, enabled by the\n * `serveNativeSubagents` engine config.\n *\n * The Pi subagent bridge already serves the children IT mints: the creator's\n * per-spawn loader mount (a8b7a0a) runs at creation and again on a persisted\n * resume. This covers the OTHER lineage — children a DSH surface mints\n * directly (DSH-native delegation, headless subagents, a second agent in the\n * web UI) — which carry no Pi lineage and would otherwise run as plain DSH\n * agents with none of the profile's Pi extensions.\n *\n * One lineage check per created Agent (maintainer point 2): subagent origin\n * AND a session id that does not carry the bridge prefix. The bridge mints\n * `pi2dsh-sub-` ids at creation and a persisted resume keeps the original id,\n * so the skip holds on both bridge paths and a bridge child is never mounted\n * twice.\n *\n * The mount reuses the engine's own child-extension catalog (maintainer\n * point 3) — the same object the bridge's hook consumes — so a native child's\n * tool set is exactly Pi's default-discovered set (the catalog's no-loader\n * path), the mount lands on the child's OWN ctx and unwinds with the child,\n * and there is no second registration path to drift out of sync.\n *\n * Partition with the root-mount path: agents the registry reports as runtime\n * roots are installAgentScopedMounts' territory (it gates them the same way\n * this path does). Live delegation shows a DSH-native child of a top-level\n * session CAN be a runtime root — serving it here as well raced two mount\n * passes onto the same scope (each package survived via the prompt-section\n * guard, but the double attempt is exactly what this flag must not do). This\n * listener therefore serves exactly the remainder: subagent-origin,\n * non-bridge, non-root agents. The predicate is the root path's own, applied\n * at the same event; its fail-open (roots() unavailable ⇒ every agent is a\n * root) mirrors here as fail-closed (this listener serves nothing).\n *\n * The readiness promise is memoized on the child's SCOPE (agent.ctx), not the\n * agent object: a re-announced agent for the same session reuses the scope,\n * and the dedupe must key on what the mount actually lands in.\n *\n * First-turn gates mirror the root path's exactly: the child's first prompt\n * assembly and its direct tool executions await the mount, and the\n * pre-waterfall tools snapshot is reconciled against the official scoped\n * projection. Without the gate a mount that lands after the first turn's\n * snapshot leaves the child's first (and sometimes only) turn tool-less —\n * proven live at depth 2, where the child's grandchild saw zero extension\n * tools because its mount raced its first model call.\n *\n * A mount failure never takes the child down: the same capability-gap\n * discipline as root mounting — the child keeps running as a plain DSH agent\n * and the failure is reported loudly once.\n */\nexport function installNativeSubagentMounts(\n  ctx: Context,\n  preparedPackages: Promise<readonly PreparedPiHostPackage[]>,\n  enabled: boolean,\n  report: { warn(message: string): void },\n): void {\n  if (!enabled) return\n  const host = ctx as unknown as EngineHostContext\n\n  // The root path's own predicate, applied at the same event it applies its\n  // (see installAgentScopedMounts): a runtime root is served — and gated —\n  // there. Failing closed keeps the partition exact when roots() is absent.\n  const isRootAgent = (agent: object): boolean => {\n    let agents: AgentRegistryLike | undefined\n    try {\n      agents = host.get?.('agents') as AgentRegistryLike | undefined\n    } catch {\n      return true\n    }\n    const roots = agents?.roots\n    if (typeof roots !== 'function') return true\n    try {\n      return (roots.call(agents) as unknown[]).includes(agent)\n    } catch {\n      return true\n    }\n  }\n\n  // child scope (agent.ctx) -> its mount's readiness. Reading the catalog\n  // after preparation (never before) covers children published while the\n  // engine was still preparing; no catalog = plain child, exactly the bridge\n  // hook's no-catalog behavior.\n  const mounts = new WeakMap<object, Promise<void>>()\n\n  const ensureMount = (agent: AgentLike): Promise<void> => {\n    const scope = agent.ctx\n    let ready = mounts.get(scope)\n    if (ready === undefined) {\n      ready = preparedPackages.then(async () => {\n        const catalog = getSharedChildExtensionCatalog(ctx)\n        if (catalog === undefined) return\n        // Native children have no creator loader: the full discovered set is\n        // Pi's default discovery (the no-loader path of\n        // resolveChildExtensionPackages).\n        const names = [...new Set(catalog.packageByEntryPath.values())]\n        if (names.length === 0) return\n        try {\n          const failures = await catalog.mount(agent as unknown as Record<string, unknown>, names)\n          for (const failure of failures) {\n            // Same message shape the bridge's own mount path uses.\n            report.warn(`[pi2dsh] child extension ${failure.name} did not mount: ${failure.error}`)\n          }\n        } catch (error) {\n          report.warn(`[pi2dsh] child extension mount failed: ${error instanceof Error ? error.message : String(error)}`)\n        }\n      })\n      mounts.set(scope, ready)\n    }\n    return ready\n  }\n\n  const handle = (agent: AgentLike): void => {\n    // The single lineage check: DSH-native subagents only. Pi-origin\n    // (bridge) children are already served by the creator's per-spawn loader\n    // mount; the `pi2dsh-sub-` prefix identifies them on creation AND on a\n    // persisted resume, where the original session id survives.\n    if (!runtimeInternals.isSubagentOrigin(agent as unknown as Record<string, unknown>)) return\n    const session = (agent as { session?: { id?: unknown } }).session ?? agent\n    if (String((session as { id?: unknown }).id ?? '').startsWith(BRIDGE_CHILD_SESSION_ID_PREFIX)) return\n    // Runtime roots are the root-mount path's territory; serving them here\n    // would race a second mount onto the same scope.\n    if (isRootAgent(agent)) return\n    void ensureMount(agent)\n  }\n\n  host.on('agent/created', ((payload: { agent: AgentLike }) => {\n    if (payload.agent !== undefined) handle(payload.agent)\n  }) as never)\n\n  // Correctness boundary #1 (mirror of the root path's): no prompt assembly\n  // for a served child proceeds before its mount lands, and the pre-waterfall\n  // tools snapshot is reconciled against the official scoped projection.\n  host.on('system-prompt/assemble', (async (\n    assembly: { tools: Array<{ name: string }> },\n    context: { agent?: AgentLike },\n    next: () => Promise<unknown>,\n  ) => {\n    const agent = context.agent\n    const ready = agent === undefined ? undefined : mounts.get(agent.ctx)\n    if (ready !== undefined) {\n      await ready\n      const schemas = host.tools?.schemas\n      if (typeof schemas === 'function') {\n        const present = new Set(assembly.tools.map(tool => tool.name))\n        for (const schema of schemas.call(host.tools, agent)) {\n          if (!present.has(schema.name)) assembly.tools.push(schema)\n        }\n      }\n    }\n    return next()\n  }) as never)\n\n  // Correctness boundary #2 (mirror of the root path's): direct tool\n  // executions for a served child wait for the same mount.\n  host.on('tools/pre-execute', (async (\n    exec: { agent?: AgentLike },\n    next: () => Promise<unknown>,\n  ) => {\n    const agent = exec.agent\n    const ready = agent === undefined ? undefined : mounts.get(agent.ctx)\n    if (ready !== undefined) await ready\n    return next()\n  }) as never)\n}\n\n/** Cordis plugin surface: `dsh plugin add pi2dsh` mounts this. */\nexport const name = 'pi2dsh'\nexport const inject = ['tools', 'systemPrompt', 'commands', 'skills']\n\nexport async function apply(ctx: Context, config: EngineConfig = {}): Promise<void> {\n  // The redirected Pi agent directory must be visible to PLUGIN code, not\n  // just to the bridge's own vendored components. Packages that import\n  // `getAgentDir` from the aliased pi-coding-agent are redirected already,\n  // but real packages also read `process.env.PI_CODING_AGENT_DIR` (or fall\n  // back to ~/.pi/agent) at module load — pi-hermes-memory's paths.ts does\n  // exactly that, and without this line its store landed in the REAL\n  // ~/.pi/agent, colliding with any actual Pi installation. Publishing the\n  // same path the shim computes keeps both classes on one directory; a\n  // user-set value is honored (the shim reads the env first, so the two can\n  // never disagree). Set before any package module is imported.\n  if (process.env.PI_CODING_AGENT_DIR === undefined || process.env.PI_CODING_AGENT_DIR === '') {\n    process.env.PI_CODING_AGENT_DIR = getAgentDir()\n  }\n\n  // Same emission as the runtime's logger helper: the cordis logger AND the\n  // console — profile logger levels must never hide what the engine mounted.\n  const log = (ctx as unknown as { logger?: { info?(m: string): void, warn?(m: string): void } }).logger\n  const warn = (message: string): void => { log?.warn?.(message); console.warn(message) }\n  const info = (message: string): void => { log?.info?.(message); console.log(message) }\n\n  // The loader resolves plugins against the profile's baseUrl; that IS the\n  // profile root. The ancestor walk from the installed engine is the\n  // fallback for compositions without a loader (tests, hand-built hosts).\n  const baseUrl = (ctx as unknown as { baseUrl?: string }).baseUrl\n  const profileRoot = (baseUrl !== undefined ? findProfileRoot(fileURLToPath(new URL('.', baseUrl))) : undefined)\n    ?? findProfileRoot(dirname(fileURLToPath(import.meta.url)))\n  if (profileRoot === undefined) {\n    throw new Error('pi2dsh engine: no DSH profile root (cordis.yml + package.json) above the installed engine — is pi2dsh installed via `dsh plugin add pi2dsh`?')\n  }\n\n  // The single mount path, before any await: gates and lifecycle listeners\n  // must exist the moment a surface can publish its first Agent. Package\n  // preparation resolves behind this promise; the gates hold each agent's\n  // first assembly until its own mount lands.\n  let resolvePrepared!: (prepared: readonly PreparedPiHostPackage[]) => void\n  const preparedPackages = new Promise<readonly PreparedPiHostPackage[]>(resolve => {\n    resolvePrepared = resolve\n  })\n  installAgentScopedMounts(ctx, preparedPackages, { warn })\n  installNativeSubagentMounts(ctx, preparedPackages, config.serveNativeSubagents === true, { warn })\n\n  registerVisionCompanions(ctx, config.visionCompanions)\n\n  try {\n    // The host half, mounted exactly once regardless of packages or agents:\n    // Pi's built-in provider directory, `/login`, and credential recovery.\n    // Without it a fresh engine cannot run `/login openai-codex`: DSH treats\n    // the unknown slash line as a model prompt and fails on the unrelated\n    // default provider credential. Community packages join the same\n    // SharedHostState (keyed on ctx.root), so host-level resources stay\n    // single-instance no matter which agent scope mounts them.\n    await applyPreparedPiHost(ctx, [{\n      name: 'pi2dsh-builtins',\n      rootUrl: new URL('.', import.meta.url),\n      manifest: {\n        schemaVersion: 1,\n        package: { name: 'pi2dsh-builtins', version: '0.0.0', source: 'engine' },\n        extensions: [],\n        skillDirs: [],\n        prompts: [],\n      },\n    }])\n\n    const packages: Array<{ name: string, anchor?: string }> = Array.isArray(config.packages) && config.packages.length > 0\n      ? config.packages.map(name => ({ name }))\n      : await discoverProfilePiPackages(profileRoot, {\n          ...(Array.isArray(config.exclude) ? { exclude: config.exclude } : {}),\n          warn,\n        })\n    if (packages.length === 0) {\n      info('[pi2dsh engine] no Pi packages installed in this profile yet — add one with: dsh plugin --profile <p> add <pi-package>')\n      resolvePrepared([])\n      return\n    }\n    info(`[pi2dsh engine] preparing ${packages.length} Pi package(s): ${packages.map(pkg => pkg.name).join(', ')}`)\n    const prepared = await preparePiHost(\n      {\n        packages: packages.map(pkg =>\n          pkg.anchor === undefined ? { name: pkg.name } : { name: pkg.name, anchor: pkg.anchor }),\n      },\n      join(profileRoot, 'package.json'),\n    )\n    // Child-extension catalog: what real Pi's createAgentSession \"default-\n    // discovered extensions\" means on this host. Entries are each package's\n    // DECLARED pi extension files (absolute, inside the installed dir), so a\n    // creator's own filter code (pi-subagents' extensions/exclude/ext:\n    // machinery) canonicalizes them exactly as it does on Pi. Mounting lands\n    // on the child agent's OWN ctx — contributions unwind with the agent,\n    // which is what makes per-child instances leak-free by construction.\n    const entryCatalog = new Map<string, string>()\n    for (const pkg of prepared) {\n      const rootDir = fileURLToPath(pkg.rootUrl)\n      // Resolved keys, because the consumer looks entries up by resolved path.\n      for (const rel of pkg.manifest.extensions) entryCatalog.set(resolve(rootDir, rel), pkg.name)\n    }\n    providePiExtensionDiscovery([...entryCatalog.keys()].map(path => ({ path })))\n    registerChildExtensionCatalog(ctx, {\n      packageByEntryPath: entryCatalog,\n      mount: async (childAgent, packageNames) => {\n        const wanted = new Set(packageNames)\n        const subset = prepared.filter(pkg => wanted.has(pkg.name))\n        const scope = (childAgent as { ctx?: Context }).ctx\n        if (scope === undefined) {\n          return subset.map(pkg => ({ name: pkg.name, error: 'the child agent exposes no ctx scope to mount into' }))\n        }\n        return applyPreparedPiHost(scope, subset, childAgent)\n      },\n    })\n\n    // Host anchors, before the per-Agent gates open: every package's\n    // HOST-level contributions (provider routes, OAuth accounts, /login,\n    // credential recovery, companions, skills) exist from engine apply — a\n    // surface with zero live Agents (web at boot) still advertises them, the\n    // first Agent's model resolution finds its routes, and routes survive\n    // Agent churn. Anchors serve no Agent; sessions belong to the per-Agent\n    // instances the gates mount.\n    // The engine-level browserPresentation flag rides into every package\n    // mount's config: the /pi2dsh route (and the web `mode: 'tui'` claim)\n    // exists only when a product layer configured a renderer. Stamped here\n    // rather than defaulted in the runtime so per-Agent remounts and the\n    // host anchor agree on one answer.\n    const presented = config.browserPresentation === true\n      ? prepared.map(pkg => ({ ...pkg, config: { ...(pkg.config ?? {}), browserPresentation: true } }))\n      : prepared\n    await applyPreparedPiHost(ctx, presented, undefined, { hostAnchor: true })\n    resolvePrepared(presented)\n  } catch (error) {\n    // The gates must never hang on a failed preparation; agents keep running\n    // as plain DSH agents while the engine failure propagates loudly.\n    resolvePrepared([])\n    throw error\n  }\n}\n","// Engine surface: `dsh plugin add pi2dsh` resolves this entry as a cordis\n// plugin (named exports, no default — a default export would make the\n// loader discard the function-plugin namespace).\nexport { apply, inject, name, discoverProfilePiPackages, findProfileRoot, installAgentScopedMounts, type EngineConfig } from './engine.js'\n\n// The CLI-only analysis surface loads lazily: its static-analysis dependency\n// (the 23 MB typescript compiler, an optional peer) must never be pulled\n// into a profile that installs the ENGINE. The dynamic import below keeps\n// the analyzer in its own chunk, off the engine's load path.\nimport type { CompatibilityReport, ResolvedPiPackage } from './types.js'\n\n/**\n * Static compatibility analysis of one resolved Pi package (CLI `inspect`).\n * @param pkg - the resolved package.\n * @returns the compatibility report.\n */\nexport async function analyzePackage(pkg: ResolvedPiPackage): Promise<CompatibilityReport> {\n  const { analyzePackage: run } = await import('./analyzer.js')\n  return run(pkg)\n}\n\nexport {\n  API_RULES,\n  CONTEXT_RULES,\n  EVENT_RULES,\n  HOST_IMPORT_RULES,\n  PI_AI_PACKAGES,\n  PI_CODING_AGENT_PACKAGES,\n  PI_TUI_PACKAGES,\n  UI_CONTEXT_RULES,\n  ruleForApi,\n  ruleForContextProperty,\n  ruleForEvent,\n  ruleForHostImport,\n  ruleForUiContextProperty,\n} from './compatibility.js'\nexport { applyPiHost, applyPreparedPiHost, manifestForInstalled, preparePiHost } from './host.js'\nexport { collectPiMcpServers, convertPiMcpConfig, renderMcpPatch } from './mcp-config.js'\nexport { resolvePiPackage } from './source.js'\nexport type * from './types.js'\n"],"mappings":"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"}