{"version":3,"file":"transition-coordinator.mjs","names":[],"sources":["../../src/extensions/transitionCoordinator.ts"],"sourcesContent":["import fs from 'node:fs';\n\nimport { loadMajorModesConfig, filterHookDisabledLayers, resolveLayers } from '@agimon-ai/doompi-config/majorModes';\nimport { readDoomConfigContextGeneration, requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport { alreadyComposed } from '@agimon-ai/doompi-core/child-process';\nimport { DOOM_CONFIG_SERVICE } from '@agimon-ai/doompi-core/config';\nimport {\n  connectDoomCordisHost,\n  DOOM_CORDIS_SESSION_SERVICE,\n  type DoomCordisSessionService,\n} from '@agimon-ai/doompi-core/cordis-host';\nimport { type DoomTransitionCoordinator, DOOM_TRANSITION_SERVICE } from '@agimon-ai/doompi-core/transition';\nimport type { TransitionSelectionSnapshot, TransitionSynchronization } from '@agimon-ai/doompi-core/transition';\nimport { createDoomTransitionCoordinator } from '@agimon-ai/doompi-core/transition-coordinator';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\n\nimport { MUTE_ENV } from '../builders/cli/compositionState';\nimport {\n  createLayerResolvers,\n  type ExtensionLayerResolvers,\n  PERSONA_ENTRY,\n  resolveExtensionComposition,\n} from '../builders/cli/extensionAssembler';\nimport { readLauncherComposition } from '../builders/cli/launcherComposition';\nimport { createMapResolvers, readSyncState } from '../composition/syncState';\nimport { extensionLayers } from '../composition/transitionLayers';\n\nconst ENABLED_FLAG = '1';\nconst DEFAULT_PRESET = 'default';\nconst PACKAGE_SOURCE = '@agimon-ai/doompi/transition-coordinator';\n\nexport function currentTransitionSynchronization(\n  repositoryRoot: string,\n  environment: NodeJS.ProcessEnv = process.env,\n  _currentMajorMode?: string,\n): TransitionSynchronization {\n  // A composing launcher session recomposes on reload, so it is neither the\n  // frozen launcher case nor the synced one. Checked before `alreadyComposed`\n  // because such a session never runs the synced composer that sets it.\n  if (readLauncherComposition(environment)) return { kind: 'launcher-composed' };\n  if (!alreadyComposed(environment)) return { kind: 'launcher' };\n  try {\n    const state = readSyncState(repositoryRoot, environment.HOME);\n    if (!state || Object.keys(state.resolved).length === 0) {\n      return {\n        kind: 'synchronized',\n        resolutionAvailable: false,\n        availableCompositionFingerprints: [],\n      };\n    }\n    const resolvedPaths = Object.values(state.resolved).filter((entry) => entry.startsWith('/'));\n    return {\n      kind: 'synchronized',\n      resolutionAvailable: resolvedPaths.every((entry) => fs.existsSync(entry)),\n      availableCompositionFingerprints: Object.entries(state.bundles ?? {})\n        .filter(([, artifact]) => fs.existsSync(artifact))\n        .map(([fingerprint]) => fingerprint),\n    };\n  } catch {\n    return {\n      kind: 'synchronized',\n      resolutionAvailable: false,\n      availableCompositionFingerprints: [],\n    };\n  }\n}\n\n/**\n * The launch values that enter parent activation alongside the selection.\n *\n * They are fixed for the process but not all readable from the environment, so\n * a composing launcher session supplies them from its record. Getting them\n * wrong does not change which disposition is chosen, because both sides of\n * every comparison share the error, but it does publish a fingerprint that no\n * other reader of harness state agrees with.\n */\ninterface CompositionIdentity {\n  agents: boolean;\n  autoStop: boolean;\n  mute: boolean;\n  preset: string;\n}\n\nfunction compositionResolver(\n  repositoryRoot: string,\n  identity: CompositionIdentity,\n  resolvers: ExtensionLayerResolvers,\n): (selection: TransitionSelectionSnapshot) =>\n  | {\n      readonly fingerprint: string;\n      readonly parentActivation: readonly string[];\n      readonly childActivation: readonly string[];\n    }\n  | undefined {\n  const majorModesConfig = loadMajorModesConfig(repositoryRoot);\n  return (selection) => {\n    try {\n      const composition = resolveExtensionComposition({\n        agents: identity.agents,\n        autoStop: identity.autoStop,\n        mute: identity.mute,\n        preset: identity.preset,\n        personaEntry: resolvers.packageEntry(PERSONA_ENTRY),\n        majorMode: selection.majorMode,\n        layers: [...selection.layers],\n        majorModesConfig,\n        resolvers,\n      });\n      return {\n        fingerprint: composition.fingerprint,\n        parentActivation: [...composition.parentActivation],\n        childActivation: [...composition.childActivation],\n      };\n    } catch {\n      return undefined;\n    }\n  };\n}\n\nexport async function transitionCoordinatorExtension(pi: ExtensionAPI): Promise<void> {\n  const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n  const fiber = connection.root.plugin(transitionCoordinatorPlugin);\n  try {\n    await fiber;\n  } catch (error) {\n    await fiber.dispose();\n    await connection.dispose();\n    throw error;\n  }\n  let disposal: Promise<void> | undefined;\n  pi.on(\n    'session_shutdown',\n    () =>\n      (disposal ??= (async () => {\n        try {\n          await fiber.dispose();\n        } finally {\n          await connection.dispose();\n        }\n      })()),\n  );\n}\n\nfunction transitionCoordinatorPlugin(cordis: Context): void {\n  cordis.inject([DOOM_CORDIS_SESSION_SERVICE, DOOM_CONFIG_SERVICE], (sessionContext) => {\n    const session = sessionContext.get(DOOM_CORDIS_SESSION_SERVICE) as DoomCordisSessionService;\n    const context = session.context;\n    const sessionId = session.sessionId;\n    const doomConfig = requireDoomConfigContext(sessionContext);\n    const harness = doomConfig.harness;\n    const repositoryRoot = harness.root ?? context.cwd;\n    const majorModesConfig = loadMajorModesConfig(repositoryRoot);\n    const state = alreadyComposed() ? readSyncState(repositoryRoot, process.env.HOME) : undefined;\n    const resolvers = state ? createMapResolvers(state.resolved, state.compiled) : createLayerResolvers(repositoryRoot);\n    // A composing launcher session records the launch identity it was started\n    // with; every other session recovers what it can from the environment.\n    const launcher = readLauncherComposition();\n    const resolveComposition = compositionResolver(\n      repositoryRoot,\n      launcher\n        ? { agents: launcher.agents, autoStop: launcher.autoStop, mute: launcher.mute, preset: launcher.preset }\n        : {\n            agents: harness.agents,\n            autoStop: false,\n            mute: process.env[MUTE_ENV] === ENABLED_FLAG,\n            preset: state?.selection.preset ?? DEFAULT_PRESET,\n          },\n      resolvers,\n    );\n    const currentSelection: TransitionSelectionSnapshot = {\n      domains: [...harness.domains],\n      majorMode: harness.majorMode,\n      layers: [...harness.layers],\n      profile: harness.profile,\n      childActivation: [...harness.childExtensions],\n    };\n    const currentComposition = resolveComposition(currentSelection);\n    const hostGeneration = `doom-transition:${crypto.randomUUID()}`;\n    const coordinator: DoomTransitionCoordinator = createDoomTransitionCoordinator({\n      sessionId,\n      hostGeneration,\n      generation: () => ({\n        sessionId,\n        hostGeneration,\n        configGeneration: readDoomConfigContextGeneration(sessionContext),\n      }),\n      classifierContext: () => ({\n        current: {\n          ...currentSelection,\n          ...(currentComposition\n            ? {\n                compositionFingerprint: currentComposition.fingerprint,\n                parentActivation: [...currentComposition.parentActivation],\n                childActivation: [...currentComposition.childActivation],\n              }\n            : {}),\n        },\n        resolveLayers: (majorMode) =>\n          filterHookDisabledLayers(majorModesConfig, resolveLayers(majorModesConfig, majorMode), harness.hooks),\n        extensionLayers: (layers) => extensionLayers(majorModesConfig, layers),\n        synchronization: currentTransitionSynchronization(repositoryRoot, process.env, harness.majorMode),\n        resolveComposition,\n      }),\n    });\n    sessionContext.provide(DOOM_TRANSITION_SERVICE, coordinator);\n\n    return () => coordinator.dispose();\n  });\n}\n\nexport default transitionCoordinatorExtension;\n"],"mappings":";;;;;;;;;;;;;;AA4BA,MAAM,eAAe;AACrB,MAAM,iBAAiB;AACvB,MAAM,iBAAiB;AAEvB,SAAgB,iCACd,gBACA,cAAiC,QAAQ,KACzC,mBAC2B;CAI3B,IAAI,wBAAwB,WAAW,GAAG,OAAO,EAAE,MAAM,oBAAoB;CAC7E,IAAI,CAAC,gBAAgB,WAAW,GAAG,OAAO,EAAE,MAAM,WAAW;CAC7D,IAAI;EACF,MAAM,QAAQ,cAAc,gBAAgB,YAAY,IAAI;EAC5D,IAAI,CAAC,SAAS,OAAO,KAAK,MAAM,QAAQ,CAAC,CAAC,WAAW,GACnD,OAAO;GACL,MAAM;GACN,qBAAqB;GACrB,kCAAkC,CAAC;EACrC;EAGF,OAAO;GACL,MAAM;GACN,qBAHoB,OAAO,OAAO,MAAM,QAAQ,CAAC,CAAC,QAAQ,UAAU,MAAM,WAAW,GAAG,CAGvD,CAAC,CAAC,OAAO,UAAU,GAAG,WAAW,KAAK,CAAC;GACxE,kCAAkC,OAAO,QAAQ,MAAM,WAAW,CAAC,CAAC,CAAC,CAClE,QAAQ,GAAG,cAAc,GAAG,WAAW,QAAQ,CAAC,CAAC,CACjD,KAAK,CAAC,iBAAiB,WAAW;EACvC;CACF,QAAQ;EACN,OAAO;GACL,MAAM;GACN,qBAAqB;GACrB,kCAAkC,CAAC;EACrC;CACF;AACF;AAkBA,SAAS,oBACP,gBACA,UACA,WAOY;CACZ,MAAM,mBAAmB,qBAAqB,cAAc;CAC5D,QAAQ,cAAc;EACpB,IAAI;GACF,MAAM,cAAc,4BAA4B;IAC9C,QAAQ,SAAS;IACjB,UAAU,SAAS;IACnB,MAAM,SAAS;IACf,QAAQ,SAAS;IACjB,cAAc,UAAU,aAAa,aAAa;IAClD,WAAW,UAAU;IACrB,QAAQ,CAAC,GAAG,UAAU,MAAM;IAC5B;IACA;GACF,CAAC;GACD,OAAO;IACL,aAAa,YAAY;IACzB,kBAAkB,CAAC,GAAG,YAAY,gBAAgB;IAClD,iBAAiB,CAAC,GAAG,YAAY,eAAe;GAClD;EACF,QAAQ;GACN;EACF;CACF;AACF;AAEA,eAAsB,+BAA+B,IAAiC;CACpF,MAAM,aAAa,MAAM,sBAAsB,IAAI,cAAc;CACjE,MAAM,QAAQ,WAAW,KAAK,OAAO,2BAA2B;CAChE,IAAI;EACF,MAAM;CACR,SAAS,OAAO;EACd,MAAM,MAAM,QAAQ;EACpB,MAAM,WAAW,QAAQ;EACzB,MAAM;CACR;CACA,IAAI;CACJ,GAAG,GACD,0BAEG,cAAc,YAAY;EACzB,IAAI;GACF,MAAM,MAAM,QAAQ;EACtB,UAAU;GACR,MAAM,WAAW,QAAQ;EAC3B;CACF,EAAA,CAAG,CACP;AACF;AAEA,SAAS,4BAA4B,QAAuB;CAC1D,OAAO,OAAO,CAAC,6BAA6B,mBAAmB,IAAI,mBAAmB;EACpF,MAAM,UAAU,eAAe,IAAI,2BAA2B;EAC9D,MAAM,UAAU,QAAQ;EACxB,MAAM,YAAY,QAAQ;EAE1B,MAAM,UADa,yBAAyB,cACnB,CAAC,CAAC;EAC3B,MAAM,iBAAiB,QAAQ,QAAQ,QAAQ;EAC/C,MAAM,mBAAmB,qBAAqB,cAAc;EAC5D,MAAM,QAAQ,gBAAgB,IAAI,cAAc,gBAAgB,QAAQ,IAAI,IAAI,IAAI,KAAA;EACpF,MAAM,YAAY,QAAQ,mBAAmB,MAAM,UAAU,MAAM,QAAQ,IAAI,qBAAqB,cAAc;EAGlH,MAAM,WAAW,wBAAwB;EACzC,MAAM,qBAAqB,oBACzB,gBACA,WACI;GAAE,QAAQ,SAAS;GAAQ,UAAU,SAAS;GAAU,MAAM,SAAS;GAAM,QAAQ,SAAS;EAAO,IACrG;GACE,QAAQ,QAAQ;GAChB,UAAU;GACV,MAAM,QAAQ,IAAI,cAAc;GAChC,QAAQ,OAAO,UAAU,UAAU;EACrC,GACJ,SACF;EACA,MAAM,mBAAgD;GACpD,SAAS,CAAC,GAAG,QAAQ,OAAO;GAC5B,WAAW,QAAQ;GACnB,QAAQ,CAAC,GAAG,QAAQ,MAAM;GAC1B,SAAS,QAAQ;GACjB,iBAAiB,CAAC,GAAG,QAAQ,eAAe;EAC9C;EACA,MAAM,qBAAqB,mBAAmB,gBAAgB;EAC9D,MAAM,iBAAiB,mBAAmB,OAAO,WAAW;EAC5D,MAAM,cAAyC,gCAAgC;GAC7E;GACA;GACA,mBAAmB;IACjB;IACA;IACA,kBAAkB,gCAAgC,cAAc;GAClE;GACA,0BAA0B;IACxB,SAAS;KACP,GAAG;KACH,GAAI,qBACA;MACE,wBAAwB,mBAAmB;MAC3C,kBAAkB,CAAC,GAAG,mBAAmB,gBAAgB;MACzD,iBAAiB,CAAC,GAAG,mBAAmB,eAAe;KACzD,IACA,CAAC;IACP;IACA,gBAAgB,cACd,yBAAyB,kBAAkB,cAAc,kBAAkB,SAAS,GAAG,QAAQ,KAAK;IACtG,kBAAkB,WAAW,gBAAgB,kBAAkB,MAAM;IACrE,iBAAiB,iCAAiC,gBAAgB,QAAQ,KAAK,QAAQ,SAAS;IAChG;GACF;EACF,CAAC;EACD,eAAe,QAAQ,yBAAyB,WAAW;EAE3D,aAAa,YAAY,QAAQ;CACnC,CAAC;AACH"}