{"version":3,"file":"harnessContext.cjs","names":["resolveProfile","createHarnessTelemetry","loadMajorModesConfig","filterHookDisabledLayers","resolveLayers","fs","layerHookGroups","materializePluginEntries","resolvePluginEntries","resolveMcpAllowlist","resolveSharedSkills","HARNESS_EVENT","collectResources","path","resolveSyncLocation","os","globalDoomConfigDirectory","writeDefaultTheme","DOOM_MCP_SESSION_ENV_VAR","applyProfileEnvironment","DEFAULT_THEME_NAME","packageEntry","PERSONA_ENTRY","buildPersonaPrompt"],"sources":["../../../src/builders/cli/harnessContext.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { globalDoomConfigDirectory } from '@agimon-ai/doompi-config/config';\nimport { resolvePluginEntries, resolveSharedSkills } from '@agimon-ai/doompi-config/domains';\nimport type { DomainMcpAllowlist, PluginEntry } from '@agimon-ai/doompi-config/domains';\nimport {\n  filterHookDisabledLayers,\n  layerHookGroups,\n  loadMajorModesConfig,\n  resolveLayers,\n} from '@agimon-ai/doompi-config/majorModes';\nimport type { MajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { applyProfileEnvironment, buildPersonaPrompt, resolveProfile } from '@agimon-ai/doompi-config/profiles';\nimport type { AgentProfile } from '@agimon-ai/doompi-config/profiles';\nimport { DOOM_MCP_SESSION_ENV_VAR } from '@agimon-ai/doompi-core/mcp-session';\nimport { packageEntry } from '@agimon-ai/doompi-core/module-resolution';\nimport {\n  createHarnessTelemetry,\n  HARNESS_EVENT,\n  type HarnessTelemetry,\n} from '@agimon-ai/doompi-core/runtime-log-sink-telemetry';\nimport { resolveSyncLocation } from '@agimon-ai/doompi-core/sync-location';\nimport { resolveMcpAllowlist } from '@agimon-ai/doompi-domain/mcp';\nimport { materializePluginEntries } from '@agimon-ai/doompi-domain/plugins';\nimport { collectResources, type HarnessResources } from '@agimon-ai/doompi-domain/resources';\nimport { DEFAULT_THEME_NAME, writeDefaultTheme } from '@agimon-ai/doompi-ui/theme';\n\nimport { createHarnessSession } from '../../composition/harnessState';\nimport type { HarnessOptions } from '../../composition/types/harness';\nimport { PERSONA_ENTRY } from './extensionAssembler';\n\nconst OLLAMA_PRESET = 'ollama';\nconst PRIVATE_FILE_MODE = 0o600;\nconst RESOLVE_CONFIG_PHASE = 'resolve_config';\nconst COLLECT_RESOURCES_PHASE = 'collect_resources';\nconst BUILD_CONTEXT_PHASE = 'build_context';\n\n/**\n * Everything a command needs, resolved once.\n *\n * All three commands need the same expensive preparation (domains resolved,\n * plugins collected into a temp directory, environment assembled), so it is\n * built here and handed to whichever command runs.\n */\nexport interface HarnessContext {\n  options: HarnessOptions;\n  environment: NodeJS.ProcessEnv;\n  resources: HarnessResources;\n  plugins: PluginEntry[];\n  profile?: string;\n  personaDirectory?: string;\n  /** Root personaDirectory resolves against, when a profile supplied one. */\n  personaRoot?: string;\n  mcpAllowlist?: DomainMcpAllowlist;\n  majorModesConfig: MajorModesConfig;\n  selectedLayers: string[];\n  hookGroups: string[];\n  personaEntry?: string;\n  defaultThemePath: string;\n  cleanup(): Promise<void>;\n}\n\n/**\n * Applies the provider environment a preset needs.\n *\n * Exported because the synced Pi path has no launcher to do it: the doom-pi\n * extension applies the same rules in-process before anything reads them.\n * `piArgs` is only consulted to spot a selected cloud model.\n */\nexport function configurePreset(\n  options: Pick<HarnessOptions, 'preset' | 'piArgs'>,\n  environment: NodeJS.ProcessEnv,\n): void {\n  if (options.preset === 'kimi') {\n    if (!environment.KIMI_API_KEY && environment.KIMI_CODE_API_KEY) {\n      environment.KIMI_API_KEY = environment.KIMI_CODE_API_KEY;\n    }\n    if (!environment.KIMI_API_KEY) {\n      throw new Error('KIMI_API_KEY or KIMI_CODE_API_KEY is required for the kimi preset');\n    }\n  }\n  if (options.preset === OLLAMA_PRESET) {\n    const selected = options.piArgs.find((argument) => argument.startsWith(`${OLLAMA_PRESET}/`));\n    const cloud = selected?.endsWith(':cloud') ?? false;\n    if (cloud && !environment.OLLAMA_API_KEY) {\n      throw new Error('OLLAMA_API_KEY is required for Ollama cloud models');\n    }\n    environment.OLLAMA_API_KEY ||= OLLAMA_PRESET;\n    environment.DOOMPI_OLLAMA_BASE_URL =\n      environment.OLLAMA_BASE_URL ?? environment.ANTHROPIC_BASE_URL ?? 'http://localhost:11434/v1';\n  }\n}\n\n/** Resolves the optional persona and environment profile selected for this run. */\nexport function resolveHarnessProfile(options: HarnessOptions): AgentProfile | undefined {\n  return options.profile ? resolveProfile(options.repoRoot, options.profile) : undefined;\n}\n\n/**\n * Resolves the full matrix and stages every resource Pi will need.\n *\n * The caller owns the returned cleanup(): the temporary directory holding\n * skills, agents, and the generated MCP config has to outlive this function\n * and be removed once the selected command is done with it.\n */\nexport async function buildHarnessContext(\n  options: HarnessOptions,\n  telemetry: HarnessTelemetry = createHarnessTelemetry(),\n): Promise<HarnessContext> {\n  // Everything up to collectResources reads configuration off disk, so a single\n  // malformed modes.yaml or profiles.yaml surfaces here as a failed launch\n  // with no other record.\n  const configured = await telemetry\n    .runInSpan(`doom_pi.${RESOLVE_CONFIG_PHASE}`, { 'harness.phase': RESOLVE_CONFIG_PHASE }, async () => {\n      const majorModesConfig = loadMajorModesConfig(options.repoRoot, options.homeDirectory);\n      const selectedLayers = filterHookDisabledLayers(\n        majorModesConfig,\n        resolveLayers(majorModesConfig, options.majorMode),\n        options.hooks,\n      );\n      const profile = resolveHarnessProfile(options);\n      if (!fs.existsSync(options.cwd) || !fs.statSync(options.cwd).isDirectory()) {\n        throw new Error(`Working directory does not exist: ${options.cwd}`);\n      }\n      return {\n        majorModesConfig,\n        selectedLayers,\n        hookGroups: layerHookGroups(majorModesConfig, selectedLayers),\n        profile,\n        plugins: await materializePluginEntries(\n          resolvePluginEntries(options.repoRoot, options.domains, options.pluginDirectories),\n        ),\n        mcpAllowlist: resolveMcpAllowlist(options.repoRoot, options.domains),\n        sharedSkills: resolveSharedSkills(options.repoRoot, options.domains),\n      };\n    })\n    .catch(async (error: unknown) => {\n      await telemetry.recordError(HARNESS_EVENT.configLoadFailed, error, {\n        'harness.phase': RESOLVE_CONFIG_PHASE,\n        'harness.major_mode': options.majorMode,\n        'harness.domain_count': options.domains.length,\n      });\n      throw error;\n    });\n\n  const { majorModesConfig, selectedLayers, hookGroups, profile, plugins, mcpAllowlist } = configured;\n  const personaDirectory = profile?.persona;\n  // A global profile's persona lives beside the global config, not in the repo.\n  const personaRoot = profile?.personaRoot ?? options.repoRoot;\n  const resources = await telemetry\n    .runInSpan(\n      `doom_pi.${COLLECT_RESOURCES_PHASE}`,\n      { 'harness.plugin.count': plugins.length, 'harness.agents': options.agents, 'harness.mcp': options.mcp },\n      () =>\n        collectResources(options.repoRoot, plugins, {\n          agents: options.agents,\n          mcp: options.mcp,\n          // Set by `doom-pi sync`, which stages into a directory that outlives\n          // the process rather than a temporary one.\n          temporaryDirectory: options.resourceDirectory,\n          skillCacheDirectory: path.join(\n            options.resourceDirectory ??\n              resolveSyncLocation(options.repoRoot, options.homeDirectory ?? os.homedir()).directory,\n            'cache',\n            'skills',\n          ),\n          mcpAllowlist,\n          sharedSkills: configured.sharedSkills,\n          pluginDataRoot: path.join(globalDoomConfigDirectory(options.homeDirectory ?? os.homedir()), 'plugin-data'),\n        }),\n    )\n    .catch(async (error: unknown) => {\n      // Conflicting skills, agents, or MCP servers all fail here, and the plugin\n      // that caused it is only identifiable from the message.\n      await telemetry.recordError(HARNESS_EVENT.resourceCollectionFailed, error, {\n        'harness.phase': COLLECT_RESOURCES_PHASE,\n        'harness.plugin.count': plugins.length,\n      });\n      throw error;\n    });\n\n  try {\n    const defaultThemePath = await writeDefaultTheme(resources.temporaryDirectory);\n    const environment = { ...process.env };\n    // The file-backed projection published through Cordis is authoritative.\n    // Never let a legacy parent-process projection leak into this Doom session.\n    delete environment[DOOM_MCP_SESSION_ENV_VAR];\n    const profileEnvironment = applyProfileEnvironment(environment, profile?.env ?? {});\n\n    configurePreset(options, environment);\n    environment.NX_DAEMON ??= 'false';\n    environment.DOOMPI_THEME ||= DEFAULT_THEME_NAME;\n    environment.CLAUDE_PROJECT_DIR = options.repoRoot;\n    environment.CODEX_REPO_ROOT = options.repoRoot;\n    environment.ORIGINAL_REPO_PATH ||= options.repoRoot;\n    environment.MCP_UI_VIEWER = options.automation ? 'none' : (environment.MCP_UI_VIEWER ?? '');\n\n    // Always loaded, even with no persona at launch: the extension no-ops\n    // without a persona file, and /profile needs it present to load one later.\n    // Pi freezes the --extension list at startup, so it cannot be added on demand.\n    const personaEntry = packageEntry(PERSONA_ENTRY, import.meta.url);\n    const personaPrompt = personaDirectory ? buildPersonaPrompt(personaRoot, personaDirectory) : undefined;\n    let personaFile: string | undefined;\n    if (personaPrompt) {\n      personaFile = path.join(resources.temporaryDirectory, 'persona.md');\n      await fs.promises.writeFile(personaFile, `${personaPrompt}\\n`, { mode: PRIVATE_FILE_MODE });\n    }\n\n    // One write, describing the child rather than this process. The state file\n    // is the child's authority and the environment is what it projects for the\n    // hooks and packages that can only read an environment.\n    createHarnessSession(\n      {\n        root: options.repoRoot,\n        majorMode: options.majorMode,\n        temporaryDirectory: resources.temporaryDirectory,\n        domains: options.domains,\n        layers: selectedLayers,\n        profile: profile?.name,\n        profileIdentity: profile?.identity,\n        profileVoice: profile?.voice,\n        profileEnvironment,\n        personaFile,\n        skillDirectories: resources.skillDirectories,\n        agentDirectories: resources.agentDirectories,\n        additionalDirectories: options.additionalDirectories,\n        childExtensions: [],\n        pluginDirectories: options.pluginDirectories,\n        pluginHooks: resources.pluginHooks,\n        hookGroups,\n        hooks: options.hooks,\n        agents: options.agents,\n        mcp: options.mcp,\n        mcpConfigPath: resources.mcpConfigPath,\n        mcpProjection: resources.mcpProjection,\n        allowProtectedWrites: options.allowProtectedWrites,\n      },\n      { directory: resources.temporaryDirectory, environment },\n    );\n    if (options.agents) {\n      environment.PI_SUBAGENT_EXTRA_AGENT_DIRS = resources.agentDirectories.join(path.delimiter);\n      environment.PI_SUBAGENT_EXTRA_SKILL_DIRS = resources.skillDirectories.join(path.delimiter);\n      environment.PI_SUBAGENT_PI_BINARY = path.join(options.repoRoot, 'pi.sh');\n    }\n\n    // Core hook groups load regardless; hookGroups selects the rest. The initial\n    // state write includes an empty list deliberately: unset means every group.\n\n    return {\n      options,\n      environment,\n      resources,\n      plugins,\n      profile: profile?.name,\n      personaDirectory,\n      personaRoot,\n      mcpAllowlist,\n      majorModesConfig,\n      selectedLayers,\n      hookGroups,\n      personaEntry,\n      defaultThemePath,\n      cleanup: resources.cleanup.bind(resources),\n    };\n  } catch (error) {\n    // Preparation failed after the temp directory existed, so nothing will be\n    // handed back to clean it up.\n    await telemetry.recordError(HARNESS_EVENT.contextBuildFailed, error, {\n      'harness.phase': BUILD_CONTEXT_PHASE,\n      'harness.major_mode': options.majorMode,\n      ...(profile?.name ? { 'harness.profile': profile.name } : {}),\n    });\n    await resources.cleanup();\n    throw error;\n  }\n}\n"],"mappings":"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"}