@agimon-ai/doompi 0.0.1-alpha.104 → 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.
@@ -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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"}
@@ -54,7 +54,8 @@ function resolveHarnessOptions(input) {
54
54
  repoRoot,
55
55
  ...input.configRoot === void 0 ? {} : { configRoot },
56
56
  ...parsed.options,
57
- homeDirectory
57
+ homeDirectory,
58
+ ...input.environment === void 0 ? {} : { environment: input.environment }
58
59
  };
59
60
  }
60
61
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"harnessOptions.cjs","names":["findRepositoryRoot","path","parseHarnessArgs","readHarnessState","os","loadDoomConfig","HARNESS_STATE_KEYS","loadMajorModesConfig","loadDomains","loadProfileCatalog"],"sources":["../../../src/cli/harnessOptions.ts"],"sourcesContent":["import os from 'node:os';\nimport path from 'node:path';\n\nimport { loadDoomConfig } from '@agimon-ai/doompi-config/config';\nimport { loadDomains } from '@agimon-ai/doompi-config/domains';\nimport { HARNESS_STATE_KEYS } from '@agimon-ai/doompi-config/harnessState';\nimport { loadMajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { loadProfileCatalog } from '@agimon-ai/doompi-config/profiles';\nimport type { HarnessFailureReporter } from '@agimon-ai/doompi-core/runtimeLogSinkTelemetry';\n\nimport { readHarnessState } from '../composition/harnessState';\nimport { findRepositoryRoot } from '../composition/repository';\nimport type { HarnessOptions } from '../composition/types/harness';\nimport { parseHarnessArgs } from './options';\n\nexport interface ResolveHarnessOptionsInput {\n /** Raw launcher arguments, as the binary received them. */\n args: readonly string[];\n environment?: NodeJS.ProcessEnv;\n cwd?: string;\n /** Reads configuration from this root instead of the checkout's own. */\n configRoot?: string;\n /** Reports a malformed inherited harness state without failing the run. */\n report?: HarnessFailureReporter;\n}\n\n/** Uses the nearest configured repository, or the selected directory for an unconfigured workspace. */\nfunction resolveConfigurationRoot(cwd: string): string {\n try {\n return findRepositoryRoot(cwd);\n } catch {\n return path.resolve(cwd);\n }\n}\n\n/**\n * Settles the configuration root and the full option matrix for one run.\n *\n * Two passes are required: the first reads --cwd without touching repository\n * configuration, which keeps a run resolvable even when modes.yaml is\n * malformed, and the second applies the defaults that root supplies. An\n * inherited root wins, which is how a nested run stays pinned to the outer\n * repository rather than re-deriving one from its own cwd. When no configured\n * repository exists, the selected working directory is the configuration root.\n */\nexport function resolveHarnessOptions(input: ResolveHarnessOptionsInput): HarnessOptions {\n const environment = input.environment ?? process.env;\n const cwd = input.cwd ?? process.cwd();\n const args = [...input.args];\n\n const initial = parseHarnessArgs(args, environment, cwd);\n const inheritedRoot = readHarnessState(environment, input.report).root;\n const repoRoot = inheritedRoot ? path.resolve(inheritedRoot) : resolveConfigurationRoot(initial.options.cwd);\n const homeDirectory = initial.options.homeDirectory ?? environment.HOME ?? os.homedir();\n const configRoot = input.configRoot ?? repoRoot;\n const defaults = loadDoomConfig(configRoot, homeDirectory).selection;\n const selectedEnvironment = { ...environment };\n for (const axis of ['majorMode', 'domains', 'profile'] as const) {\n const value = defaults?.[axis];\n if (value !== undefined && selectedEnvironment[HARNESS_STATE_KEYS[axis]] === undefined)\n selectedEnvironment[HARNESS_STATE_KEYS[axis]] = Array.isArray(value) ? value.join(',') : value;\n }\n const parsed = parseHarnessArgs(\n args,\n selectedEnvironment,\n cwd,\n loadMajorModesConfig(configRoot, homeDirectory).defaultMajorMode,\n loadDomains(configRoot, homeDirectory).defaultDomains,\n loadProfileCatalog(configRoot, homeDirectory).defaultProfile,\n );\n return { repoRoot, ...(input.configRoot === undefined ? {} : { configRoot }), ...parsed.options, homeDirectory };\n}\n"],"mappings":";;;;;;;;;;;;;;;AA2BA,SAAS,yBAAyB,KAAqB;CACrD,IAAI;EACF,OAAOA,gBAAAA,mBAAmB,GAAG;CAC/B,QAAQ;EACN,OAAOC,UAAAA,QAAK,QAAQ,GAAG;CACzB;AACF;;;;;;;;;;;AAYA,SAAgB,sBAAsB,OAAmD;CACvF,MAAM,cAAc,MAAM,eAAe,QAAQ;CACjD,MAAM,MAAM,MAAM,OAAO,QAAQ,IAAI;CACrC,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI;CAE3B,MAAM,UAAUC,gBAAAA,iBAAiB,MAAM,aAAa,GAAG;CACvD,MAAM,gBAAgBC,cAAAA,iBAAiB,aAAa,MAAM,MAAM,CAAC,CAAC;CAClE,MAAM,WAAW,gBAAgBF,UAAAA,QAAK,QAAQ,aAAa,IAAI,yBAAyB,QAAQ,QAAQ,GAAG;CAC3G,MAAM,gBAAgB,QAAQ,QAAQ,iBAAiB,YAAY,QAAQG,QAAAA,QAAG,QAAQ;CACtF,MAAM,aAAa,MAAM,cAAc;CACvC,MAAM,YAAA,GAAWC,gCAAAA,eAAAA,CAAe,YAAY,aAAa,CAAC,CAAC;CAC3D,MAAM,sBAAsB,EAAE,GAAG,YAAY;CAC7C,KAAK,MAAM,QAAQ;EAAC;EAAa;EAAW;CAAS,GAAY;EAC/D,MAAM,QAAQ,WAAW;EACzB,IAAI,UAAU,KAAA,KAAa,oBAAoBC,sCAAAA,mBAAmB,WAAW,KAAA,GAC3E,oBAAoBA,sCAAAA,mBAAmB,SAAS,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,GAAG,IAAI;CAC7F;CACA,MAAM,SAASJ,gBAAAA,iBACb,MACA,qBACA,MAAA,GACAK,oCAAAA,qBAAAA,CAAqB,YAAY,aAAa,CAAC,CAAC,mBAAA,GAChDC,iCAAAA,YAAAA,CAAY,YAAY,aAAa,CAAC,CAAC,iBAAA,GACvCC,kCAAAA,mBAAAA,CAAmB,YAAY,aAAa,CAAC,CAAC,cAChD;CACA,OAAO;EAAE;EAAU,GAAI,MAAM,eAAe,KAAA,IAAY,CAAC,IAAI,EAAE,WAAW;EAAI,GAAG,OAAO;EAAS;CAAc;AACjH"}
1
+ {"version":3,"file":"harnessOptions.cjs","names":["findRepositoryRoot","path","parseHarnessArgs","readHarnessState","os","loadDoomConfig","HARNESS_STATE_KEYS","loadMajorModesConfig","loadDomains","loadProfileCatalog"],"sources":["../../../src/cli/harnessOptions.ts"],"sourcesContent":["import os from 'node:os';\nimport path from 'node:path';\n\nimport { loadDoomConfig } from '@agimon-ai/doompi-config/config';\nimport { loadDomains } from '@agimon-ai/doompi-config/domains';\nimport { HARNESS_STATE_KEYS } from '@agimon-ai/doompi-config/harnessState';\nimport { loadMajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { loadProfileCatalog } from '@agimon-ai/doompi-config/profiles';\nimport type { HarnessFailureReporter } from '@agimon-ai/doompi-core/runtimeLogSinkTelemetry';\n\nimport { readHarnessState } from '../composition/harnessState';\nimport { findRepositoryRoot } from '../composition/repository';\nimport type { HarnessOptions } from '../composition/types/harness';\nimport { parseHarnessArgs } from './options';\n\nexport interface ResolveHarnessOptionsInput {\n /** Raw launcher arguments, as the binary received them. */\n args: readonly string[];\n environment?: NodeJS.ProcessEnv;\n cwd?: string;\n /** Reads configuration from this root instead of the checkout's own. */\n configRoot?: string;\n /** Reports a malformed inherited harness state without failing the run. */\n report?: HarnessFailureReporter;\n}\n\n/** Uses the nearest configured repository, or the selected directory for an unconfigured workspace. */\nfunction resolveConfigurationRoot(cwd: string): string {\n try {\n return findRepositoryRoot(cwd);\n } catch {\n return path.resolve(cwd);\n }\n}\n\n/**\n * Settles the configuration root and the full option matrix for one run.\n *\n * Two passes are required: the first reads --cwd without touching repository\n * configuration, which keeps a run resolvable even when modes.yaml is\n * malformed, and the second applies the defaults that root supplies. An\n * inherited root wins, which is how a nested run stays pinned to the outer\n * repository rather than re-deriving one from its own cwd. When no configured\n * repository exists, the selected working directory is the configuration root.\n */\nexport function resolveHarnessOptions(input: ResolveHarnessOptionsInput): HarnessOptions {\n const environment = input.environment ?? process.env;\n const cwd = input.cwd ?? process.cwd();\n const args = [...input.args];\n\n const initial = parseHarnessArgs(args, environment, cwd);\n const inheritedRoot = readHarnessState(environment, input.report).root;\n const repoRoot = inheritedRoot ? path.resolve(inheritedRoot) : resolveConfigurationRoot(initial.options.cwd);\n const homeDirectory = initial.options.homeDirectory ?? environment.HOME ?? os.homedir();\n const configRoot = input.configRoot ?? repoRoot;\n const defaults = loadDoomConfig(configRoot, homeDirectory).selection;\n const selectedEnvironment = { ...environment };\n for (const axis of ['majorMode', 'domains', 'profile'] as const) {\n const value = defaults?.[axis];\n if (value !== undefined && selectedEnvironment[HARNESS_STATE_KEYS[axis]] === undefined)\n selectedEnvironment[HARNESS_STATE_KEYS[axis]] = Array.isArray(value) ? value.join(',') : value;\n }\n const parsed = parseHarnessArgs(\n args,\n selectedEnvironment,\n cwd,\n loadMajorModesConfig(configRoot, homeDirectory).defaultMajorMode,\n loadDomains(configRoot, homeDirectory).defaultDomains,\n loadProfileCatalog(configRoot, homeDirectory).defaultProfile,\n );\n return {\n repoRoot,\n ...(input.configRoot === undefined ? {} : { configRoot }),\n ...parsed.options,\n homeDirectory,\n ...(input.environment === undefined ? {} : { environment: input.environment }),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;AA2BA,SAAS,yBAAyB,KAAqB;CACrD,IAAI;EACF,OAAOA,gBAAAA,mBAAmB,GAAG;CAC/B,QAAQ;EACN,OAAOC,UAAAA,QAAK,QAAQ,GAAG;CACzB;AACF;;;;;;;;;;;AAYA,SAAgB,sBAAsB,OAAmD;CACvF,MAAM,cAAc,MAAM,eAAe,QAAQ;CACjD,MAAM,MAAM,MAAM,OAAO,QAAQ,IAAI;CACrC,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI;CAE3B,MAAM,UAAUC,gBAAAA,iBAAiB,MAAM,aAAa,GAAG;CACvD,MAAM,gBAAgBC,cAAAA,iBAAiB,aAAa,MAAM,MAAM,CAAC,CAAC;CAClE,MAAM,WAAW,gBAAgBF,UAAAA,QAAK,QAAQ,aAAa,IAAI,yBAAyB,QAAQ,QAAQ,GAAG;CAC3G,MAAM,gBAAgB,QAAQ,QAAQ,iBAAiB,YAAY,QAAQG,QAAAA,QAAG,QAAQ;CACtF,MAAM,aAAa,MAAM,cAAc;CACvC,MAAM,YAAA,GAAWC,gCAAAA,eAAAA,CAAe,YAAY,aAAa,CAAC,CAAC;CAC3D,MAAM,sBAAsB,EAAE,GAAG,YAAY;CAC7C,KAAK,MAAM,QAAQ;EAAC;EAAa;EAAW;CAAS,GAAY;EAC/D,MAAM,QAAQ,WAAW;EACzB,IAAI,UAAU,KAAA,KAAa,oBAAoBC,sCAAAA,mBAAmB,WAAW,KAAA,GAC3E,oBAAoBA,sCAAAA,mBAAmB,SAAS,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,GAAG,IAAI;CAC7F;CACA,MAAM,SAASJ,gBAAAA,iBACb,MACA,qBACA,MAAA,GACAK,oCAAAA,qBAAAA,CAAqB,YAAY,aAAa,CAAC,CAAC,mBAAA,GAChDC,iCAAAA,YAAAA,CAAY,YAAY,aAAa,CAAC,CAAC,iBAAA,GACvCC,kCAAAA,mBAAAA,CAAmB,YAAY,aAAa,CAAC,CAAC,cAChD;CACA,OAAO;EACL;EACA,GAAI,MAAM,eAAe,KAAA,IAAY,CAAC,IAAI,EAAE,WAAW;EACvD,GAAG,OAAO;EACV;EACA,GAAI,MAAM,gBAAgB,KAAA,IAAY,CAAC,IAAI,EAAE,aAAa,MAAM,YAAY;CAC9E;AACF"}
@@ -51,7 +51,8 @@ function resolveHarnessOptions(input) {
51
51
  repoRoot,
52
52
  ...input.configRoot === void 0 ? {} : { configRoot },
53
53
  ...parsed.options,
54
- homeDirectory
54
+ homeDirectory,
55
+ ...input.environment === void 0 ? {} : { environment: input.environment }
55
56
  };
56
57
  }
57
58
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"harnessOptions.mjs","names":["readHarnessState"],"sources":["../../../src/cli/harnessOptions.ts"],"sourcesContent":["import os from 'node:os';\nimport path from 'node:path';\n\nimport { loadDoomConfig } from '@agimon-ai/doompi-config/config';\nimport { loadDomains } from '@agimon-ai/doompi-config/domains';\nimport { HARNESS_STATE_KEYS } from '@agimon-ai/doompi-config/harnessState';\nimport { loadMajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { loadProfileCatalog } from '@agimon-ai/doompi-config/profiles';\nimport type { HarnessFailureReporter } from '@agimon-ai/doompi-core/runtimeLogSinkTelemetry';\n\nimport { readHarnessState } from '../composition/harnessState';\nimport { findRepositoryRoot } from '../composition/repository';\nimport type { HarnessOptions } from '../composition/types/harness';\nimport { parseHarnessArgs } from './options';\n\nexport interface ResolveHarnessOptionsInput {\n /** Raw launcher arguments, as the binary received them. */\n args: readonly string[];\n environment?: NodeJS.ProcessEnv;\n cwd?: string;\n /** Reads configuration from this root instead of the checkout's own. */\n configRoot?: string;\n /** Reports a malformed inherited harness state without failing the run. */\n report?: HarnessFailureReporter;\n}\n\n/** Uses the nearest configured repository, or the selected directory for an unconfigured workspace. */\nfunction resolveConfigurationRoot(cwd: string): string {\n try {\n return findRepositoryRoot(cwd);\n } catch {\n return path.resolve(cwd);\n }\n}\n\n/**\n * Settles the configuration root and the full option matrix for one run.\n *\n * Two passes are required: the first reads --cwd without touching repository\n * configuration, which keeps a run resolvable even when modes.yaml is\n * malformed, and the second applies the defaults that root supplies. An\n * inherited root wins, which is how a nested run stays pinned to the outer\n * repository rather than re-deriving one from its own cwd. When no configured\n * repository exists, the selected working directory is the configuration root.\n */\nexport function resolveHarnessOptions(input: ResolveHarnessOptionsInput): HarnessOptions {\n const environment = input.environment ?? process.env;\n const cwd = input.cwd ?? process.cwd();\n const args = [...input.args];\n\n const initial = parseHarnessArgs(args, environment, cwd);\n const inheritedRoot = readHarnessState(environment, input.report).root;\n const repoRoot = inheritedRoot ? path.resolve(inheritedRoot) : resolveConfigurationRoot(initial.options.cwd);\n const homeDirectory = initial.options.homeDirectory ?? environment.HOME ?? os.homedir();\n const configRoot = input.configRoot ?? repoRoot;\n const defaults = loadDoomConfig(configRoot, homeDirectory).selection;\n const selectedEnvironment = { ...environment };\n for (const axis of ['majorMode', 'domains', 'profile'] as const) {\n const value = defaults?.[axis];\n if (value !== undefined && selectedEnvironment[HARNESS_STATE_KEYS[axis]] === undefined)\n selectedEnvironment[HARNESS_STATE_KEYS[axis]] = Array.isArray(value) ? value.join(',') : value;\n }\n const parsed = parseHarnessArgs(\n args,\n selectedEnvironment,\n cwd,\n loadMajorModesConfig(configRoot, homeDirectory).defaultMajorMode,\n loadDomains(configRoot, homeDirectory).defaultDomains,\n loadProfileCatalog(configRoot, homeDirectory).defaultProfile,\n );\n return { repoRoot, ...(input.configRoot === undefined ? {} : { configRoot }), ...parsed.options, homeDirectory };\n}\n"],"mappings":";;;;;;;;;;;;AA2BA,SAAS,yBAAyB,KAAqB;CACrD,IAAI;EACF,OAAO,mBAAmB,GAAG;CAC/B,QAAQ;EACN,OAAO,KAAK,QAAQ,GAAG;CACzB;AACF;;;;;;;;;;;AAYA,SAAgB,sBAAsB,OAAmD;CACvF,MAAM,cAAc,MAAM,eAAe,QAAQ;CACjD,MAAM,MAAM,MAAM,OAAO,QAAQ,IAAI;CACrC,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI;CAE3B,MAAM,UAAU,iBAAiB,MAAM,aAAa,GAAG;CACvD,MAAM,gBAAgBA,mBAAiB,aAAa,MAAM,MAAM,CAAC,CAAC;CAClE,MAAM,WAAW,gBAAgB,KAAK,QAAQ,aAAa,IAAI,yBAAyB,QAAQ,QAAQ,GAAG;CAC3G,MAAM,gBAAgB,QAAQ,QAAQ,iBAAiB,YAAY,QAAQ,GAAG,QAAQ;CACtF,MAAM,aAAa,MAAM,cAAc;CACvC,MAAM,WAAW,eAAe,YAAY,aAAa,CAAC,CAAC;CAC3D,MAAM,sBAAsB,EAAE,GAAG,YAAY;CAC7C,KAAK,MAAM,QAAQ;EAAC;EAAa;EAAW;CAAS,GAAY;EAC/D,MAAM,QAAQ,WAAW;EACzB,IAAI,UAAU,KAAA,KAAa,oBAAoB,mBAAmB,WAAW,KAAA,GAC3E,oBAAoB,mBAAmB,SAAS,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,GAAG,IAAI;CAC7F;CACA,MAAM,SAAS,iBACb,MACA,qBACA,KACA,qBAAqB,YAAY,aAAa,CAAC,CAAC,kBAChD,YAAY,YAAY,aAAa,CAAC,CAAC,gBACvC,mBAAmB,YAAY,aAAa,CAAC,CAAC,cAChD;CACA,OAAO;EAAE;EAAU,GAAI,MAAM,eAAe,KAAA,IAAY,CAAC,IAAI,EAAE,WAAW;EAAI,GAAG,OAAO;EAAS;CAAc;AACjH"}
1
+ {"version":3,"file":"harnessOptions.mjs","names":["readHarnessState"],"sources":["../../../src/cli/harnessOptions.ts"],"sourcesContent":["import os from 'node:os';\nimport path from 'node:path';\n\nimport { loadDoomConfig } from '@agimon-ai/doompi-config/config';\nimport { loadDomains } from '@agimon-ai/doompi-config/domains';\nimport { HARNESS_STATE_KEYS } from '@agimon-ai/doompi-config/harnessState';\nimport { loadMajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { loadProfileCatalog } from '@agimon-ai/doompi-config/profiles';\nimport type { HarnessFailureReporter } from '@agimon-ai/doompi-core/runtimeLogSinkTelemetry';\n\nimport { readHarnessState } from '../composition/harnessState';\nimport { findRepositoryRoot } from '../composition/repository';\nimport type { HarnessOptions } from '../composition/types/harness';\nimport { parseHarnessArgs } from './options';\n\nexport interface ResolveHarnessOptionsInput {\n /** Raw launcher arguments, as the binary received them. */\n args: readonly string[];\n environment?: NodeJS.ProcessEnv;\n cwd?: string;\n /** Reads configuration from this root instead of the checkout's own. */\n configRoot?: string;\n /** Reports a malformed inherited harness state without failing the run. */\n report?: HarnessFailureReporter;\n}\n\n/** Uses the nearest configured repository, or the selected directory for an unconfigured workspace. */\nfunction resolveConfigurationRoot(cwd: string): string {\n try {\n return findRepositoryRoot(cwd);\n } catch {\n return path.resolve(cwd);\n }\n}\n\n/**\n * Settles the configuration root and the full option matrix for one run.\n *\n * Two passes are required: the first reads --cwd without touching repository\n * configuration, which keeps a run resolvable even when modes.yaml is\n * malformed, and the second applies the defaults that root supplies. An\n * inherited root wins, which is how a nested run stays pinned to the outer\n * repository rather than re-deriving one from its own cwd. When no configured\n * repository exists, the selected working directory is the configuration root.\n */\nexport function resolveHarnessOptions(input: ResolveHarnessOptionsInput): HarnessOptions {\n const environment = input.environment ?? process.env;\n const cwd = input.cwd ?? process.cwd();\n const args = [...input.args];\n\n const initial = parseHarnessArgs(args, environment, cwd);\n const inheritedRoot = readHarnessState(environment, input.report).root;\n const repoRoot = inheritedRoot ? path.resolve(inheritedRoot) : resolveConfigurationRoot(initial.options.cwd);\n const homeDirectory = initial.options.homeDirectory ?? environment.HOME ?? os.homedir();\n const configRoot = input.configRoot ?? repoRoot;\n const defaults = loadDoomConfig(configRoot, homeDirectory).selection;\n const selectedEnvironment = { ...environment };\n for (const axis of ['majorMode', 'domains', 'profile'] as const) {\n const value = defaults?.[axis];\n if (value !== undefined && selectedEnvironment[HARNESS_STATE_KEYS[axis]] === undefined)\n selectedEnvironment[HARNESS_STATE_KEYS[axis]] = Array.isArray(value) ? value.join(',') : value;\n }\n const parsed = parseHarnessArgs(\n args,\n selectedEnvironment,\n cwd,\n loadMajorModesConfig(configRoot, homeDirectory).defaultMajorMode,\n loadDomains(configRoot, homeDirectory).defaultDomains,\n loadProfileCatalog(configRoot, homeDirectory).defaultProfile,\n );\n return {\n repoRoot,\n ...(input.configRoot === undefined ? {} : { configRoot }),\n ...parsed.options,\n homeDirectory,\n ...(input.environment === undefined ? {} : { environment: input.environment }),\n };\n}\n"],"mappings":";;;;;;;;;;;;AA2BA,SAAS,yBAAyB,KAAqB;CACrD,IAAI;EACF,OAAO,mBAAmB,GAAG;CAC/B,QAAQ;EACN,OAAO,KAAK,QAAQ,GAAG;CACzB;AACF;;;;;;;;;;;AAYA,SAAgB,sBAAsB,OAAmD;CACvF,MAAM,cAAc,MAAM,eAAe,QAAQ;CACjD,MAAM,MAAM,MAAM,OAAO,QAAQ,IAAI;CACrC,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI;CAE3B,MAAM,UAAU,iBAAiB,MAAM,aAAa,GAAG;CACvD,MAAM,gBAAgBA,mBAAiB,aAAa,MAAM,MAAM,CAAC,CAAC;CAClE,MAAM,WAAW,gBAAgB,KAAK,QAAQ,aAAa,IAAI,yBAAyB,QAAQ,QAAQ,GAAG;CAC3G,MAAM,gBAAgB,QAAQ,QAAQ,iBAAiB,YAAY,QAAQ,GAAG,QAAQ;CACtF,MAAM,aAAa,MAAM,cAAc;CACvC,MAAM,WAAW,eAAe,YAAY,aAAa,CAAC,CAAC;CAC3D,MAAM,sBAAsB,EAAE,GAAG,YAAY;CAC7C,KAAK,MAAM,QAAQ;EAAC;EAAa;EAAW;CAAS,GAAY;EAC/D,MAAM,QAAQ,WAAW;EACzB,IAAI,UAAU,KAAA,KAAa,oBAAoB,mBAAmB,WAAW,KAAA,GAC3E,oBAAoB,mBAAmB,SAAS,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,GAAG,IAAI;CAC7F;CACA,MAAM,SAAS,iBACb,MACA,qBACA,KACA,qBAAqB,YAAY,aAAa,CAAC,CAAC,kBAChD,YAAY,YAAY,aAAa,CAAC,CAAC,gBACvC,mBAAmB,YAAY,aAAa,CAAC,CAAC,cAChD;CACA,OAAO;EACL;EACA,GAAI,MAAM,eAAe,KAAA,IAAY,CAAC,IAAI,EAAE,WAAW;EACvD,GAAG,OAAO;EACV;EACA,GAAI,MAAM,gBAAgB,KAAA,IAAY,CAAC,IAAI,EAAE,aAAa,MAAM,YAAY;CAC9E;AACF"}
@@ -12,6 +12,8 @@ export interface HarnessOptions {
12
12
  /** Injected home root for home-scoped generated artifacts. */
13
13
  homeDirectory?: string;
14
14
  cwd: string;
15
+ /** Execution environment supplied by the caller; defaults to process.env. */
16
+ environment?: NodeJS.ProcessEnv;
15
17
  /** One persona and its environment defaults from `.doom/profiles.yaml`. */
16
18
  profile?: string;
17
19
  /** Domain names that select plugins, skills, agents, and MCP scope. */
@@ -1 +1 @@
1
- {"version":3,"file":"harness.d.mts","names":[],"sources":["../../../../src/composition/types/harness.ts"],"mappings":";YAAY;YACA;iBAEK;;EAEf;;;;;EAKA;;EAEA;EACA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;;;;;;EAOA;EACA;EACA;EACA,QAAQ;;EAER,cAAc;;EAEd;EACA;EACA;;EAEA;EACA;EACA;EACA;EACA;EACA;;iBAGe;EACf,SAAS,KAAK;EACd;EACA"}
1
+ {"version":3,"file":"harness.d.mts","names":[],"sources":["../../../../src/composition/types/harness.ts"],"mappings":";YAAY;YACA;iBAEK;;EAEf;;;;;EAKA;;EAEA;EACA;;EAEA,cAAc,OAAO;;EAErB;;EAEA;;EAEA;;EAEA;;EAEA;;;;;;;EAOA;EACA;EACA;EACA,QAAQ;;EAER,cAAc;;EAEd;EACA;EACA;;EAEA;EACA;EACA;EACA;EACA;EACA;;iBAGe;EACf,SAAS,KAAK;EACd;EACA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi",
3
- "version": "0.0.1-alpha.104",
3
+ "version": "0.0.1-alpha.106",
4
4
  "description": "Opinionated, composable Pi distribution for scoped agent tools, skills, MCP servers, and developer workflows.",
5
5
  "keywords": [
6
6
  "ai",
@@ -277,21 +277,21 @@
277
277
  "registry": "https://registry.npmjs.org/"
278
278
  },
279
279
  "dependencies": {
280
- "@agimon-ai/doompi-autostop": "0.0.1-alpha.77",
281
- "@agimon-ai/doompi-cache": "0.0.1-alpha.66",
282
- "@agimon-ai/doompi-config": "0.0.1-alpha.99",
283
- "@agimon-ai/doompi-core": "0.0.1-alpha.100",
284
- "@agimon-ai/doompi-domain": "0.0.1-alpha.78",
285
- "@agimon-ai/doompi-major-mode": "0.0.1-alpha.78",
286
- "@agimon-ai/doompi-minor-mode": "0.0.1-alpha.100",
287
- "@agimon-ai/doompi-notification": "0.0.1-alpha.77",
288
- "@agimon-ai/doompi-profile": "0.0.1-alpha.78",
289
- "@agimon-ai/doompi-session": "0.0.1-alpha.24",
290
- "@agimon-ai/doompi-skill": "0.0.1-alpha.78",
291
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.90",
292
- "@agimon-ai/doompi-ui": "0.0.1-alpha.100",
293
- "@agimon-ai/doompi-web-components": "0.0.1-alpha.58",
294
- "@agimon-ai/doompi-web-security": "0.0.1-alpha.53",
280
+ "@agimon-ai/doompi-autostop": "0.0.1-alpha.79",
281
+ "@agimon-ai/doompi-cache": "0.0.1-alpha.68",
282
+ "@agimon-ai/doompi-config": "0.0.1-alpha.101",
283
+ "@agimon-ai/doompi-core": "0.0.1-alpha.102",
284
+ "@agimon-ai/doompi-domain": "0.0.1-alpha.80",
285
+ "@agimon-ai/doompi-major-mode": "0.0.1-alpha.80",
286
+ "@agimon-ai/doompi-minor-mode": "0.0.1-alpha.102",
287
+ "@agimon-ai/doompi-notification": "0.0.1-alpha.79",
288
+ "@agimon-ai/doompi-profile": "0.0.1-alpha.80",
289
+ "@agimon-ai/doompi-session": "0.0.1-alpha.26",
290
+ "@agimon-ai/doompi-skill": "0.0.1-alpha.80",
291
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.92",
292
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.102",
293
+ "@agimon-ai/doompi-web-components": "0.0.1-alpha.60",
294
+ "@agimon-ai/doompi-web-security": "0.0.1-alpha.55",
295
295
  "@agimon-ai/log-sink-mcp": "0.29.35",
296
296
  "@deepseek-ai/cordis": "4.0.2",
297
297
  "@earendil-works/chord": "0.99.1",
@@ -317,8 +317,8 @@
317
317
  "yaml": "2.9.1"
318
318
  },
319
319
  "devDependencies": {
320
- "@agimon-ai/doompi-runner": "0.0.1-alpha.101",
321
- "@agimon-ai/vibe-lint-plugin-doom-cli": "0.0.1-alpha.25",
320
+ "@agimon-ai/doompi-runner": "0.0.1-alpha.103",
321
+ "@agimon-ai/vibe-lint-plugin-doom-cli": "0.0.1-alpha.27",
322
322
  "@earendil-works/pi-ai": "0.99.1",
323
323
  "@earendil-works/pi-client": "0.99.1",
324
324
  "@earendil-works/pi-tui": "0.99.1",