@agimon-ai/doompi 0.0.1-alpha.103 → 0.0.1-alpha.106

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/src/builders/cli/harnessContext.cjs +1 -1
  2. package/dist/src/builders/cli/harnessContext.cjs.map +1 -1
  3. package/dist/src/builders/cli/harnessContext.mjs +1 -1
  4. package/dist/src/builders/cli/harnessContext.mjs.map +1 -1
  5. package/dist/src/builders/server/runtime.cjs +25 -9
  6. package/dist/src/builders/server/runtime.cjs.map +1 -1
  7. package/dist/src/builders/server/runtime.mjs +25 -9
  8. package/dist/src/builders/server/runtime.mjs.map +1 -1
  9. package/dist/src/builders/server/runtimeResources.cjs +72 -0
  10. package/dist/src/builders/server/runtimeResources.cjs.map +1 -0
  11. package/dist/src/builders/server/runtimeResources.mjs +71 -0
  12. package/dist/src/builders/server/runtimeResources.mjs.map +1 -0
  13. package/dist/src/cli/harnessOptions.cjs +2 -1
  14. package/dist/src/cli/harnessOptions.cjs.map +1 -1
  15. package/dist/src/cli/harnessOptions.mjs +2 -1
  16. package/dist/src/cli/harnessOptions.mjs.map +1 -1
  17. package/dist/src/composition/layerPackageInstaller/index.cjs +4 -2
  18. package/dist/src/composition/layerPackageInstaller/index.cjs.map +1 -1
  19. package/dist/src/composition/layerPackageInstaller/index.d.mts.map +1 -1
  20. package/dist/src/composition/layerPackageInstaller/index.mjs +4 -2
  21. package/dist/src/composition/layerPackageInstaller/index.mjs.map +1 -1
  22. package/dist/src/composition/types/harness.d.mts +2 -0
  23. package/dist/src/composition/types/harness.d.mts.map +1 -1
  24. package/package.json +18 -18
  25. package/dist/src/builders/server/executionBudget.cjs +0 -64
  26. package/dist/src/builders/server/executionBudget.cjs.map +0 -1
  27. package/dist/src/builders/server/executionBudget.mjs +0 -64
  28. package/dist/src/builders/server/executionBudget.mjs.map +0 -1
@@ -103,7 +103,7 @@ async function buildHarnessContext(options, telemetry = (0, _agimon_ai_doompi_co
103
103
  });
104
104
  try {
105
105
  const defaultThemePath = await (0, _agimon_ai_doompi_ui_theme.writeDefaultTheme)(resources.temporaryDirectory);
106
- const environment = { ...process.env };
106
+ const environment = { ...options.environment ?? process.env };
107
107
  delete environment[_agimon_ai_doompi_core_mcpSession.DOOM_MCP_SESSION_ENV_VAR];
108
108
  const profileEnvironment = (0, _agimon_ai_doompi_config_profiles.applyProfileEnvironment)(environment, profile?.env ?? {});
109
109
  configurePreset(options, environment);
@@ -1 +1 @@
1
- {"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/mcpSession';\nimport { packageEntry } from '@agimon-ai/doompi-core/moduleResolution';\nimport {\n createHarnessTelemetry,\n HARNESS_EVENT,\n type HarnessTelemetry,\n} from '@agimon-ai/doompi-core/runtimeLogSinkTelemetry';\nimport { resolveSyncLocation } from '@agimon-ai/doompi-core/syncLocation';\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.configRoot ?? 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 // A worktree reads its workspace's configuration; resources still come from the checkout.\n const configRoot = options.configRoot ?? options.repoRoot;\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(configRoot, 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(configRoot, options.domains, options.pluginDirectories),\n ),\n mcpAllowlist: resolveMcpAllowlist(configRoot, options.domains),\n sharedSkills: resolveSharedSkills(configRoot, 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 ?? configRoot;\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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"}
1
+ {"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/mcpSession';\nimport { packageEntry } from '@agimon-ai/doompi-core/moduleResolution';\nimport {\n createHarnessTelemetry,\n HARNESS_EVENT,\n type HarnessTelemetry,\n} from '@agimon-ai/doompi-core/runtimeLogSinkTelemetry';\nimport { resolveSyncLocation } from '@agimon-ai/doompi-core/syncLocation';\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.configRoot ?? 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 // A worktree reads its workspace's configuration; resources still come from the checkout.\n const configRoot = options.configRoot ?? options.repoRoot;\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(configRoot, 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(configRoot, options.domains, options.pluginDirectories),\n ),\n mcpAllowlist: resolveMcpAllowlist(configRoot, options.domains),\n sharedSkills: resolveSharedSkills(configRoot, 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 ?? configRoot;\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 = { ...(options.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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"}
@@ -98,7 +98,7 @@ async function buildHarnessContext(options, telemetry = createHarnessTelemetry()
98
98
  });
99
99
  try {
100
100
  const defaultThemePath = await writeDefaultTheme(resources.temporaryDirectory);
101
- const environment = { ...process.env };
101
+ const environment = { ...options.environment ?? process.env };
102
102
  delete environment[DOOM_MCP_SESSION_ENV_VAR];
103
103
  const profileEnvironment = applyProfileEnvironment(environment, profile?.env ?? {});
104
104
  configurePreset(options, environment);
@@ -1 +1 @@
1
- {"version":3,"file":"harnessContext.mjs","names":[],"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/mcpSession';\nimport { packageEntry } from '@agimon-ai/doompi-core/moduleResolution';\nimport {\n createHarnessTelemetry,\n HARNESS_EVENT,\n type HarnessTelemetry,\n} from '@agimon-ai/doompi-core/runtimeLogSinkTelemetry';\nimport { resolveSyncLocation } from '@agimon-ai/doompi-core/syncLocation';\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.configRoot ?? 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 // A worktree reads its workspace's configuration; resources still come from the checkout.\n const configRoot = options.configRoot ?? options.repoRoot;\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(configRoot, 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(configRoot, options.domains, options.pluginDirectories),\n ),\n mcpAllowlist: resolveMcpAllowlist(configRoot, options.domains),\n sharedSkills: resolveSharedSkills(configRoot, 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 ?? configRoot;\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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"}
1
+ {"version":3,"file":"harnessContext.mjs","names":[],"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/mcpSession';\nimport { packageEntry } from '@agimon-ai/doompi-core/moduleResolution';\nimport {\n createHarnessTelemetry,\n HARNESS_EVENT,\n type HarnessTelemetry,\n} from '@agimon-ai/doompi-core/runtimeLogSinkTelemetry';\nimport { resolveSyncLocation } from '@agimon-ai/doompi-core/syncLocation';\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.configRoot ?? 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 // A worktree reads its workspace's configuration; resources still come from the checkout.\n const configRoot = options.configRoot ?? options.repoRoot;\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(configRoot, 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(configRoot, options.domains, options.pluginDirectories),\n ),\n mcpAllowlist: resolveMcpAllowlist(configRoot, options.domains),\n sharedSkills: resolveSharedSkills(configRoot, 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 ?? configRoot;\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 = { ...(options.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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"}
@@ -7,8 +7,8 @@ const require_index$4 = require("../../composition/syncDrift/index.cjs");
7
7
  const require_harnessContext = require("../cli/harnessContext.cjs");
8
8
  const require_computerUseBinding = require("./computerUseBinding.cjs");
9
9
  const require_defaultWorkspace = require("./defaultWorkspace.cjs");
10
- const require_executionBudget = require("./executionBudget.cjs");
11
10
  const require_logSink = require("./logSink.cjs");
11
+ const require_runtimeResources = require("./runtimeResources.cjs");
12
12
  const require_selectionStatus = require("./selectionStatus.cjs");
13
13
  const require_sessionArguments = require("./sessionArguments.cjs");
14
14
  const require_sessionArtifact = require("./sessionArtifact.cjs");
@@ -35,6 +35,7 @@ let _agimon_ai_doompi_core_headlessHub = require("@agimon-ai/doompi-core/headles
35
35
  let _agimon_ai_doompi_core_headlessServer = require("@agimon-ai/doompi-core/headlessServer");
36
36
  let _agimon_ai_doompi_core_headlessSessionManager = require("@agimon-ai/doompi-core/headlessSessionManager");
37
37
  let _agimon_ai_doompi_core_history = require("@agimon-ai/doompi-core/history");
38
+ let _agimon_ai_doompi_core_packageApi = require("@agimon-ai/doompi-core/packageApi");
38
39
  let _agimon_ai_doompi_core_packageApiServer = require("@agimon-ai/doompi-core/packageApiServer");
39
40
  let _agimon_ai_doompi_core_piSettings = require("@agimon-ai/doompi-core/piSettings");
40
41
  let _agimon_ai_doompi_core_remoteRuntime = require("@agimon-ai/doompi-core/remoteRuntime");
@@ -58,7 +59,7 @@ async function runServerRuntime(options, runtime) {
58
59
  ...incomingEnvironment,
59
60
  LOG_SINK_INSTANCE: "global"
60
61
  };
61
- const executionBudget = require_executionBudget.createExecutionBudget(Number(baseEnvironment.DOOM_RUNNER_MAX_HEAVY_JOBS ?? 2));
62
+ const executionBudget = (0, _agimon_ai_doompi_core_packageApi.createServerExecutionBudget)(baseEnvironment);
62
63
  await require_logSink.ensureGlobalLogSink({
63
64
  cwd: baseCwd,
64
65
  env: baseEnvironment,
@@ -276,6 +277,19 @@ async function runServerRuntime(options, runtime) {
276
277
  let remoteRuntime;
277
278
  let attachToken;
278
279
  let webCompositions;
280
+ const stopResourceMonitor = require_runtimeResources.monitorRuntimeResources({
281
+ record: (attributes) => telemetry.recordEvent("doompi_server.resources", attributes),
282
+ counts: () => {
283
+ const budget = executionBudget.getSnapshot();
284
+ return {
285
+ sessions: sessionManager.sessions().length,
286
+ running_jobs: budget.running,
287
+ queued_jobs: budget.queued,
288
+ job_limit: budget.limit
289
+ };
290
+ },
291
+ warn: notice
292
+ });
279
293
  try {
280
294
  await telemetry.runInSpan("doompi_server.startup", {}, async () => {
281
295
  const token = node_fs.default.readFileSync(options.tokenFile, "utf8").trim();
@@ -300,26 +314,27 @@ async function runServerRuntime(options, runtime) {
300
314
  activeLayers: (0, _agimon_ai_doompi_config_majorModes.resolveLayers)(config, majorMode)
301
315
  };
302
316
  })();
303
- return (0, _agimon_ai_doompi_core_serverFacet.loadServerBundle)(scope, {
317
+ return telemetry.runInSpan("doompi_server.composition_load", { scope }, () => (0, _agimon_ai_doompi_core_serverFacet.loadServerBundle)(scope, {
304
318
  ...source,
305
319
  ...selected,
306
320
  retainCandidates: scope === "session",
307
321
  onNotice: notice
308
- });
322
+ }));
309
323
  };
310
324
  const loadSessionMcp = async (root, selection, pinnedRegistration) => {
311
325
  const registration = pinnedRegistration ?? (0, _agimon_ai_doompi_core_syncRegistration.readSyncRegistration)(root, homeDirectory);
312
326
  if (registration?.mcpBundle === void 0) throw new Error(`Run the scoped DoomPi sync for '${root}' before opening its MCP runtime.`);
313
- return (0, _agimon_ai_doompi_core_mcpFacet.loadMcpBundle)({
314
- directory: node_path.default.dirname(registration.mcpBundle.path),
327
+ const bundle = registration.mcpBundle;
328
+ return telemetry.runInSpan("doompi_server.mcp_bundle_load", {}, () => (0, _agimon_ai_doompi_core_mcpFacet.loadMcpBundle)({
329
+ directory: node_path.default.dirname(bundle.path),
315
330
  generation: registration.generation,
316
- fingerprint: registration.mcpBundle.fingerprint,
317
- descriptorSha256: registration.mcpBundle.sha256,
331
+ fingerprint: bundle.fingerprint,
332
+ descriptorSha256: bundle.sha256,
318
333
  majorMode: selection.majorMode,
319
334
  activeLayers: selection.activeLayers,
320
335
  retainCandidates: true,
321
336
  onNotice: notice
322
- });
337
+ }));
323
338
  };
324
339
  const sharedApiContext = {
325
340
  homeDirectory,
@@ -961,6 +976,7 @@ async function runServerRuntime(options, runtime) {
961
976
  }
962
977
  } finally {
963
978
  shuttingDown = true;
979
+ stopResourceMonitor();
964
980
  executionBudget.close();
965
981
  stopPersistSessionNames();
966
982
  clearInterval(eventLoopMonitor);