@agimon-ai/doompi 0.0.1-alpha.110 → 0.0.1-alpha.111

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 (70) hide show
  1. package/dist/builders/cli/index.cjs +1 -1
  2. package/dist/builders/cli/index.cjs.map +1 -1
  3. package/dist/builders/cli/index.mjs +1 -1
  4. package/dist/builders/cli/index.mjs.map +1 -1
  5. package/dist/src/builders/cli/composition.cjs +3 -3
  6. package/dist/src/builders/cli/composition.cjs.map +1 -1
  7. package/dist/src/builders/cli/composition.mjs +3 -3
  8. package/dist/src/builders/cli/composition.mjs.map +1 -1
  9. package/dist/src/builders/cli/harnessContext.cjs +1 -1
  10. package/dist/src/builders/cli/harnessContext.cjs.map +1 -1
  11. package/dist/src/builders/cli/harnessContext.mjs +1 -1
  12. package/dist/src/builders/cli/harnessContext.mjs.map +1 -1
  13. package/dist/src/builders/cli/prepare.cjs +5 -4
  14. package/dist/src/builders/cli/prepare.cjs.map +1 -1
  15. package/dist/src/builders/cli/prepare.mjs +5 -4
  16. package/dist/src/builders/cli/prepare.mjs.map +1 -1
  17. package/dist/src/builders/server/computerUseBinding.cjs +120 -13
  18. package/dist/src/builders/server/computerUseBinding.cjs.map +1 -1
  19. package/dist/src/builders/server/computerUseBinding.mjs +116 -13
  20. package/dist/src/builders/server/computerUseBinding.mjs.map +1 -1
  21. package/dist/src/builders/server/computerUseRecordings.cjs +188 -0
  22. package/dist/src/builders/server/computerUseRecordings.cjs.map +1 -0
  23. package/dist/src/builders/server/computerUseRecordings.mjs +184 -0
  24. package/dist/src/builders/server/computerUseRecordings.mjs.map +1 -0
  25. package/dist/src/builders/server/runtime.cjs +27 -9
  26. package/dist/src/builders/server/runtime.cjs.map +1 -1
  27. package/dist/src/builders/server/runtime.mjs +27 -9
  28. package/dist/src/builders/server/runtime.mjs.map +1 -1
  29. package/dist/src/cli/commands/compat/providers/process.mjs +2 -2
  30. package/dist/src/cli/commands/compat/providers/process.mjs.map +1 -1
  31. package/dist/src/cli/commands/sync/index.cjs +7 -4
  32. package/dist/src/cli/commands/sync/index.cjs.map +1 -1
  33. package/dist/src/cli/commands/sync/index.d.mts.map +1 -1
  34. package/dist/src/cli/commands/sync/index.mjs +7 -4
  35. package/dist/src/cli/commands/sync/index.mjs.map +1 -1
  36. package/dist/src/cli/commands/sync/inspection.cjs +5 -4
  37. package/dist/src/cli/commands/sync/inspection.cjs.map +1 -1
  38. package/dist/src/cli/commands/sync/inspection.d.mts +1 -1
  39. package/dist/src/cli/commands/sync/inspection.d.mts.map +1 -1
  40. package/dist/src/cli/commands/sync/inspection.mjs +5 -4
  41. package/dist/src/cli/commands/sync/inspection.mjs.map +1 -1
  42. package/dist/src/cli/commands/sync/prepare.cjs +1 -1
  43. package/dist/src/cli/commands/sync/prepare.cjs.map +1 -1
  44. package/dist/src/cli/commands/sync/prepare.mjs +1 -1
  45. package/dist/src/cli/commands/sync/prepare.mjs.map +1 -1
  46. package/dist/src/cli/commands/sync/workflow.cjs +2 -1
  47. package/dist/src/cli/commands/sync/workflow.cjs.map +1 -1
  48. package/dist/src/cli/commands/sync/workflow.mjs +2 -1
  49. package/dist/src/cli/commands/sync/workflow.mjs.map +1 -1
  50. package/dist/src/cli/harnessOptions.cjs +2 -2
  51. package/dist/src/cli/harnessOptions.cjs.map +1 -1
  52. package/dist/src/cli/harnessOptions.mjs +2 -2
  53. package/dist/src/cli/harnessOptions.mjs.map +1 -1
  54. package/dist/src/cli/server/run.cjs +17 -9
  55. package/dist/src/cli/server/run.cjs.map +1 -1
  56. package/dist/src/cli/server/run.mjs +17 -9
  57. package/dist/src/cli/server/run.mjs.map +1 -1
  58. package/dist/src/composition/syncDrift/index.cjs +1 -1
  59. package/dist/src/composition/syncDrift/index.cjs.map +1 -1
  60. package/dist/src/composition/syncDrift/index.d.mts +1 -0
  61. package/dist/src/composition/syncDrift/index.d.mts.map +1 -1
  62. package/dist/src/composition/syncDrift/index.mjs +1 -1
  63. package/dist/src/composition/syncDrift/index.mjs.map +1 -1
  64. package/dist/src/composition/syncState/index.cjs +2 -1
  65. package/dist/src/composition/syncState/index.cjs.map +1 -1
  66. package/dist/src/composition/syncState/index.d.mts +1 -1
  67. package/dist/src/composition/syncState/index.d.mts.map +1 -1
  68. package/dist/src/composition/syncState/index.mjs +2 -1
  69. package/dist/src/composition/syncState/index.mjs.map +1 -1
  70. package/package.json +18 -18
@@ -43,7 +43,7 @@ async function buildSyncedRuntime(repoRoot, environment = process.env, homeDirec
43
43
  if (!state) throw new Error(`DoomPi is not synchronized at ${repoRoot}. Run doompi sync.`);
44
44
  const runtimeEnvironment = mergedEnvironment(state.env, environment);
45
45
  const harness = (0, _agimon_ai_doompi_config_harnessState.readHarnessState)(runtimeEnvironment);
46
- const majorModesConfig = (0, _agimon_ai_doompi_config_majorModes.loadMajorModesConfig)(repoRoot, homeDirectory);
46
+ const majorModesConfig = (0, _agimon_ai_doompi_config_majorModes.loadMajorModesConfig)(repoRoot, homeDirectory, environment);
47
47
  const resolvers = require_index.createMapResolvers(state.resolved);
48
48
  const base = {
49
49
  agents: harness.agents,
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["os","readSyncState","readHarnessState","loadMajorModesConfig","createMapResolvers","PERSONA_ENTRY","resolveSyncLocation","syncDirectory","path","filterHookDisabledLayers","resolveExtensionComposition","compileModeExtension","ownEntry","compileExtensionSet","extensionSetManifestPath","PRECOMPILE_STATE_VERSION","BUNDLED_PRECOMPILE_STRATEGY","writeSyncState"],"sources":["../../../src/builders/cli/index.ts"],"sourcesContent":["import os from 'node:os';\nimport path from 'node:path';\n\nimport { readHarnessState } from '@agimon-ai/doompi-config/harnessState';\nimport { filterHookDisabledLayers, loadMajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { resolveSyncLocation } from '@agimon-ai/doompi-core/syncLocation';\nimport { BUNDLED_PRECOMPILE_STRATEGY, PRECOMPILE_STATE_VERSION } from '@agimon-ai/doompi-core/syncStateContract';\n\nimport { compileExtensionSet, extensionSetManifestPath } from '../../compiler';\nimport {\n createMapResolvers,\n readSyncState,\n type SyncState,\n syncDirectory,\n writeSyncState,\n} from '../../composition/syncState';\nimport { ownEntry } from './entryResolution';\nimport { PERSONA_ENTRY, resolveExtensionComposition, type ExtensionComposition } from './extensionAssembler';\nimport { compileModeExtension, type ModeBuildSelection } from './runtimeBundle';\n\nconst EXTENSION_CACHE_DIRECTORY = 'cache';\nconst MODE_DIST_DIRECTORY = 'dist';\nconst COMPOSED_PI_ENTRY = 'composedPi';\n\nexport interface SyncedRuntimeBuild {\n bootstrap: string;\n bundles: Record<string, string>;\n bundleManifests: Record<string, string>;\n state: SyncState;\n compositions: ExtensionComposition[];\n}\n\nexport interface SyncedRuntimeBuildOptions {\n /** In-memory state being staged into a not-yet-published generation. */\n state?: SyncState;\n /** Generation root for cache and compiled outputs. */\n directory?: string;\n /** Starts dependent target builds as soon as composition planning finishes. */\n onCompositionsResolved?: (compositions: readonly ExtensionComposition[]) => void;\n}\n\nfunction mergedEnvironment(recorded: Record<string, string>, current: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n const merged: NodeJS.ProcessEnv = { ...recorded };\n for (const [key, value] of Object.entries(current)) {\n if (value !== undefined) merged[key] = value;\n }\n return merged;\n}\n\nfunction selectionFor(\n state: SyncState,\n harness: ReturnType<typeof readHarnessState>,\n majorMode: string,\n mute: boolean,\n): ModeBuildSelection {\n return {\n majorMode,\n domains: [...state.selection.domains],\n profile: state.selection.profile,\n preset: state.selection.preset,\n mute,\n autoStop: false,\n agents: harness.agents,\n mcp: harness.mcp,\n };\n}\n\nfunction compilationStateChanged(previous: SyncState, current: SyncState): boolean {\n return (\n previous.inputsHash !== current.inputsHash ||\n JSON.stringify(previous.selection) !== JSON.stringify(current.selection) ||\n JSON.stringify(previous.resolved) !== JSON.stringify(current.resolved)\n );\n}\n\n/** Heavy graph bundling reserved for the private build phase of `doompi sync`. */\nexport async function buildSyncedRuntime(\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n homeDirectory: string = environment.HOME ?? os.homedir(),\n options: SyncedRuntimeBuildOptions = {},\n): Promise<SyncedRuntimeBuild> {\n const state = options.state ?? readSyncState(repoRoot, homeDirectory);\n if (!state) throw new Error(`DoomPi is not synchronized at ${repoRoot}. Run doompi sync.`);\n\n const runtimeEnvironment = mergedEnvironment(state.env, environment);\n const harness = readHarnessState(runtimeEnvironment);\n const majorModesConfig = loadMajorModesConfig(repoRoot, homeDirectory);\n const resolvers = createMapResolvers(state.resolved);\n const base = {\n agents: harness.agents,\n autoStop: false,\n preset: state.selection.preset,\n personaEntry: resolvers.packageEntry(PERSONA_ENTRY),\n majorModesConfig,\n resolvers,\n };\n\n const location = resolveSyncLocation(repoRoot, homeDirectory);\n const directory = options.directory ?? syncDirectory(repoRoot, homeDirectory);\n const cacheDirectory = path.join(directory, EXTENSION_CACHE_DIRECTORY);\n const outputDirectory = path.join(directory, MODE_DIST_DIRECTORY);\n const bundles: Record<string, string> = {};\n const bundleManifests: Record<string, string> = {};\n const compositions: ExtensionComposition[] = [];\n const builds: {\n composition: ExtensionComposition;\n majorMode: string;\n mute: boolean;\n options: Parameters<typeof compileModeExtension>[0];\n }[] = [];\n for (const [majorMode, definition] of Object.entries(majorModesConfig.majorMode)) {\n const selectedLayers = filterHookDisabledLayers(majorModesConfig, definition.layers, harness.hooks);\n for (const mute of [false, true]) {\n const composition = resolveExtensionComposition({\n ...base,\n majorMode,\n layers: selectedLayers,\n mute,\n });\n compositions.push(composition);\n const extensionPaths = [...composition.parentActivation];\n const childExtensionPaths = [...composition.childActivation];\n const outputName = `${majorMode}${mute ? '.mute' : ''}`;\n builds.push({\n composition,\n majorMode,\n mute,\n options: {\n repoRoot,\n selection: selectionFor(state, harness, majorMode, mute),\n extensionPaths,\n childExtensionPaths,\n compositionFingerprint: composition.fingerprint,\n skillPaths: harness.skillDirectories,\n pluginRoots: harness.pluginDirectories,\n outputName,\n cacheDirectory,\n outputDirectory,\n sharedCacheDirectory: location.sharedCacheDirectory,\n },\n });\n }\n }\n options.onCompositionsResolved?.(compositions);\n const results = await Promise.allSettled(builds.map(({ options }) => compileModeExtension(options)));\n for (const [index, result] of results.entries()) {\n const { composition, majorMode, mute } = builds[index]!;\n if (result.status === 'fulfilled') {\n bundles[composition.fingerprint] = result.value.bundle;\n bundleManifests[composition.fingerprint] = result.value.compilerManifest;\n } else {\n const detail = result.reason instanceof Error ? result.reason.message : String(result.reason);\n process.stderr.write(`[doompi] could not precompile ${majorMode}${mute ? ' (mute)' : ''}: ${detail}\\n`);\n }\n }\n\n if (!bundles[state.compositionFingerprint]) {\n throw new Error('The synchronized active composition did not produce a bundle. Run doompi sync.');\n }\n\n const bootstrapEntries = [ownEntry(COMPOSED_PI_ENTRY)];\n const bootstrapOptions = { outputDirectory, outputName: 'bootstrap' };\n const bootstrap = await compileExtensionSet(bootstrapEntries, cacheDirectory, {\n ...bootstrapOptions,\n repositoryRoot: repoRoot,\n sharedCacheDirectory: location.sharedCacheDirectory,\n });\n const bootstrapManifest = extensionSetManifestPath(bootstrapEntries, cacheDirectory, bootstrapOptions);\n\n const nextState: SyncState = {\n ...state,\n compiled: undefined,\n bundles,\n bootstrap,\n precompile: {\n version: PRECOMPILE_STATE_VERSION,\n strategy: BUNDLED_PRECOMPILE_STRATEGY,\n bootstrapEntry: bootstrapEntries[0],\n bootstrapManifest,\n bundleManifests,\n },\n };\n if (options.state === undefined) {\n const current = readSyncState(repoRoot, homeDirectory);\n if (!current) throw new Error(`DoomPi synchronization disappeared while compiling ${repoRoot}`);\n if (compilationStateChanged(state, current)) {\n throw new Error(`DoomPi synchronization changed while compiling ${repoRoot}; restart Pi to use the new state.`);\n }\n await writeSyncState(repoRoot, nextState, homeDirectory);\n }\n return {\n bootstrap,\n bundles,\n bundleManifests,\n state: nextState,\n compositions,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["os","readSyncState","readHarnessState","loadMajorModesConfig","createMapResolvers","PERSONA_ENTRY","resolveSyncLocation","syncDirectory","path","filterHookDisabledLayers","resolveExtensionComposition","compileModeExtension","ownEntry","compileExtensionSet","extensionSetManifestPath","PRECOMPILE_STATE_VERSION","BUNDLED_PRECOMPILE_STRATEGY","writeSyncState"],"sources":["../../../src/builders/cli/index.ts"],"sourcesContent":["import os from 'node:os';\nimport path from 'node:path';\n\nimport { readHarnessState } from '@agimon-ai/doompi-config/harnessState';\nimport { filterHookDisabledLayers, loadMajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { resolveSyncLocation } from '@agimon-ai/doompi-core/syncLocation';\nimport { BUNDLED_PRECOMPILE_STRATEGY, PRECOMPILE_STATE_VERSION } from '@agimon-ai/doompi-core/syncStateContract';\n\nimport { compileExtensionSet, extensionSetManifestPath } from '../../compiler';\nimport {\n createMapResolvers,\n readSyncState,\n type SyncState,\n syncDirectory,\n writeSyncState,\n} from '../../composition/syncState';\nimport { ownEntry } from './entryResolution';\nimport { PERSONA_ENTRY, resolveExtensionComposition, type ExtensionComposition } from './extensionAssembler';\nimport { compileModeExtension, type ModeBuildSelection } from './runtimeBundle';\n\nconst EXTENSION_CACHE_DIRECTORY = 'cache';\nconst MODE_DIST_DIRECTORY = 'dist';\nconst COMPOSED_PI_ENTRY = 'composedPi';\n\nexport interface SyncedRuntimeBuild {\n bootstrap: string;\n bundles: Record<string, string>;\n bundleManifests: Record<string, string>;\n state: SyncState;\n compositions: ExtensionComposition[];\n}\n\nexport interface SyncedRuntimeBuildOptions {\n /** In-memory state being staged into a not-yet-published generation. */\n state?: SyncState;\n /** Generation root for cache and compiled outputs. */\n directory?: string;\n /** Starts dependent target builds as soon as composition planning finishes. */\n onCompositionsResolved?: (compositions: readonly ExtensionComposition[]) => void;\n}\n\nfunction mergedEnvironment(recorded: Record<string, string>, current: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n const merged: NodeJS.ProcessEnv = { ...recorded };\n for (const [key, value] of Object.entries(current)) {\n if (value !== undefined) merged[key] = value;\n }\n return merged;\n}\n\nfunction selectionFor(\n state: SyncState,\n harness: ReturnType<typeof readHarnessState>,\n majorMode: string,\n mute: boolean,\n): ModeBuildSelection {\n return {\n majorMode,\n domains: [...state.selection.domains],\n profile: state.selection.profile,\n preset: state.selection.preset,\n mute,\n autoStop: false,\n agents: harness.agents,\n mcp: harness.mcp,\n };\n}\n\nfunction compilationStateChanged(previous: SyncState, current: SyncState): boolean {\n return (\n previous.inputsHash !== current.inputsHash ||\n JSON.stringify(previous.selection) !== JSON.stringify(current.selection) ||\n JSON.stringify(previous.resolved) !== JSON.stringify(current.resolved)\n );\n}\n\n/** Heavy graph bundling reserved for the private build phase of `doompi sync`. */\nexport async function buildSyncedRuntime(\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n homeDirectory: string = environment.HOME ?? os.homedir(),\n options: SyncedRuntimeBuildOptions = {},\n): Promise<SyncedRuntimeBuild> {\n const state = options.state ?? readSyncState(repoRoot, homeDirectory);\n if (!state) throw new Error(`DoomPi is not synchronized at ${repoRoot}. Run doompi sync.`);\n\n const runtimeEnvironment = mergedEnvironment(state.env, environment);\n const harness = readHarnessState(runtimeEnvironment);\n const majorModesConfig = loadMajorModesConfig(repoRoot, homeDirectory, environment);\n const resolvers = createMapResolvers(state.resolved);\n const base = {\n agents: harness.agents,\n autoStop: false,\n preset: state.selection.preset,\n personaEntry: resolvers.packageEntry(PERSONA_ENTRY),\n majorModesConfig,\n resolvers,\n };\n\n const location = resolveSyncLocation(repoRoot, homeDirectory);\n const directory = options.directory ?? syncDirectory(repoRoot, homeDirectory);\n const cacheDirectory = path.join(directory, EXTENSION_CACHE_DIRECTORY);\n const outputDirectory = path.join(directory, MODE_DIST_DIRECTORY);\n const bundles: Record<string, string> = {};\n const bundleManifests: Record<string, string> = {};\n const compositions: ExtensionComposition[] = [];\n const builds: {\n composition: ExtensionComposition;\n majorMode: string;\n mute: boolean;\n options: Parameters<typeof compileModeExtension>[0];\n }[] = [];\n for (const [majorMode, definition] of Object.entries(majorModesConfig.majorMode)) {\n const selectedLayers = filterHookDisabledLayers(majorModesConfig, definition.layers, harness.hooks);\n for (const mute of [false, true]) {\n const composition = resolveExtensionComposition({\n ...base,\n majorMode,\n layers: selectedLayers,\n mute,\n });\n compositions.push(composition);\n const extensionPaths = [...composition.parentActivation];\n const childExtensionPaths = [...composition.childActivation];\n const outputName = `${majorMode}${mute ? '.mute' : ''}`;\n builds.push({\n composition,\n majorMode,\n mute,\n options: {\n repoRoot,\n selection: selectionFor(state, harness, majorMode, mute),\n extensionPaths,\n childExtensionPaths,\n compositionFingerprint: composition.fingerprint,\n skillPaths: harness.skillDirectories,\n pluginRoots: harness.pluginDirectories,\n outputName,\n cacheDirectory,\n outputDirectory,\n sharedCacheDirectory: location.sharedCacheDirectory,\n },\n });\n }\n }\n options.onCompositionsResolved?.(compositions);\n const results = await Promise.allSettled(builds.map(({ options }) => compileModeExtension(options)));\n for (const [index, result] of results.entries()) {\n const { composition, majorMode, mute } = builds[index]!;\n if (result.status === 'fulfilled') {\n bundles[composition.fingerprint] = result.value.bundle;\n bundleManifests[composition.fingerprint] = result.value.compilerManifest;\n } else {\n const detail = result.reason instanceof Error ? result.reason.message : String(result.reason);\n process.stderr.write(`[doompi] could not precompile ${majorMode}${mute ? ' (mute)' : ''}: ${detail}\\n`);\n }\n }\n\n if (!bundles[state.compositionFingerprint]) {\n throw new Error('The synchronized active composition did not produce a bundle. Run doompi sync.');\n }\n\n const bootstrapEntries = [ownEntry(COMPOSED_PI_ENTRY)];\n const bootstrapOptions = { outputDirectory, outputName: 'bootstrap' };\n const bootstrap = await compileExtensionSet(bootstrapEntries, cacheDirectory, {\n ...bootstrapOptions,\n repositoryRoot: repoRoot,\n sharedCacheDirectory: location.sharedCacheDirectory,\n });\n const bootstrapManifest = extensionSetManifestPath(bootstrapEntries, cacheDirectory, bootstrapOptions);\n\n const nextState: SyncState = {\n ...state,\n compiled: undefined,\n bundles,\n bootstrap,\n precompile: {\n version: PRECOMPILE_STATE_VERSION,\n strategy: BUNDLED_PRECOMPILE_STRATEGY,\n bootstrapEntry: bootstrapEntries[0],\n bootstrapManifest,\n bundleManifests,\n },\n };\n if (options.state === undefined) {\n const current = readSyncState(repoRoot, homeDirectory);\n if (!current) throw new Error(`DoomPi synchronization disappeared while compiling ${repoRoot}`);\n if (compilationStateChanged(state, current)) {\n throw new Error(`DoomPi synchronization changed while compiling ${repoRoot}; restart Pi to use the new state.`);\n }\n await writeSyncState(repoRoot, nextState, homeDirectory);\n }\n return {\n bootstrap,\n bundles,\n bundleManifests,\n state: nextState,\n compositions,\n };\n}\n"],"mappings":"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"}
@@ -39,7 +39,7 @@ async function buildSyncedRuntime(repoRoot, environment = process.env, homeDirec
39
39
  if (!state) throw new Error(`DoomPi is not synchronized at ${repoRoot}. Run doompi sync.`);
40
40
  const runtimeEnvironment = mergedEnvironment(state.env, environment);
41
41
  const harness = readHarnessState(runtimeEnvironment);
42
- const majorModesConfig = loadMajorModesConfig(repoRoot, homeDirectory);
42
+ const majorModesConfig = loadMajorModesConfig(repoRoot, homeDirectory, environment);
43
43
  const resolvers = createMapResolvers(state.resolved);
44
44
  const base = {
45
45
  agents: harness.agents,
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/builders/cli/index.ts"],"sourcesContent":["import os from 'node:os';\nimport path from 'node:path';\n\nimport { readHarnessState } from '@agimon-ai/doompi-config/harnessState';\nimport { filterHookDisabledLayers, loadMajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { resolveSyncLocation } from '@agimon-ai/doompi-core/syncLocation';\nimport { BUNDLED_PRECOMPILE_STRATEGY, PRECOMPILE_STATE_VERSION } from '@agimon-ai/doompi-core/syncStateContract';\n\nimport { compileExtensionSet, extensionSetManifestPath } from '../../compiler';\nimport {\n createMapResolvers,\n readSyncState,\n type SyncState,\n syncDirectory,\n writeSyncState,\n} from '../../composition/syncState';\nimport { ownEntry } from './entryResolution';\nimport { PERSONA_ENTRY, resolveExtensionComposition, type ExtensionComposition } from './extensionAssembler';\nimport { compileModeExtension, type ModeBuildSelection } from './runtimeBundle';\n\nconst EXTENSION_CACHE_DIRECTORY = 'cache';\nconst MODE_DIST_DIRECTORY = 'dist';\nconst COMPOSED_PI_ENTRY = 'composedPi';\n\nexport interface SyncedRuntimeBuild {\n bootstrap: string;\n bundles: Record<string, string>;\n bundleManifests: Record<string, string>;\n state: SyncState;\n compositions: ExtensionComposition[];\n}\n\nexport interface SyncedRuntimeBuildOptions {\n /** In-memory state being staged into a not-yet-published generation. */\n state?: SyncState;\n /** Generation root for cache and compiled outputs. */\n directory?: string;\n /** Starts dependent target builds as soon as composition planning finishes. */\n onCompositionsResolved?: (compositions: readonly ExtensionComposition[]) => void;\n}\n\nfunction mergedEnvironment(recorded: Record<string, string>, current: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n const merged: NodeJS.ProcessEnv = { ...recorded };\n for (const [key, value] of Object.entries(current)) {\n if (value !== undefined) merged[key] = value;\n }\n return merged;\n}\n\nfunction selectionFor(\n state: SyncState,\n harness: ReturnType<typeof readHarnessState>,\n majorMode: string,\n mute: boolean,\n): ModeBuildSelection {\n return {\n majorMode,\n domains: [...state.selection.domains],\n profile: state.selection.profile,\n preset: state.selection.preset,\n mute,\n autoStop: false,\n agents: harness.agents,\n mcp: harness.mcp,\n };\n}\n\nfunction compilationStateChanged(previous: SyncState, current: SyncState): boolean {\n return (\n previous.inputsHash !== current.inputsHash ||\n JSON.stringify(previous.selection) !== JSON.stringify(current.selection) ||\n JSON.stringify(previous.resolved) !== JSON.stringify(current.resolved)\n );\n}\n\n/** Heavy graph bundling reserved for the private build phase of `doompi sync`. */\nexport async function buildSyncedRuntime(\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n homeDirectory: string = environment.HOME ?? os.homedir(),\n options: SyncedRuntimeBuildOptions = {},\n): Promise<SyncedRuntimeBuild> {\n const state = options.state ?? readSyncState(repoRoot, homeDirectory);\n if (!state) throw new Error(`DoomPi is not synchronized at ${repoRoot}. Run doompi sync.`);\n\n const runtimeEnvironment = mergedEnvironment(state.env, environment);\n const harness = readHarnessState(runtimeEnvironment);\n const majorModesConfig = loadMajorModesConfig(repoRoot, homeDirectory);\n const resolvers = createMapResolvers(state.resolved);\n const base = {\n agents: harness.agents,\n autoStop: false,\n preset: state.selection.preset,\n personaEntry: resolvers.packageEntry(PERSONA_ENTRY),\n majorModesConfig,\n resolvers,\n };\n\n const location = resolveSyncLocation(repoRoot, homeDirectory);\n const directory = options.directory ?? syncDirectory(repoRoot, homeDirectory);\n const cacheDirectory = path.join(directory, EXTENSION_CACHE_DIRECTORY);\n const outputDirectory = path.join(directory, MODE_DIST_DIRECTORY);\n const bundles: Record<string, string> = {};\n const bundleManifests: Record<string, string> = {};\n const compositions: ExtensionComposition[] = [];\n const builds: {\n composition: ExtensionComposition;\n majorMode: string;\n mute: boolean;\n options: Parameters<typeof compileModeExtension>[0];\n }[] = [];\n for (const [majorMode, definition] of Object.entries(majorModesConfig.majorMode)) {\n const selectedLayers = filterHookDisabledLayers(majorModesConfig, definition.layers, harness.hooks);\n for (const mute of [false, true]) {\n const composition = resolveExtensionComposition({\n ...base,\n majorMode,\n layers: selectedLayers,\n mute,\n });\n compositions.push(composition);\n const extensionPaths = [...composition.parentActivation];\n const childExtensionPaths = [...composition.childActivation];\n const outputName = `${majorMode}${mute ? '.mute' : ''}`;\n builds.push({\n composition,\n majorMode,\n mute,\n options: {\n repoRoot,\n selection: selectionFor(state, harness, majorMode, mute),\n extensionPaths,\n childExtensionPaths,\n compositionFingerprint: composition.fingerprint,\n skillPaths: harness.skillDirectories,\n pluginRoots: harness.pluginDirectories,\n outputName,\n cacheDirectory,\n outputDirectory,\n sharedCacheDirectory: location.sharedCacheDirectory,\n },\n });\n }\n }\n options.onCompositionsResolved?.(compositions);\n const results = await Promise.allSettled(builds.map(({ options }) => compileModeExtension(options)));\n for (const [index, result] of results.entries()) {\n const { composition, majorMode, mute } = builds[index]!;\n if (result.status === 'fulfilled') {\n bundles[composition.fingerprint] = result.value.bundle;\n bundleManifests[composition.fingerprint] = result.value.compilerManifest;\n } else {\n const detail = result.reason instanceof Error ? result.reason.message : String(result.reason);\n process.stderr.write(`[doompi] could not precompile ${majorMode}${mute ? ' (mute)' : ''}: ${detail}\\n`);\n }\n }\n\n if (!bundles[state.compositionFingerprint]) {\n throw new Error('The synchronized active composition did not produce a bundle. Run doompi sync.');\n }\n\n const bootstrapEntries = [ownEntry(COMPOSED_PI_ENTRY)];\n const bootstrapOptions = { outputDirectory, outputName: 'bootstrap' };\n const bootstrap = await compileExtensionSet(bootstrapEntries, cacheDirectory, {\n ...bootstrapOptions,\n repositoryRoot: repoRoot,\n sharedCacheDirectory: location.sharedCacheDirectory,\n });\n const bootstrapManifest = extensionSetManifestPath(bootstrapEntries, cacheDirectory, bootstrapOptions);\n\n const nextState: SyncState = {\n ...state,\n compiled: undefined,\n bundles,\n bootstrap,\n precompile: {\n version: PRECOMPILE_STATE_VERSION,\n strategy: BUNDLED_PRECOMPILE_STRATEGY,\n bootstrapEntry: bootstrapEntries[0],\n bootstrapManifest,\n bundleManifests,\n },\n };\n if (options.state === undefined) {\n const current = readSyncState(repoRoot, homeDirectory);\n if (!current) throw new Error(`DoomPi synchronization disappeared while compiling ${repoRoot}`);\n if (compilationStateChanged(state, current)) {\n throw new Error(`DoomPi synchronization changed while compiling ${repoRoot}; restart Pi to use the new state.`);\n }\n await writeSyncState(repoRoot, nextState, homeDirectory);\n }\n return {\n bootstrap,\n bundles,\n bundleManifests,\n state: nextState,\n compositions,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/builders/cli/index.ts"],"sourcesContent":["import os from 'node:os';\nimport path from 'node:path';\n\nimport { readHarnessState } from '@agimon-ai/doompi-config/harnessState';\nimport { filterHookDisabledLayers, loadMajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { resolveSyncLocation } from '@agimon-ai/doompi-core/syncLocation';\nimport { BUNDLED_PRECOMPILE_STRATEGY, PRECOMPILE_STATE_VERSION } from '@agimon-ai/doompi-core/syncStateContract';\n\nimport { compileExtensionSet, extensionSetManifestPath } from '../../compiler';\nimport {\n createMapResolvers,\n readSyncState,\n type SyncState,\n syncDirectory,\n writeSyncState,\n} from '../../composition/syncState';\nimport { ownEntry } from './entryResolution';\nimport { PERSONA_ENTRY, resolveExtensionComposition, type ExtensionComposition } from './extensionAssembler';\nimport { compileModeExtension, type ModeBuildSelection } from './runtimeBundle';\n\nconst EXTENSION_CACHE_DIRECTORY = 'cache';\nconst MODE_DIST_DIRECTORY = 'dist';\nconst COMPOSED_PI_ENTRY = 'composedPi';\n\nexport interface SyncedRuntimeBuild {\n bootstrap: string;\n bundles: Record<string, string>;\n bundleManifests: Record<string, string>;\n state: SyncState;\n compositions: ExtensionComposition[];\n}\n\nexport interface SyncedRuntimeBuildOptions {\n /** In-memory state being staged into a not-yet-published generation. */\n state?: SyncState;\n /** Generation root for cache and compiled outputs. */\n directory?: string;\n /** Starts dependent target builds as soon as composition planning finishes. */\n onCompositionsResolved?: (compositions: readonly ExtensionComposition[]) => void;\n}\n\nfunction mergedEnvironment(recorded: Record<string, string>, current: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n const merged: NodeJS.ProcessEnv = { ...recorded };\n for (const [key, value] of Object.entries(current)) {\n if (value !== undefined) merged[key] = value;\n }\n return merged;\n}\n\nfunction selectionFor(\n state: SyncState,\n harness: ReturnType<typeof readHarnessState>,\n majorMode: string,\n mute: boolean,\n): ModeBuildSelection {\n return {\n majorMode,\n domains: [...state.selection.domains],\n profile: state.selection.profile,\n preset: state.selection.preset,\n mute,\n autoStop: false,\n agents: harness.agents,\n mcp: harness.mcp,\n };\n}\n\nfunction compilationStateChanged(previous: SyncState, current: SyncState): boolean {\n return (\n previous.inputsHash !== current.inputsHash ||\n JSON.stringify(previous.selection) !== JSON.stringify(current.selection) ||\n JSON.stringify(previous.resolved) !== JSON.stringify(current.resolved)\n );\n}\n\n/** Heavy graph bundling reserved for the private build phase of `doompi sync`. */\nexport async function buildSyncedRuntime(\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n homeDirectory: string = environment.HOME ?? os.homedir(),\n options: SyncedRuntimeBuildOptions = {},\n): Promise<SyncedRuntimeBuild> {\n const state = options.state ?? readSyncState(repoRoot, homeDirectory);\n if (!state) throw new Error(`DoomPi is not synchronized at ${repoRoot}. Run doompi sync.`);\n\n const runtimeEnvironment = mergedEnvironment(state.env, environment);\n const harness = readHarnessState(runtimeEnvironment);\n const majorModesConfig = loadMajorModesConfig(repoRoot, homeDirectory, environment);\n const resolvers = createMapResolvers(state.resolved);\n const base = {\n agents: harness.agents,\n autoStop: false,\n preset: state.selection.preset,\n personaEntry: resolvers.packageEntry(PERSONA_ENTRY),\n majorModesConfig,\n resolvers,\n };\n\n const location = resolveSyncLocation(repoRoot, homeDirectory);\n const directory = options.directory ?? syncDirectory(repoRoot, homeDirectory);\n const cacheDirectory = path.join(directory, EXTENSION_CACHE_DIRECTORY);\n const outputDirectory = path.join(directory, MODE_DIST_DIRECTORY);\n const bundles: Record<string, string> = {};\n const bundleManifests: Record<string, string> = {};\n const compositions: ExtensionComposition[] = [];\n const builds: {\n composition: ExtensionComposition;\n majorMode: string;\n mute: boolean;\n options: Parameters<typeof compileModeExtension>[0];\n }[] = [];\n for (const [majorMode, definition] of Object.entries(majorModesConfig.majorMode)) {\n const selectedLayers = filterHookDisabledLayers(majorModesConfig, definition.layers, harness.hooks);\n for (const mute of [false, true]) {\n const composition = resolveExtensionComposition({\n ...base,\n majorMode,\n layers: selectedLayers,\n mute,\n });\n compositions.push(composition);\n const extensionPaths = [...composition.parentActivation];\n const childExtensionPaths = [...composition.childActivation];\n const outputName = `${majorMode}${mute ? '.mute' : ''}`;\n builds.push({\n composition,\n majorMode,\n mute,\n options: {\n repoRoot,\n selection: selectionFor(state, harness, majorMode, mute),\n extensionPaths,\n childExtensionPaths,\n compositionFingerprint: composition.fingerprint,\n skillPaths: harness.skillDirectories,\n pluginRoots: harness.pluginDirectories,\n outputName,\n cacheDirectory,\n outputDirectory,\n sharedCacheDirectory: location.sharedCacheDirectory,\n },\n });\n }\n }\n options.onCompositionsResolved?.(compositions);\n const results = await Promise.allSettled(builds.map(({ options }) => compileModeExtension(options)));\n for (const [index, result] of results.entries()) {\n const { composition, majorMode, mute } = builds[index]!;\n if (result.status === 'fulfilled') {\n bundles[composition.fingerprint] = result.value.bundle;\n bundleManifests[composition.fingerprint] = result.value.compilerManifest;\n } else {\n const detail = result.reason instanceof Error ? result.reason.message : String(result.reason);\n process.stderr.write(`[doompi] could not precompile ${majorMode}${mute ? ' (mute)' : ''}: ${detail}\\n`);\n }\n }\n\n if (!bundles[state.compositionFingerprint]) {\n throw new Error('The synchronized active composition did not produce a bundle. Run doompi sync.');\n }\n\n const bootstrapEntries = [ownEntry(COMPOSED_PI_ENTRY)];\n const bootstrapOptions = { outputDirectory, outputName: 'bootstrap' };\n const bootstrap = await compileExtensionSet(bootstrapEntries, cacheDirectory, {\n ...bootstrapOptions,\n repositoryRoot: repoRoot,\n sharedCacheDirectory: location.sharedCacheDirectory,\n });\n const bootstrapManifest = extensionSetManifestPath(bootstrapEntries, cacheDirectory, bootstrapOptions);\n\n const nextState: SyncState = {\n ...state,\n compiled: undefined,\n bundles,\n bootstrap,\n precompile: {\n version: PRECOMPILE_STATE_VERSION,\n strategy: BUNDLED_PRECOMPILE_STRATEGY,\n bootstrapEntry: bootstrapEntries[0],\n bootstrapManifest,\n bundleManifests,\n },\n };\n if (options.state === undefined) {\n const current = readSyncState(repoRoot, homeDirectory);\n if (!current) throw new Error(`DoomPi synchronization disappeared while compiling ${repoRoot}`);\n if (compilationStateChanged(state, current)) {\n throw new Error(`DoomPi synchronization changed while compiling ${repoRoot}; restart Pi to use the new state.`);\n }\n await writeSyncState(repoRoot, nextState, homeDirectory);\n }\n return {\n bootstrap,\n bundles,\n bundleManifests,\n state: nextState,\n compositions,\n };\n}\n"],"mappings":";;;;;;;;;;;;AAoBA,MAAM,4BAA4B;AAClC,MAAM,sBAAsB;AAC5B,MAAM,oBAAoB;AAmB1B,SAAS,kBAAkB,UAAkC,SAA+C;CAC1G,MAAM,SAA4B,EAAE,GAAG,SAAS;CAChD,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,GAC/C,IAAI,UAAU,KAAA,GAAW,OAAO,OAAO;CAEzC,OAAO;AACT;AAEA,SAAS,aACP,OACA,SACA,WACA,MACoB;CACpB,OAAO;EACL;EACA,SAAS,CAAC,GAAG,MAAM,UAAU,OAAO;EACpC,SAAS,MAAM,UAAU;EACzB,QAAQ,MAAM,UAAU;EACxB;EACA,UAAU;EACV,QAAQ,QAAQ;EAChB,KAAK,QAAQ;CACf;AACF;AAEA,SAAS,wBAAwB,UAAqB,SAA6B;CACjF,OACE,SAAS,eAAe,QAAQ,cAChC,KAAK,UAAU,SAAS,SAAS,MAAM,KAAK,UAAU,QAAQ,SAAS,KACvE,KAAK,UAAU,SAAS,QAAQ,MAAM,KAAK,UAAU,QAAQ,QAAQ;AAEzE;;AAGA,eAAsB,mBACpB,UACA,cAAiC,QAAQ,KACzC,gBAAwB,YAAY,QAAQ,GAAG,QAAQ,GACvD,UAAqC,CAAC,GACT;CAC7B,MAAM,QAAQ,QAAQ,SAAS,cAAc,UAAU,aAAa;CACpE,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,iCAAiC,SAAS,mBAAmB;CAEzF,MAAM,qBAAqB,kBAAkB,MAAM,KAAK,WAAW;CACnE,MAAM,UAAU,iBAAiB,kBAAkB;CACnD,MAAM,mBAAmB,qBAAqB,UAAU,eAAe,WAAW;CAClF,MAAM,YAAY,mBAAmB,MAAM,QAAQ;CACnD,MAAM,OAAO;EACX,QAAQ,QAAQ;EAChB,UAAU;EACV,QAAQ,MAAM,UAAU;EACxB,cAAc,UAAU,aAAa,aAAa;EAClD;EACA;CACF;CAEA,MAAM,WAAW,oBAAoB,UAAU,aAAa;CAC5D,MAAM,YAAY,QAAQ,aAAa,cAAc,UAAU,aAAa;CAC5E,MAAM,iBAAiB,KAAK,KAAK,WAAW,yBAAyB;CACrE,MAAM,kBAAkB,KAAK,KAAK,WAAW,mBAAmB;CAChE,MAAM,UAAkC,CAAC;CACzC,MAAM,kBAA0C,CAAC;CACjD,MAAM,eAAuC,CAAC;CAC9C,MAAM,SAKA,CAAC;CACP,KAAK,MAAM,CAAC,WAAW,eAAe,OAAO,QAAQ,iBAAiB,SAAS,GAAG;EAChF,MAAM,iBAAiB,yBAAyB,kBAAkB,WAAW,QAAQ,QAAQ,KAAK;EAClG,KAAK,MAAM,QAAQ,CAAC,OAAO,IAAI,GAAG;GAChC,MAAM,cAAc,4BAA4B;IAC9C,GAAG;IACH;IACA,QAAQ;IACR;GACF,CAAC;GACD,aAAa,KAAK,WAAW;GAC7B,MAAM,iBAAiB,CAAC,GAAG,YAAY,gBAAgB;GACvD,MAAM,sBAAsB,CAAC,GAAG,YAAY,eAAe;GAC3D,MAAM,aAAa,GAAG,YAAY,OAAO,UAAU;GACnD,OAAO,KAAK;IACV;IACA;IACA;IACA,SAAS;KACP;KACA,WAAW,aAAa,OAAO,SAAS,WAAW,IAAI;KACvD;KACA;KACA,wBAAwB,YAAY;KACpC,YAAY,QAAQ;KACpB,aAAa,QAAQ;KACrB;KACA;KACA;KACA,sBAAsB,SAAS;IACjC;GACF,CAAC;EACH;CACF;CACA,QAAQ,yBAAyB,YAAY;CAC7C,MAAM,UAAU,MAAM,QAAQ,WAAW,OAAO,KAAK,EAAE,cAAc,qBAAqB,OAAO,CAAC,CAAC;CACnG,KAAK,MAAM,CAAC,OAAO,WAAW,QAAQ,QAAQ,GAAG;EAC/C,MAAM,EAAE,aAAa,WAAW,SAAS,OAAO;EAChD,IAAI,OAAO,WAAW,aAAa;GACjC,QAAQ,YAAY,eAAe,OAAO,MAAM;GAChD,gBAAgB,YAAY,eAAe,OAAO,MAAM;EAC1D,OAAO;GACL,MAAM,SAAS,OAAO,kBAAkB,QAAQ,OAAO,OAAO,UAAU,OAAO,OAAO,MAAM;GAC5F,QAAQ,OAAO,MAAM,iCAAiC,YAAY,OAAO,YAAY,GAAG,IAAI,OAAO,GAAG;EACxG;CACF;CAEA,IAAI,CAAC,QAAQ,MAAM,yBACjB,MAAM,IAAI,MAAM,gFAAgF;CAGlG,MAAM,mBAAmB,CAAC,SAAS,iBAAiB,CAAC;CACrD,MAAM,mBAAmB;EAAE;EAAiB,YAAY;CAAY;CACpE,MAAM,YAAY,MAAM,oBAAoB,kBAAkB,gBAAgB;EAC5E,GAAG;EACH,gBAAgB;EAChB,sBAAsB,SAAS;CACjC,CAAC;CACD,MAAM,oBAAoB,yBAAyB,kBAAkB,gBAAgB,gBAAgB;CAErG,MAAM,YAAuB;EAC3B,GAAG;EACH,UAAU,KAAA;EACV;EACA;EACA,YAAY;GACV,SAAS;GACT,UAAU;GACV,gBAAgB,iBAAiB;GACjC;GACA;EACF;CACF;CACA,IAAI,QAAQ,UAAU,KAAA,GAAW;EAC/B,MAAM,UAAU,cAAc,UAAU,aAAa;EACrD,IAAI,CAAC,SAAS,MAAM,IAAI,MAAM,sDAAsD,UAAU;EAC9F,IAAI,wBAAwB,OAAO,OAAO,GACxC,MAAM,IAAI,MAAM,kDAAkD,SAAS,mCAAmC;EAEhH,MAAM,eAAe,UAAU,WAAW,aAAa;CACzD;CACA,OAAO;EACL;EACA;EACA;EACA,OAAO;EACP;CACF;AACF"}
@@ -205,7 +205,7 @@ async function composeRuntimeLoadPlan(state, harness = require_index.getHarnessS
205
205
  personaEntry: resolvers.packageEntry(require_builders_cli_extensionAssembler_index.PERSONA_ENTRY),
206
206
  majorMode: harness.majorMode,
207
207
  layers: [...harness.layers],
208
- majorModesConfig: (0, _agimon_ai_doompi_config_majorModes.loadMajorModesConfig)(repoRoot),
208
+ majorModesConfig: (0, _agimon_ai_doompi_config_majorModes.loadMajorModesConfig)(repoRoot, environment.HOME ?? node_os.default.homedir(), environment),
209
209
  resolvers
210
210
  };
211
211
  const composition = require_builders_cli_extensionAssembler_index.resolveExtensionComposition({
@@ -316,7 +316,7 @@ async function composeDoomSession(pi, options = {}) {
316
316
  await startSyncedSession(state, repoRoot, environment);
317
317
  const flags = readStartupFlags(argv);
318
318
  if (flags.mute) environment[require_compositionState.MUTE_ENV] = ENABLED_FLAG;
319
- await applyStartupFlags(flags, (0, _agimon_ai_doompi_config_majorModes.loadMajorModesConfig)(repoRoot), repoRoot, problems);
319
+ await applyStartupFlags(flags, (0, _agimon_ai_doompi_config_majorModes.loadMajorModesConfig)(repoRoot, homeDirectory, environment), repoRoot, problems);
320
320
  }
321
321
  let loadPlan;
322
322
  try {
@@ -346,7 +346,7 @@ async function composeDoomSession(pi, options = {}) {
346
346
  const loaded = await loadComposedExtensions(pi, loadPlan.entries, problems);
347
347
  return {
348
348
  problems,
349
- stale: require_index$1.computeInputsHash(repoRoot, state.selection, homeDirectory) !== state.inputsHash || (0, _agimon_ai_doompi_config_harnessStore.loadHarnessState)(environment).state.majorMode === state.selection.majorMode && loadPlan.fingerprint !== state.compositionFingerprint,
349
+ stale: require_index$1.computeInputsHash(repoRoot, state.selection, homeDirectory, environment) !== state.inputsHash || (0, _agimon_ai_doompi_config_harnessStore.loadHarnessState)(environment).state.majorMode === state.selection.majorMode && loadPlan.fingerprint !== state.compositionFingerprint,
350
350
  loaded
351
351
  };
352
352
  }
@@ -1 +1 @@
1
- {"version":3,"file":"composition.cjs","names":["fs","path","parseArgs","readHarnessState","getHarnessState","runDirectory","os","createMapResolvers","PERSONA_ENTRY","loadMajorModesConfig","resolveExtensionComposition","MUTE_ENV","packageAttribution","pathToFileURL","withExtensionSource","findSyncedRoot","readSyncState","EXTERNAL_EXTENSIONS_ENV","alreadyComposed","COMPOSED_ENV","loadHarnessState","readBundleStatus","computeInputsHash"],"sources":["../../../../src/builders/cli/composition.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport { loadMajorModesConfig, type MajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { alreadyComposed } from '@agimon-ai/doompi-core/childProcess';\nimport { withExtensionSource } from '@agimon-ai/doompi-ui/extensionName';\nimport { type ExtensionAPI, parseArgs } from '@earendil-works/pi-coding-agent';\n\nimport {\n createHarnessSession,\n getHarnessState,\n type HarnessState,\n loadHarnessState,\n readHarnessState,\n updateHarnessState,\n} from '../../composition/harnessState';\nimport {\n computeInputsHash,\n createMapResolvers,\n readSyncState,\n runDirectory,\n type SyncState,\n} from '../../composition/syncState';\nimport type { HarnessPreset } from '../../composition/types/harness';\nimport { findSyncedRoot, readBundleStatus } from './bootstrapLocator';\nimport { COMPOSED_ENV, EXTERNAL_EXTENSIONS_ENV, MUTE_ENV } from './compositionState';\nimport { PERSONA_ENTRY, packageAttribution, resolveExtensionComposition } from './extensionAssembler';\nimport { configurePreset } from './harnessContext';\n\nexport { findSyncedRoot } from './bootstrapLocator';\nexport { alreadyComposed } from '@agimon-ai/doompi-core/childProcess';\nexport { COMPOSED_ENV, EXTERNAL_EXTENSIONS_ENV, MUTE_ENV } from './compositionState';\n\n/**\n * The runtime half of `doom-pi sync`.\n *\n * Pi's own config carries a single entry, the doom-pi extension, and this is\n * what that entry runs: hydrate the harness environment, apply the startup\n * flags, then load the extension set the matrix selects. It runs again on every\n * `/reload`, which is what lets `/mode` swap the set without a restart,\n * since Pi rebuilds its extension runner from whatever a reload produced.\n */\n\nconst ENABLED_FLAG = '1';\nconst SUBAGENT_AGENT_DIRS_ENV = 'PI_SUBAGENT_EXTRA_AGENT_DIRS';\nconst SUBAGENT_SKILL_DIRS_ENV = 'PI_SUBAGENT_EXTRA_SKILL_DIRS';\nconst DOMAIN_SEPARATOR = ',';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst DOMAIN_APPLY_MODULE = '@agimon-ai/doompi-domain/apply';\nconst SELECTION_SWITCH_MODULE = '@agimon-ai/doompi-config/selectionSwitch';\nconst NOT_SYNCED = 'doompi is configured for Pi but this repository was never synced. Run doompi sync.';\nconst UNUSABLE_STATE_MESSAGE = 'doompi could not read its synchronized state. Run doompi sync.';\nconst TIMING_ENABLED = process.env.DOOMPI_TIMING === ENABLED_FLAG || process.env.PI_TIMING === ENABLED_FLAG;\n\nexport const DOOM_FLAGS = {\n majorMode: 'major-mode',\n domains: 'domains',\n profile: 'profile',\n mute: 'mute',\n} as const;\n\n/** Removed flag, reported rather than ignored when a synced session passes it. */\nexport const REMOVED_LAYER_FLAG = 'layer';\n\nexport interface StartupFlags {\n majorMode?: string;\n domains?: string[];\n profile?: string;\n mute: boolean;\n /** True when the caller passed the removed `--layer` flag. */\n removedLayer: boolean;\n}\n\nexport interface ComposeOutcome {\n /** Everything that went wrong, reported once the session can show messages. */\n problems: string[];\n /** Config changed since the last sync, which is a prompt to re-sync. */\n stale: boolean;\n loaded: string[];\n}\n\nfunction message(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction realPath(target: string): string {\n try {\n return fs.realpathSync(target);\n } catch {\n return path.resolve(target);\n }\n}\n\n/**\n * True when this process was handed the doom extension set on the command line.\n *\n * The launcher and detached Doom Team children both pass the composed list as\n * `--extension` arguments, and Pi merges those with whatever project settings\n * declare. Composing again here would load every extension twice, which Pi\n * reports as each tool and flag conflicting with itself.\n *\n * Matching against the synced paths rather than the mere presence of\n * `--extension` keeps an unrelated one-off, `pi -e ./debug.ts`, from silently\n * costing the user their whole setup.\n */\nexport function extensionsProvidedExternally(argv: string[], resolved: Record<string, string>): boolean {\n const { extensions } = parseArgs(argv);\n if (!extensions || extensions.length === 0) return false;\n const composed = new Set(Object.values(resolved).map(realPath));\n return extensions.some((entry) => composed.has(realPath(entry)));\n}\n\n/**\n * Opens this process's session from what sync pinned.\n *\n * The synced record is decoded underneath the live environment, so an inherited\n * value still wins: a launcher run, a nested session, or a CI override is more\n * specific than anything sync wrote. The result becomes the session's state\n * file, which is the authority from here on; the environment it publishes is\n * for the bash hooks and the packages that can only read one.\n */\nexport async function startSyncedSession(\n state: SyncState,\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<HarnessState> {\n const directory = await prepareRunDirectory(repoRoot, environment);\n const harness: HarnessState = {\n ...readHarnessState({ ...state.env, ...environment }),\n // Recorded separately because no variable carries them.\n ...state.fileState,\n // Every synced process gets private mutable staging. The source content\n // identity is unchanged because the projection fingerprint excludes it.\n mcpProjection: {\n ...state.fileState.mcpProjection,\n stagingDirectory: directory,\n },\n temporaryDirectory: directory,\n };\n createHarnessSession(harness, { directory, environment });\n // The profile's own defaults, which the launcher applies at spawn. They are\n // recorded as a map rather than as variables so /profile can tell which ones\n // it owns when it swaps them out later.\n for (const [key, value] of Object.entries(harness.profileEnvironment)) {\n if (environment[key] === undefined) environment[key] = value;\n }\n return harness;\n}\n\n/**\n * Reads the doom flags off the command line.\n *\n * Pi collects unrecognized flags for extensions but hands their values back\n * only after every extension has loaded, which is too late to decide what to\n * load. Parsing argv with Pi's own parser keeps the value rules identical.\n */\nexport function readStartupFlags(argv: string[]): StartupFlags {\n const { unknownFlags } = parseArgs(argv);\n const text = (name: string): string | undefined => {\n const value = unknownFlags.get(name);\n return typeof value === 'string' && value.trim() ? value.trim() : undefined;\n };\n const domains = text(DOOM_FLAGS.domains);\n return {\n majorMode: text(DOOM_FLAGS.majorMode),\n domains: domains\n ?.split(DOMAIN_SEPARATOR)\n .map((name) => name.trim())\n .filter(Boolean),\n profile: text(DOOM_FLAGS.profile),\n mute: unknownFlags.get(DOOM_FLAGS.mute) !== undefined,\n removedLayer: unknownFlags.get(REMOVED_LAYER_FLAG) !== undefined,\n };\n}\n\n/** Registers the flags so Pi accepts them and lists them in `pi --help`. */\nexport function registerDoomFlags(pi: Pick<ExtensionAPI, 'registerFlag'>): void {\n pi.registerFlag(DOOM_FLAGS.majorMode, {\n type: 'string',\n description: 'Named major mode from .doom/modes.yaml',\n });\n pi.registerFlag(DOOM_FLAGS.domains, { type: 'string', description: 'Comma-separated content domains' });\n pi.registerFlag(DOOM_FLAGS.profile, { type: 'string', description: 'Persona and env from .doom/profiles.yaml' });\n pi.registerFlag(DOOM_FLAGS.mute, { type: 'boolean', description: 'Disable the notification extension' });\n}\n\n/**\n * Applies the startup flags through the same switchers the slash commands use.\n *\n * A bad value is collected rather than thrown: a factory that throws takes the\n * whole doom setup down with it, which is far worse than one unapplied flag.\n */\nexport async function applyStartupFlags(\n flags: StartupFlags,\n majorModesConfig: MajorModesConfig,\n repoRoot: string,\n problems: string[],\n): Promise<void> {\n // Reported rather than ignored: Pi hands unrecognized flags to extensions, so\n // a stale `--layer dev` would otherwise start a session on the wrong mode\n // without saying anything.\n if (flags.removedLayer) problems.push(`--${REMOVED_LAYER_FLAG} was replaced by --${DOOM_FLAGS.majorMode}`);\n if (flags.majorMode) {\n try {\n const { applyMajorMode } = await import(SELECTION_SWITCH_MODULE);\n applyMajorMode(majorModesConfig, flags.majorMode, getHarnessState());\n } catch (error) {\n problems.push(`--${DOOM_FLAGS.majorMode} ${flags.majorMode}: ${message(error)}`);\n }\n }\n if (flags.domains && flags.domains.length > 0) {\n try {\n const { applyDomains } = await import(DOMAIN_APPLY_MODULE);\n await applyDomains(flags.domains, getHarnessState());\n } catch (error) {\n problems.push(`--domains ${flags.domains.join(DOMAIN_SEPARATOR)}: ${message(error)}`);\n }\n }\n if (flags.profile) {\n try {\n const [{ applyProfile }, { resolveProfile }] = await Promise.all([\n import(SELECTION_SWITCH_MODULE),\n import('@agimon-ai/doompi-config/profiles'),\n ]);\n await applyProfile(resolveProfile(repoRoot, flags.profile), getHarnessState());\n } catch (error) {\n problems.push(`--profile ${flags.profile}: ${message(error)}`);\n }\n }\n}\n\n/**\n * Points harness state at a per-process directory before anything writes there.\n *\n * The synced directory is a baseline that every session reads; live switches\n * materialize into this one instead, so a second session in the same repository\n * cannot overwrite what the first is using.\n */\nexport async function prepareRunDirectory(\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<string> {\n const directory = runDirectory(repoRoot, process.pid, environment.HOME ?? os.homedir());\n await fs.promises.mkdir(directory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n // Through the store rather than the environment: the state file is the\n // authority, and a switcher reading a stale directory would write a live\n // session's resources into the wrong place.\n updateHarnessState({ temporaryDirectory: directory }, environment);\n return directory;\n}\n\n/** Removes this session's scratch directory. */\nexport async function cleanupRunDirectory(\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<void> {\n await fs.promises.rm(runDirectory(repoRoot, process.pid, environment.HOME ?? os.homedir()), {\n recursive: true,\n force: true,\n });\n}\n\n/**\n * Builds the ordered list of extensions to load for the current state.\n *\n * Returns entries in actual Pi factory activation order. CLI argument adaptation\n * remains a launcher concern and synchronized bundles preserve this same order.\n */\nexport interface ComposedRuntimeLoadPlan {\n readonly entries: string[];\n readonly fingerprint: string;\n}\n\n/** Builds the canonical synchronized activation plan. */\nexport async function composeRuntimeLoadPlan(\n state: SyncState,\n harness: HarnessState = getHarnessState(),\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<ComposedRuntimeLoadPlan> {\n const repoRoot = harness.root ?? state.root;\n const resolvers = createMapResolvers(state.resolved, state.compiled);\n const context = {\n agents: harness.agents,\n // Exiting when the agent settles is a launcher decision with no synced\n // equivalent: a session started by hand is meant to stay open.\n autoStop: false,\n preset: state.selection.preset,\n personaEntry: resolvers.packageEntry(PERSONA_ENTRY),\n majorMode: harness.majorMode,\n layers: [...harness.layers],\n majorModesConfig: loadMajorModesConfig(repoRoot),\n resolvers,\n };\n\n // A state field, so it goes through the store: Doom Team reads it from the\n // environment the store publishes when it builds a child's spawn snapshot.\n const composition = resolveExtensionComposition({\n ...context,\n mute: environment[MUTE_ENV] === ENABLED_FLAG,\n });\n updateHarnessState(\n {\n childExtensions: [...composition.childActivation],\n compositionFingerprint: composition.fingerprint,\n packageAttribution: packageAttribution(composition),\n },\n environment,\n );\n if (harness.agents) {\n environment[SUBAGENT_AGENT_DIRS_ENV] = harness.agentDirectories.join(path.delimiter);\n environment[SUBAGENT_SKILL_DIRS_ENV] = harness.skillDirectories.join(path.delimiter);\n }\n const entries = [...composition.parentActivation];\n const bundle = state.bundles?.[composition.fingerprint];\n return {\n entries: bundle ? [bundle] : entries,\n fingerprint: composition.fingerprint,\n };\n}\n\n/** Compatibility view for callers that only need Pi extension entries. */\nexport async function composeLoadOrder(\n state: SyncState,\n harness: HarnessState = getHarnessState(),\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<string[]> {\n return (await composeRuntimeLoadPlan(state, harness, environment)).entries;\n}\n\n/**\n * Imports each extension and hands it a source-aware view of the Pi API.\n *\n * Every Pi extension entry is a factory of the same shape, so one extension can\n * activate the rest. The view delegates to the same host API while retaining the\n * entry path for tools registered through a composed loader. Failures are per\n * entry: one unloadable module must not cost the session everything after it.\n */\nexport async function loadComposedExtensions(\n pi: ExtensionAPI,\n entries: string[],\n problems: string[],\n): Promise<string[]> {\n const loaded: string[] = [];\n for (const entry of entries) {\n const importStartedAt = performance.now();\n let module: { default?: unknown };\n try {\n module = (await import(pathToFileURL(entry).href)) as { default?: unknown };\n if (TIMING_ENABLED) {\n process.stderr.write(`[doompi:timing] import ${(performance.now() - importStartedAt).toFixed(1)}ms ${entry}\\n`);\n }\n } catch (error) {\n problems.push(`${entry}: ${message(error)}`);\n continue;\n }\n if (typeof module.default !== 'function') {\n problems.push(`${entry}: does not export an extension factory`);\n continue;\n }\n const factoryStartedAt = performance.now();\n try {\n await (module.default as (api: ExtensionAPI) => void | Promise<void>)(withExtensionSource(pi, entry));\n if (TIMING_ENABLED) {\n process.stderr.write(\n `[doompi:timing] factory ${(performance.now() - factoryStartedAt).toFixed(1)}ms ${entry}\\n`,\n );\n }\n loaded.push(entry);\n } catch (error) {\n problems.push(`${entry}: ${message(error)}`);\n }\n }\n return loaded;\n}\n\nexport interface ComposeOptions {\n cwd?: string;\n argv?: string[];\n environment?: NodeJS.ProcessEnv;\n}\n\n/**\n * Runs the whole synced startup for one Pi session.\n *\n * Never throws: whatever fails is reported through the outcome so the session\n * still starts and can say what is wrong.\n */\nexport async function composeDoomSession(pi: ExtensionAPI, options: ComposeOptions = {}): Promise<ComposeOutcome> {\n const environment = options.environment ?? process.env;\n const problems: string[] = [];\n const homeDirectory = environment.HOME ?? os.homedir();\n const repoRoot = findSyncedRoot(options.cwd ?? process.cwd(), homeDirectory);\n if (!repoRoot) return { problems: [NOT_SYNCED], stale: false, loaded: [] };\n\n let state: SyncState | undefined;\n try {\n state = readSyncState(repoRoot, homeDirectory);\n } catch (error) {\n return { problems: [message(error)], stale: false, loaded: [] };\n }\n if (!state) return { problems: [NOT_SYNCED], stale: false, loaded: [] };\n\n const argv = options.argv ?? process.argv.slice(2);\n // The launcher and Doom Team children compose the set themselves and pass it\n // in, so this entry has nothing left to do but stay out of the way.\n if (\n environment[EXTERNAL_EXTENSIONS_ENV] === ENABLED_FLAG ||\n extensionsProvidedExternally(argv, { ...state.resolved, ...state.bundles })\n ) {\n return { problems: [], stale: false, loaded: [] };\n }\n\n configurePreset({ preset: state.selection.preset as HarnessPreset, piArgs: [] }, environment);\n\n // Only the first load opens a session. A reload finds the pointer this set\n // and reads the file back, which is what carries a live /domains or /profile\n // switch across the reload instead of resetting it to what sync pinned.\n if (!alreadyComposed(environment)) {\n environment[COMPOSED_ENV] = ENABLED_FLAG;\n await startSyncedSession(state, repoRoot, environment);\n const flags = readStartupFlags(argv);\n if (flags.mute) environment[MUTE_ENV] = ENABLED_FLAG;\n await applyStartupFlags(flags, loadMajorModesConfig(repoRoot), repoRoot, problems);\n }\n\n let loadPlan: ComposedRuntimeLoadPlan;\n try {\n // Read back through the store rather than from this process: the startup\n // flags may have switched the layer or the domains a moment ago, and the\n // environment being managed is not always this process's own.\n loadPlan = await composeRuntimeLoadPlan(state, loadHarnessState(environment).state, environment);\n } catch (error) {\n return { problems: [...problems, message(error)], stale: false, loaded: [] };\n }\n\n const selectedBundle = state.bundles?.[loadPlan.fingerprint];\n if (selectedBundle) {\n try {\n const status = readBundleStatus(repoRoot, loadPlan.fingerprint, homeDirectory);\n if (!status.fresh || status.bundle !== selectedBundle) {\n return { problems: [...problems, UNUSABLE_STATE_MESSAGE], stale: false, loaded: [] };\n }\n } catch {\n return { problems: [...problems, UNUSABLE_STATE_MESSAGE], stale: false, loaded: [] };\n }\n }\n\n const loaded = await loadComposedExtensions(pi, loadPlan.entries, problems);\n return {\n problems,\n stale:\n computeInputsHash(repoRoot, state.selection, homeDirectory) !== state.inputsHash ||\n (loadHarnessState(environment).state.majorMode === state.selection.majorMode &&\n loadPlan.fingerprint !== state.compositionFingerprint),\n loaded,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"composition.cjs","names":["fs","path","parseArgs","readHarnessState","getHarnessState","runDirectory","os","createMapResolvers","PERSONA_ENTRY","loadMajorModesConfig","resolveExtensionComposition","MUTE_ENV","packageAttribution","pathToFileURL","withExtensionSource","findSyncedRoot","readSyncState","EXTERNAL_EXTENSIONS_ENV","alreadyComposed","COMPOSED_ENV","loadHarnessState","readBundleStatus","computeInputsHash"],"sources":["../../../../src/builders/cli/composition.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport { loadMajorModesConfig, type MajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { alreadyComposed } from '@agimon-ai/doompi-core/childProcess';\nimport { withExtensionSource } from '@agimon-ai/doompi-ui/extensionName';\nimport { type ExtensionAPI, parseArgs } from '@earendil-works/pi-coding-agent';\n\nimport {\n createHarnessSession,\n getHarnessState,\n type HarnessState,\n loadHarnessState,\n readHarnessState,\n updateHarnessState,\n} from '../../composition/harnessState';\nimport {\n computeInputsHash,\n createMapResolvers,\n readSyncState,\n runDirectory,\n type SyncState,\n} from '../../composition/syncState';\nimport type { HarnessPreset } from '../../composition/types/harness';\nimport { findSyncedRoot, readBundleStatus } from './bootstrapLocator';\nimport { COMPOSED_ENV, EXTERNAL_EXTENSIONS_ENV, MUTE_ENV } from './compositionState';\nimport { PERSONA_ENTRY, packageAttribution, resolveExtensionComposition } from './extensionAssembler';\nimport { configurePreset } from './harnessContext';\n\nexport { findSyncedRoot } from './bootstrapLocator';\nexport { alreadyComposed } from '@agimon-ai/doompi-core/childProcess';\nexport { COMPOSED_ENV, EXTERNAL_EXTENSIONS_ENV, MUTE_ENV } from './compositionState';\n\n/**\n * The runtime half of `doom-pi sync`.\n *\n * Pi's own config carries a single entry, the doom-pi extension, and this is\n * what that entry runs: hydrate the harness environment, apply the startup\n * flags, then load the extension set the matrix selects. It runs again on every\n * `/reload`, which is what lets `/mode` swap the set without a restart,\n * since Pi rebuilds its extension runner from whatever a reload produced.\n */\n\nconst ENABLED_FLAG = '1';\nconst SUBAGENT_AGENT_DIRS_ENV = 'PI_SUBAGENT_EXTRA_AGENT_DIRS';\nconst SUBAGENT_SKILL_DIRS_ENV = 'PI_SUBAGENT_EXTRA_SKILL_DIRS';\nconst DOMAIN_SEPARATOR = ',';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst DOMAIN_APPLY_MODULE = '@agimon-ai/doompi-domain/apply';\nconst SELECTION_SWITCH_MODULE = '@agimon-ai/doompi-config/selectionSwitch';\nconst NOT_SYNCED = 'doompi is configured for Pi but this repository was never synced. Run doompi sync.';\nconst UNUSABLE_STATE_MESSAGE = 'doompi could not read its synchronized state. Run doompi sync.';\nconst TIMING_ENABLED = process.env.DOOMPI_TIMING === ENABLED_FLAG || process.env.PI_TIMING === ENABLED_FLAG;\n\nexport const DOOM_FLAGS = {\n majorMode: 'major-mode',\n domains: 'domains',\n profile: 'profile',\n mute: 'mute',\n} as const;\n\n/** Removed flag, reported rather than ignored when a synced session passes it. */\nexport const REMOVED_LAYER_FLAG = 'layer';\n\nexport interface StartupFlags {\n majorMode?: string;\n domains?: string[];\n profile?: string;\n mute: boolean;\n /** True when the caller passed the removed `--layer` flag. */\n removedLayer: boolean;\n}\n\nexport interface ComposeOutcome {\n /** Everything that went wrong, reported once the session can show messages. */\n problems: string[];\n /** Config changed since the last sync, which is a prompt to re-sync. */\n stale: boolean;\n loaded: string[];\n}\n\nfunction message(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction realPath(target: string): string {\n try {\n return fs.realpathSync(target);\n } catch {\n return path.resolve(target);\n }\n}\n\n/**\n * True when this process was handed the doom extension set on the command line.\n *\n * The launcher and detached Doom Team children both pass the composed list as\n * `--extension` arguments, and Pi merges those with whatever project settings\n * declare. Composing again here would load every extension twice, which Pi\n * reports as each tool and flag conflicting with itself.\n *\n * Matching against the synced paths rather than the mere presence of\n * `--extension` keeps an unrelated one-off, `pi -e ./debug.ts`, from silently\n * costing the user their whole setup.\n */\nexport function extensionsProvidedExternally(argv: string[], resolved: Record<string, string>): boolean {\n const { extensions } = parseArgs(argv);\n if (!extensions || extensions.length === 0) return false;\n const composed = new Set(Object.values(resolved).map(realPath));\n return extensions.some((entry) => composed.has(realPath(entry)));\n}\n\n/**\n * Opens this process's session from what sync pinned.\n *\n * The synced record is decoded underneath the live environment, so an inherited\n * value still wins: a launcher run, a nested session, or a CI override is more\n * specific than anything sync wrote. The result becomes the session's state\n * file, which is the authority from here on; the environment it publishes is\n * for the bash hooks and the packages that can only read one.\n */\nexport async function startSyncedSession(\n state: SyncState,\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<HarnessState> {\n const directory = await prepareRunDirectory(repoRoot, environment);\n const harness: HarnessState = {\n ...readHarnessState({ ...state.env, ...environment }),\n // Recorded separately because no variable carries them.\n ...state.fileState,\n // Every synced process gets private mutable staging. The source content\n // identity is unchanged because the projection fingerprint excludes it.\n mcpProjection: {\n ...state.fileState.mcpProjection,\n stagingDirectory: directory,\n },\n temporaryDirectory: directory,\n };\n createHarnessSession(harness, { directory, environment });\n // The profile's own defaults, which the launcher applies at spawn. They are\n // recorded as a map rather than as variables so /profile can tell which ones\n // it owns when it swaps them out later.\n for (const [key, value] of Object.entries(harness.profileEnvironment)) {\n if (environment[key] === undefined) environment[key] = value;\n }\n return harness;\n}\n\n/**\n * Reads the doom flags off the command line.\n *\n * Pi collects unrecognized flags for extensions but hands their values back\n * only after every extension has loaded, which is too late to decide what to\n * load. Parsing argv with Pi's own parser keeps the value rules identical.\n */\nexport function readStartupFlags(argv: string[]): StartupFlags {\n const { unknownFlags } = parseArgs(argv);\n const text = (name: string): string | undefined => {\n const value = unknownFlags.get(name);\n return typeof value === 'string' && value.trim() ? value.trim() : undefined;\n };\n const domains = text(DOOM_FLAGS.domains);\n return {\n majorMode: text(DOOM_FLAGS.majorMode),\n domains: domains\n ?.split(DOMAIN_SEPARATOR)\n .map((name) => name.trim())\n .filter(Boolean),\n profile: text(DOOM_FLAGS.profile),\n mute: unknownFlags.get(DOOM_FLAGS.mute) !== undefined,\n removedLayer: unknownFlags.get(REMOVED_LAYER_FLAG) !== undefined,\n };\n}\n\n/** Registers the flags so Pi accepts them and lists them in `pi --help`. */\nexport function registerDoomFlags(pi: Pick<ExtensionAPI, 'registerFlag'>): void {\n pi.registerFlag(DOOM_FLAGS.majorMode, {\n type: 'string',\n description: 'Named major mode from .doom/modes.yaml',\n });\n pi.registerFlag(DOOM_FLAGS.domains, { type: 'string', description: 'Comma-separated content domains' });\n pi.registerFlag(DOOM_FLAGS.profile, { type: 'string', description: 'Persona and env from .doom/profiles.yaml' });\n pi.registerFlag(DOOM_FLAGS.mute, { type: 'boolean', description: 'Disable the notification extension' });\n}\n\n/**\n * Applies the startup flags through the same switchers the slash commands use.\n *\n * A bad value is collected rather than thrown: a factory that throws takes the\n * whole doom setup down with it, which is far worse than one unapplied flag.\n */\nexport async function applyStartupFlags(\n flags: StartupFlags,\n majorModesConfig: MajorModesConfig,\n repoRoot: string,\n problems: string[],\n): Promise<void> {\n // Reported rather than ignored: Pi hands unrecognized flags to extensions, so\n // a stale `--layer dev` would otherwise start a session on the wrong mode\n // without saying anything.\n if (flags.removedLayer) problems.push(`--${REMOVED_LAYER_FLAG} was replaced by --${DOOM_FLAGS.majorMode}`);\n if (flags.majorMode) {\n try {\n const { applyMajorMode } = await import(SELECTION_SWITCH_MODULE);\n applyMajorMode(majorModesConfig, flags.majorMode, getHarnessState());\n } catch (error) {\n problems.push(`--${DOOM_FLAGS.majorMode} ${flags.majorMode}: ${message(error)}`);\n }\n }\n if (flags.domains && flags.domains.length > 0) {\n try {\n const { applyDomains } = await import(DOMAIN_APPLY_MODULE);\n await applyDomains(flags.domains, getHarnessState());\n } catch (error) {\n problems.push(`--domains ${flags.domains.join(DOMAIN_SEPARATOR)}: ${message(error)}`);\n }\n }\n if (flags.profile) {\n try {\n const [{ applyProfile }, { resolveProfile }] = await Promise.all([\n import(SELECTION_SWITCH_MODULE),\n import('@agimon-ai/doompi-config/profiles'),\n ]);\n await applyProfile(resolveProfile(repoRoot, flags.profile), getHarnessState());\n } catch (error) {\n problems.push(`--profile ${flags.profile}: ${message(error)}`);\n }\n }\n}\n\n/**\n * Points harness state at a per-process directory before anything writes there.\n *\n * The synced directory is a baseline that every session reads; live switches\n * materialize into this one instead, so a second session in the same repository\n * cannot overwrite what the first is using.\n */\nexport async function prepareRunDirectory(\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<string> {\n const directory = runDirectory(repoRoot, process.pid, environment.HOME ?? os.homedir());\n await fs.promises.mkdir(directory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n // Through the store rather than the environment: the state file is the\n // authority, and a switcher reading a stale directory would write a live\n // session's resources into the wrong place.\n updateHarnessState({ temporaryDirectory: directory }, environment);\n return directory;\n}\n\n/** Removes this session's scratch directory. */\nexport async function cleanupRunDirectory(\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<void> {\n await fs.promises.rm(runDirectory(repoRoot, process.pid, environment.HOME ?? os.homedir()), {\n recursive: true,\n force: true,\n });\n}\n\n/**\n * Builds the ordered list of extensions to load for the current state.\n *\n * Returns entries in actual Pi factory activation order. CLI argument adaptation\n * remains a launcher concern and synchronized bundles preserve this same order.\n */\nexport interface ComposedRuntimeLoadPlan {\n readonly entries: string[];\n readonly fingerprint: string;\n}\n\n/** Builds the canonical synchronized activation plan. */\nexport async function composeRuntimeLoadPlan(\n state: SyncState,\n harness: HarnessState = getHarnessState(),\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<ComposedRuntimeLoadPlan> {\n const repoRoot = harness.root ?? state.root;\n const resolvers = createMapResolvers(state.resolved, state.compiled);\n const context = {\n agents: harness.agents,\n // Exiting when the agent settles is a launcher decision with no synced\n // equivalent: a session started by hand is meant to stay open.\n autoStop: false,\n preset: state.selection.preset,\n personaEntry: resolvers.packageEntry(PERSONA_ENTRY),\n majorMode: harness.majorMode,\n layers: [...harness.layers],\n majorModesConfig: loadMajorModesConfig(repoRoot, environment.HOME ?? os.homedir(), environment),\n resolvers,\n };\n\n // A state field, so it goes through the store: Doom Team reads it from the\n // environment the store publishes when it builds a child's spawn snapshot.\n const composition = resolveExtensionComposition({\n ...context,\n mute: environment[MUTE_ENV] === ENABLED_FLAG,\n });\n updateHarnessState(\n {\n childExtensions: [...composition.childActivation],\n compositionFingerprint: composition.fingerprint,\n packageAttribution: packageAttribution(composition),\n },\n environment,\n );\n if (harness.agents) {\n environment[SUBAGENT_AGENT_DIRS_ENV] = harness.agentDirectories.join(path.delimiter);\n environment[SUBAGENT_SKILL_DIRS_ENV] = harness.skillDirectories.join(path.delimiter);\n }\n const entries = [...composition.parentActivation];\n const bundle = state.bundles?.[composition.fingerprint];\n return {\n entries: bundle ? [bundle] : entries,\n fingerprint: composition.fingerprint,\n };\n}\n\n/** Compatibility view for callers that only need Pi extension entries. */\nexport async function composeLoadOrder(\n state: SyncState,\n harness: HarnessState = getHarnessState(),\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<string[]> {\n return (await composeRuntimeLoadPlan(state, harness, environment)).entries;\n}\n\n/**\n * Imports each extension and hands it a source-aware view of the Pi API.\n *\n * Every Pi extension entry is a factory of the same shape, so one extension can\n * activate the rest. The view delegates to the same host API while retaining the\n * entry path for tools registered through a composed loader. Failures are per\n * entry: one unloadable module must not cost the session everything after it.\n */\nexport async function loadComposedExtensions(\n pi: ExtensionAPI,\n entries: string[],\n problems: string[],\n): Promise<string[]> {\n const loaded: string[] = [];\n for (const entry of entries) {\n const importStartedAt = performance.now();\n let module: { default?: unknown };\n try {\n module = (await import(pathToFileURL(entry).href)) as { default?: unknown };\n if (TIMING_ENABLED) {\n process.stderr.write(`[doompi:timing] import ${(performance.now() - importStartedAt).toFixed(1)}ms ${entry}\\n`);\n }\n } catch (error) {\n problems.push(`${entry}: ${message(error)}`);\n continue;\n }\n if (typeof module.default !== 'function') {\n problems.push(`${entry}: does not export an extension factory`);\n continue;\n }\n const factoryStartedAt = performance.now();\n try {\n await (module.default as (api: ExtensionAPI) => void | Promise<void>)(withExtensionSource(pi, entry));\n if (TIMING_ENABLED) {\n process.stderr.write(\n `[doompi:timing] factory ${(performance.now() - factoryStartedAt).toFixed(1)}ms ${entry}\\n`,\n );\n }\n loaded.push(entry);\n } catch (error) {\n problems.push(`${entry}: ${message(error)}`);\n }\n }\n return loaded;\n}\n\nexport interface ComposeOptions {\n cwd?: string;\n argv?: string[];\n environment?: NodeJS.ProcessEnv;\n}\n\n/**\n * Runs the whole synced startup for one Pi session.\n *\n * Never throws: whatever fails is reported through the outcome so the session\n * still starts and can say what is wrong.\n */\nexport async function composeDoomSession(pi: ExtensionAPI, options: ComposeOptions = {}): Promise<ComposeOutcome> {\n const environment = options.environment ?? process.env;\n const problems: string[] = [];\n const homeDirectory = environment.HOME ?? os.homedir();\n const repoRoot = findSyncedRoot(options.cwd ?? process.cwd(), homeDirectory);\n if (!repoRoot) return { problems: [NOT_SYNCED], stale: false, loaded: [] };\n\n let state: SyncState | undefined;\n try {\n state = readSyncState(repoRoot, homeDirectory);\n } catch (error) {\n return { problems: [message(error)], stale: false, loaded: [] };\n }\n if (!state) return { problems: [NOT_SYNCED], stale: false, loaded: [] };\n\n const argv = options.argv ?? process.argv.slice(2);\n // The launcher and Doom Team children compose the set themselves and pass it\n // in, so this entry has nothing left to do but stay out of the way.\n if (\n environment[EXTERNAL_EXTENSIONS_ENV] === ENABLED_FLAG ||\n extensionsProvidedExternally(argv, { ...state.resolved, ...state.bundles })\n ) {\n return { problems: [], stale: false, loaded: [] };\n }\n\n configurePreset({ preset: state.selection.preset as HarnessPreset, piArgs: [] }, environment);\n\n // Only the first load opens a session. A reload finds the pointer this set\n // and reads the file back, which is what carries a live /domains or /profile\n // switch across the reload instead of resetting it to what sync pinned.\n if (!alreadyComposed(environment)) {\n environment[COMPOSED_ENV] = ENABLED_FLAG;\n await startSyncedSession(state, repoRoot, environment);\n const flags = readStartupFlags(argv);\n if (flags.mute) environment[MUTE_ENV] = ENABLED_FLAG;\n await applyStartupFlags(flags, loadMajorModesConfig(repoRoot, homeDirectory, environment), repoRoot, problems);\n }\n\n let loadPlan: ComposedRuntimeLoadPlan;\n try {\n // Read back through the store rather than from this process: the startup\n // flags may have switched the layer or the domains a moment ago, and the\n // environment being managed is not always this process's own.\n loadPlan = await composeRuntimeLoadPlan(state, loadHarnessState(environment).state, environment);\n } catch (error) {\n return { problems: [...problems, message(error)], stale: false, loaded: [] };\n }\n\n const selectedBundle = state.bundles?.[loadPlan.fingerprint];\n if (selectedBundle) {\n try {\n const status = readBundleStatus(repoRoot, loadPlan.fingerprint, homeDirectory);\n if (!status.fresh || status.bundle !== selectedBundle) {\n return { problems: [...problems, UNUSABLE_STATE_MESSAGE], stale: false, loaded: [] };\n }\n } catch {\n return { problems: [...problems, UNUSABLE_STATE_MESSAGE], stale: false, loaded: [] };\n }\n }\n\n const loaded = await loadComposedExtensions(pi, loadPlan.entries, problems);\n return {\n problems,\n stale:\n computeInputsHash(repoRoot, state.selection, homeDirectory, environment) !== state.inputsHash ||\n (loadHarnessState(environment).state.majorMode === state.selection.majorMode &&\n loadPlan.fingerprint !== state.compositionFingerprint),\n loaded,\n };\n}\n"],"mappings":"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"}
@@ -200,7 +200,7 @@ async function composeRuntimeLoadPlan(state, harness = getHarnessState(), enviro
200
200
  personaEntry: resolvers.packageEntry(PERSONA_ENTRY),
201
201
  majorMode: harness.majorMode,
202
202
  layers: [...harness.layers],
203
- majorModesConfig: loadMajorModesConfig(repoRoot),
203
+ majorModesConfig: loadMajorModesConfig(repoRoot, environment.HOME ?? os.homedir(), environment),
204
204
  resolvers
205
205
  };
206
206
  const composition = resolveExtensionComposition({
@@ -311,7 +311,7 @@ async function composeDoomSession(pi, options = {}) {
311
311
  await startSyncedSession(state, repoRoot, environment);
312
312
  const flags = readStartupFlags(argv);
313
313
  if (flags.mute) environment[MUTE_ENV] = ENABLED_FLAG;
314
- await applyStartupFlags(flags, loadMajorModesConfig(repoRoot), repoRoot, problems);
314
+ await applyStartupFlags(flags, loadMajorModesConfig(repoRoot, homeDirectory, environment), repoRoot, problems);
315
315
  }
316
316
  let loadPlan;
317
317
  try {
@@ -341,7 +341,7 @@ async function composeDoomSession(pi, options = {}) {
341
341
  const loaded = await loadComposedExtensions(pi, loadPlan.entries, problems);
342
342
  return {
343
343
  problems,
344
- stale: computeInputsHash(repoRoot, state.selection, homeDirectory) !== state.inputsHash || loadHarnessState(environment).state.majorMode === state.selection.majorMode && loadPlan.fingerprint !== state.compositionFingerprint,
344
+ stale: computeInputsHash(repoRoot, state.selection, homeDirectory, environment) !== state.inputsHash || loadHarnessState(environment).state.majorMode === state.selection.majorMode && loadPlan.fingerprint !== state.compositionFingerprint,
345
345
  loaded
346
346
  };
347
347
  }
@@ -1 +1 @@
1
- {"version":3,"file":"composition.mjs","names":[],"sources":["../../../../src/builders/cli/composition.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport { loadMajorModesConfig, type MajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { alreadyComposed } from '@agimon-ai/doompi-core/childProcess';\nimport { withExtensionSource } from '@agimon-ai/doompi-ui/extensionName';\nimport { type ExtensionAPI, parseArgs } from '@earendil-works/pi-coding-agent';\n\nimport {\n createHarnessSession,\n getHarnessState,\n type HarnessState,\n loadHarnessState,\n readHarnessState,\n updateHarnessState,\n} from '../../composition/harnessState';\nimport {\n computeInputsHash,\n createMapResolvers,\n readSyncState,\n runDirectory,\n type SyncState,\n} from '../../composition/syncState';\nimport type { HarnessPreset } from '../../composition/types/harness';\nimport { findSyncedRoot, readBundleStatus } from './bootstrapLocator';\nimport { COMPOSED_ENV, EXTERNAL_EXTENSIONS_ENV, MUTE_ENV } from './compositionState';\nimport { PERSONA_ENTRY, packageAttribution, resolveExtensionComposition } from './extensionAssembler';\nimport { configurePreset } from './harnessContext';\n\nexport { findSyncedRoot } from './bootstrapLocator';\nexport { alreadyComposed } from '@agimon-ai/doompi-core/childProcess';\nexport { COMPOSED_ENV, EXTERNAL_EXTENSIONS_ENV, MUTE_ENV } from './compositionState';\n\n/**\n * The runtime half of `doom-pi sync`.\n *\n * Pi's own config carries a single entry, the doom-pi extension, and this is\n * what that entry runs: hydrate the harness environment, apply the startup\n * flags, then load the extension set the matrix selects. It runs again on every\n * `/reload`, which is what lets `/mode` swap the set without a restart,\n * since Pi rebuilds its extension runner from whatever a reload produced.\n */\n\nconst ENABLED_FLAG = '1';\nconst SUBAGENT_AGENT_DIRS_ENV = 'PI_SUBAGENT_EXTRA_AGENT_DIRS';\nconst SUBAGENT_SKILL_DIRS_ENV = 'PI_SUBAGENT_EXTRA_SKILL_DIRS';\nconst DOMAIN_SEPARATOR = ',';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst DOMAIN_APPLY_MODULE = '@agimon-ai/doompi-domain/apply';\nconst SELECTION_SWITCH_MODULE = '@agimon-ai/doompi-config/selectionSwitch';\nconst NOT_SYNCED = 'doompi is configured for Pi but this repository was never synced. Run doompi sync.';\nconst UNUSABLE_STATE_MESSAGE = 'doompi could not read its synchronized state. Run doompi sync.';\nconst TIMING_ENABLED = process.env.DOOMPI_TIMING === ENABLED_FLAG || process.env.PI_TIMING === ENABLED_FLAG;\n\nexport const DOOM_FLAGS = {\n majorMode: 'major-mode',\n domains: 'domains',\n profile: 'profile',\n mute: 'mute',\n} as const;\n\n/** Removed flag, reported rather than ignored when a synced session passes it. */\nexport const REMOVED_LAYER_FLAG = 'layer';\n\nexport interface StartupFlags {\n majorMode?: string;\n domains?: string[];\n profile?: string;\n mute: boolean;\n /** True when the caller passed the removed `--layer` flag. */\n removedLayer: boolean;\n}\n\nexport interface ComposeOutcome {\n /** Everything that went wrong, reported once the session can show messages. */\n problems: string[];\n /** Config changed since the last sync, which is a prompt to re-sync. */\n stale: boolean;\n loaded: string[];\n}\n\nfunction message(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction realPath(target: string): string {\n try {\n return fs.realpathSync(target);\n } catch {\n return path.resolve(target);\n }\n}\n\n/**\n * True when this process was handed the doom extension set on the command line.\n *\n * The launcher and detached Doom Team children both pass the composed list as\n * `--extension` arguments, and Pi merges those with whatever project settings\n * declare. Composing again here would load every extension twice, which Pi\n * reports as each tool and flag conflicting with itself.\n *\n * Matching against the synced paths rather than the mere presence of\n * `--extension` keeps an unrelated one-off, `pi -e ./debug.ts`, from silently\n * costing the user their whole setup.\n */\nexport function extensionsProvidedExternally(argv: string[], resolved: Record<string, string>): boolean {\n const { extensions } = parseArgs(argv);\n if (!extensions || extensions.length === 0) return false;\n const composed = new Set(Object.values(resolved).map(realPath));\n return extensions.some((entry) => composed.has(realPath(entry)));\n}\n\n/**\n * Opens this process's session from what sync pinned.\n *\n * The synced record is decoded underneath the live environment, so an inherited\n * value still wins: a launcher run, a nested session, or a CI override is more\n * specific than anything sync wrote. The result becomes the session's state\n * file, which is the authority from here on; the environment it publishes is\n * for the bash hooks and the packages that can only read one.\n */\nexport async function startSyncedSession(\n state: SyncState,\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<HarnessState> {\n const directory = await prepareRunDirectory(repoRoot, environment);\n const harness: HarnessState = {\n ...readHarnessState({ ...state.env, ...environment }),\n // Recorded separately because no variable carries them.\n ...state.fileState,\n // Every synced process gets private mutable staging. The source content\n // identity is unchanged because the projection fingerprint excludes it.\n mcpProjection: {\n ...state.fileState.mcpProjection,\n stagingDirectory: directory,\n },\n temporaryDirectory: directory,\n };\n createHarnessSession(harness, { directory, environment });\n // The profile's own defaults, which the launcher applies at spawn. They are\n // recorded as a map rather than as variables so /profile can tell which ones\n // it owns when it swaps them out later.\n for (const [key, value] of Object.entries(harness.profileEnvironment)) {\n if (environment[key] === undefined) environment[key] = value;\n }\n return harness;\n}\n\n/**\n * Reads the doom flags off the command line.\n *\n * Pi collects unrecognized flags for extensions but hands their values back\n * only after every extension has loaded, which is too late to decide what to\n * load. Parsing argv with Pi's own parser keeps the value rules identical.\n */\nexport function readStartupFlags(argv: string[]): StartupFlags {\n const { unknownFlags } = parseArgs(argv);\n const text = (name: string): string | undefined => {\n const value = unknownFlags.get(name);\n return typeof value === 'string' && value.trim() ? value.trim() : undefined;\n };\n const domains = text(DOOM_FLAGS.domains);\n return {\n majorMode: text(DOOM_FLAGS.majorMode),\n domains: domains\n ?.split(DOMAIN_SEPARATOR)\n .map((name) => name.trim())\n .filter(Boolean),\n profile: text(DOOM_FLAGS.profile),\n mute: unknownFlags.get(DOOM_FLAGS.mute) !== undefined,\n removedLayer: unknownFlags.get(REMOVED_LAYER_FLAG) !== undefined,\n };\n}\n\n/** Registers the flags so Pi accepts them and lists them in `pi --help`. */\nexport function registerDoomFlags(pi: Pick<ExtensionAPI, 'registerFlag'>): void {\n pi.registerFlag(DOOM_FLAGS.majorMode, {\n type: 'string',\n description: 'Named major mode from .doom/modes.yaml',\n });\n pi.registerFlag(DOOM_FLAGS.domains, { type: 'string', description: 'Comma-separated content domains' });\n pi.registerFlag(DOOM_FLAGS.profile, { type: 'string', description: 'Persona and env from .doom/profiles.yaml' });\n pi.registerFlag(DOOM_FLAGS.mute, { type: 'boolean', description: 'Disable the notification extension' });\n}\n\n/**\n * Applies the startup flags through the same switchers the slash commands use.\n *\n * A bad value is collected rather than thrown: a factory that throws takes the\n * whole doom setup down with it, which is far worse than one unapplied flag.\n */\nexport async function applyStartupFlags(\n flags: StartupFlags,\n majorModesConfig: MajorModesConfig,\n repoRoot: string,\n problems: string[],\n): Promise<void> {\n // Reported rather than ignored: Pi hands unrecognized flags to extensions, so\n // a stale `--layer dev` would otherwise start a session on the wrong mode\n // without saying anything.\n if (flags.removedLayer) problems.push(`--${REMOVED_LAYER_FLAG} was replaced by --${DOOM_FLAGS.majorMode}`);\n if (flags.majorMode) {\n try {\n const { applyMajorMode } = await import(SELECTION_SWITCH_MODULE);\n applyMajorMode(majorModesConfig, flags.majorMode, getHarnessState());\n } catch (error) {\n problems.push(`--${DOOM_FLAGS.majorMode} ${flags.majorMode}: ${message(error)}`);\n }\n }\n if (flags.domains && flags.domains.length > 0) {\n try {\n const { applyDomains } = await import(DOMAIN_APPLY_MODULE);\n await applyDomains(flags.domains, getHarnessState());\n } catch (error) {\n problems.push(`--domains ${flags.domains.join(DOMAIN_SEPARATOR)}: ${message(error)}`);\n }\n }\n if (flags.profile) {\n try {\n const [{ applyProfile }, { resolveProfile }] = await Promise.all([\n import(SELECTION_SWITCH_MODULE),\n import('@agimon-ai/doompi-config/profiles'),\n ]);\n await applyProfile(resolveProfile(repoRoot, flags.profile), getHarnessState());\n } catch (error) {\n problems.push(`--profile ${flags.profile}: ${message(error)}`);\n }\n }\n}\n\n/**\n * Points harness state at a per-process directory before anything writes there.\n *\n * The synced directory is a baseline that every session reads; live switches\n * materialize into this one instead, so a second session in the same repository\n * cannot overwrite what the first is using.\n */\nexport async function prepareRunDirectory(\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<string> {\n const directory = runDirectory(repoRoot, process.pid, environment.HOME ?? os.homedir());\n await fs.promises.mkdir(directory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n // Through the store rather than the environment: the state file is the\n // authority, and a switcher reading a stale directory would write a live\n // session's resources into the wrong place.\n updateHarnessState({ temporaryDirectory: directory }, environment);\n return directory;\n}\n\n/** Removes this session's scratch directory. */\nexport async function cleanupRunDirectory(\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<void> {\n await fs.promises.rm(runDirectory(repoRoot, process.pid, environment.HOME ?? os.homedir()), {\n recursive: true,\n force: true,\n });\n}\n\n/**\n * Builds the ordered list of extensions to load for the current state.\n *\n * Returns entries in actual Pi factory activation order. CLI argument adaptation\n * remains a launcher concern and synchronized bundles preserve this same order.\n */\nexport interface ComposedRuntimeLoadPlan {\n readonly entries: string[];\n readonly fingerprint: string;\n}\n\n/** Builds the canonical synchronized activation plan. */\nexport async function composeRuntimeLoadPlan(\n state: SyncState,\n harness: HarnessState = getHarnessState(),\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<ComposedRuntimeLoadPlan> {\n const repoRoot = harness.root ?? state.root;\n const resolvers = createMapResolvers(state.resolved, state.compiled);\n const context = {\n agents: harness.agents,\n // Exiting when the agent settles is a launcher decision with no synced\n // equivalent: a session started by hand is meant to stay open.\n autoStop: false,\n preset: state.selection.preset,\n personaEntry: resolvers.packageEntry(PERSONA_ENTRY),\n majorMode: harness.majorMode,\n layers: [...harness.layers],\n majorModesConfig: loadMajorModesConfig(repoRoot),\n resolvers,\n };\n\n // A state field, so it goes through the store: Doom Team reads it from the\n // environment the store publishes when it builds a child's spawn snapshot.\n const composition = resolveExtensionComposition({\n ...context,\n mute: environment[MUTE_ENV] === ENABLED_FLAG,\n });\n updateHarnessState(\n {\n childExtensions: [...composition.childActivation],\n compositionFingerprint: composition.fingerprint,\n packageAttribution: packageAttribution(composition),\n },\n environment,\n );\n if (harness.agents) {\n environment[SUBAGENT_AGENT_DIRS_ENV] = harness.agentDirectories.join(path.delimiter);\n environment[SUBAGENT_SKILL_DIRS_ENV] = harness.skillDirectories.join(path.delimiter);\n }\n const entries = [...composition.parentActivation];\n const bundle = state.bundles?.[composition.fingerprint];\n return {\n entries: bundle ? [bundle] : entries,\n fingerprint: composition.fingerprint,\n };\n}\n\n/** Compatibility view for callers that only need Pi extension entries. */\nexport async function composeLoadOrder(\n state: SyncState,\n harness: HarnessState = getHarnessState(),\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<string[]> {\n return (await composeRuntimeLoadPlan(state, harness, environment)).entries;\n}\n\n/**\n * Imports each extension and hands it a source-aware view of the Pi API.\n *\n * Every Pi extension entry is a factory of the same shape, so one extension can\n * activate the rest. The view delegates to the same host API while retaining the\n * entry path for tools registered through a composed loader. Failures are per\n * entry: one unloadable module must not cost the session everything after it.\n */\nexport async function loadComposedExtensions(\n pi: ExtensionAPI,\n entries: string[],\n problems: string[],\n): Promise<string[]> {\n const loaded: string[] = [];\n for (const entry of entries) {\n const importStartedAt = performance.now();\n let module: { default?: unknown };\n try {\n module = (await import(pathToFileURL(entry).href)) as { default?: unknown };\n if (TIMING_ENABLED) {\n process.stderr.write(`[doompi:timing] import ${(performance.now() - importStartedAt).toFixed(1)}ms ${entry}\\n`);\n }\n } catch (error) {\n problems.push(`${entry}: ${message(error)}`);\n continue;\n }\n if (typeof module.default !== 'function') {\n problems.push(`${entry}: does not export an extension factory`);\n continue;\n }\n const factoryStartedAt = performance.now();\n try {\n await (module.default as (api: ExtensionAPI) => void | Promise<void>)(withExtensionSource(pi, entry));\n if (TIMING_ENABLED) {\n process.stderr.write(\n `[doompi:timing] factory ${(performance.now() - factoryStartedAt).toFixed(1)}ms ${entry}\\n`,\n );\n }\n loaded.push(entry);\n } catch (error) {\n problems.push(`${entry}: ${message(error)}`);\n }\n }\n return loaded;\n}\n\nexport interface ComposeOptions {\n cwd?: string;\n argv?: string[];\n environment?: NodeJS.ProcessEnv;\n}\n\n/**\n * Runs the whole synced startup for one Pi session.\n *\n * Never throws: whatever fails is reported through the outcome so the session\n * still starts and can say what is wrong.\n */\nexport async function composeDoomSession(pi: ExtensionAPI, options: ComposeOptions = {}): Promise<ComposeOutcome> {\n const environment = options.environment ?? process.env;\n const problems: string[] = [];\n const homeDirectory = environment.HOME ?? os.homedir();\n const repoRoot = findSyncedRoot(options.cwd ?? process.cwd(), homeDirectory);\n if (!repoRoot) return { problems: [NOT_SYNCED], stale: false, loaded: [] };\n\n let state: SyncState | undefined;\n try {\n state = readSyncState(repoRoot, homeDirectory);\n } catch (error) {\n return { problems: [message(error)], stale: false, loaded: [] };\n }\n if (!state) return { problems: [NOT_SYNCED], stale: false, loaded: [] };\n\n const argv = options.argv ?? process.argv.slice(2);\n // The launcher and Doom Team children compose the set themselves and pass it\n // in, so this entry has nothing left to do but stay out of the way.\n if (\n environment[EXTERNAL_EXTENSIONS_ENV] === ENABLED_FLAG ||\n extensionsProvidedExternally(argv, { ...state.resolved, ...state.bundles })\n ) {\n return { problems: [], stale: false, loaded: [] };\n }\n\n configurePreset({ preset: state.selection.preset as HarnessPreset, piArgs: [] }, environment);\n\n // Only the first load opens a session. A reload finds the pointer this set\n // and reads the file back, which is what carries a live /domains or /profile\n // switch across the reload instead of resetting it to what sync pinned.\n if (!alreadyComposed(environment)) {\n environment[COMPOSED_ENV] = ENABLED_FLAG;\n await startSyncedSession(state, repoRoot, environment);\n const flags = readStartupFlags(argv);\n if (flags.mute) environment[MUTE_ENV] = ENABLED_FLAG;\n await applyStartupFlags(flags, loadMajorModesConfig(repoRoot), repoRoot, problems);\n }\n\n let loadPlan: ComposedRuntimeLoadPlan;\n try {\n // Read back through the store rather than from this process: the startup\n // flags may have switched the layer or the domains a moment ago, and the\n // environment being managed is not always this process's own.\n loadPlan = await composeRuntimeLoadPlan(state, loadHarnessState(environment).state, environment);\n } catch (error) {\n return { problems: [...problems, message(error)], stale: false, loaded: [] };\n }\n\n const selectedBundle = state.bundles?.[loadPlan.fingerprint];\n if (selectedBundle) {\n try {\n const status = readBundleStatus(repoRoot, loadPlan.fingerprint, homeDirectory);\n if (!status.fresh || status.bundle !== selectedBundle) {\n return { problems: [...problems, UNUSABLE_STATE_MESSAGE], stale: false, loaded: [] };\n }\n } catch {\n return { problems: [...problems, UNUSABLE_STATE_MESSAGE], stale: false, loaded: [] };\n }\n }\n\n const loaded = await loadComposedExtensions(pi, loadPlan.entries, problems);\n return {\n problems,\n stale:\n computeInputsHash(repoRoot, state.selection, homeDirectory) !== state.inputsHash ||\n (loadHarnessState(environment).state.majorMode === state.selection.majorMode &&\n loadPlan.fingerprint !== state.compositionFingerprint),\n loaded,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"composition.mjs","names":[],"sources":["../../../../src/builders/cli/composition.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport { loadMajorModesConfig, type MajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { alreadyComposed } from '@agimon-ai/doompi-core/childProcess';\nimport { withExtensionSource } from '@agimon-ai/doompi-ui/extensionName';\nimport { type ExtensionAPI, parseArgs } from '@earendil-works/pi-coding-agent';\n\nimport {\n createHarnessSession,\n getHarnessState,\n type HarnessState,\n loadHarnessState,\n readHarnessState,\n updateHarnessState,\n} from '../../composition/harnessState';\nimport {\n computeInputsHash,\n createMapResolvers,\n readSyncState,\n runDirectory,\n type SyncState,\n} from '../../composition/syncState';\nimport type { HarnessPreset } from '../../composition/types/harness';\nimport { findSyncedRoot, readBundleStatus } from './bootstrapLocator';\nimport { COMPOSED_ENV, EXTERNAL_EXTENSIONS_ENV, MUTE_ENV } from './compositionState';\nimport { PERSONA_ENTRY, packageAttribution, resolveExtensionComposition } from './extensionAssembler';\nimport { configurePreset } from './harnessContext';\n\nexport { findSyncedRoot } from './bootstrapLocator';\nexport { alreadyComposed } from '@agimon-ai/doompi-core/childProcess';\nexport { COMPOSED_ENV, EXTERNAL_EXTENSIONS_ENV, MUTE_ENV } from './compositionState';\n\n/**\n * The runtime half of `doom-pi sync`.\n *\n * Pi's own config carries a single entry, the doom-pi extension, and this is\n * what that entry runs: hydrate the harness environment, apply the startup\n * flags, then load the extension set the matrix selects. It runs again on every\n * `/reload`, which is what lets `/mode` swap the set without a restart,\n * since Pi rebuilds its extension runner from whatever a reload produced.\n */\n\nconst ENABLED_FLAG = '1';\nconst SUBAGENT_AGENT_DIRS_ENV = 'PI_SUBAGENT_EXTRA_AGENT_DIRS';\nconst SUBAGENT_SKILL_DIRS_ENV = 'PI_SUBAGENT_EXTRA_SKILL_DIRS';\nconst DOMAIN_SEPARATOR = ',';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst DOMAIN_APPLY_MODULE = '@agimon-ai/doompi-domain/apply';\nconst SELECTION_SWITCH_MODULE = '@agimon-ai/doompi-config/selectionSwitch';\nconst NOT_SYNCED = 'doompi is configured for Pi but this repository was never synced. Run doompi sync.';\nconst UNUSABLE_STATE_MESSAGE = 'doompi could not read its synchronized state. Run doompi sync.';\nconst TIMING_ENABLED = process.env.DOOMPI_TIMING === ENABLED_FLAG || process.env.PI_TIMING === ENABLED_FLAG;\n\nexport const DOOM_FLAGS = {\n majorMode: 'major-mode',\n domains: 'domains',\n profile: 'profile',\n mute: 'mute',\n} as const;\n\n/** Removed flag, reported rather than ignored when a synced session passes it. */\nexport const REMOVED_LAYER_FLAG = 'layer';\n\nexport interface StartupFlags {\n majorMode?: string;\n domains?: string[];\n profile?: string;\n mute: boolean;\n /** True when the caller passed the removed `--layer` flag. */\n removedLayer: boolean;\n}\n\nexport interface ComposeOutcome {\n /** Everything that went wrong, reported once the session can show messages. */\n problems: string[];\n /** Config changed since the last sync, which is a prompt to re-sync. */\n stale: boolean;\n loaded: string[];\n}\n\nfunction message(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction realPath(target: string): string {\n try {\n return fs.realpathSync(target);\n } catch {\n return path.resolve(target);\n }\n}\n\n/**\n * True when this process was handed the doom extension set on the command line.\n *\n * The launcher and detached Doom Team children both pass the composed list as\n * `--extension` arguments, and Pi merges those with whatever project settings\n * declare. Composing again here would load every extension twice, which Pi\n * reports as each tool and flag conflicting with itself.\n *\n * Matching against the synced paths rather than the mere presence of\n * `--extension` keeps an unrelated one-off, `pi -e ./debug.ts`, from silently\n * costing the user their whole setup.\n */\nexport function extensionsProvidedExternally(argv: string[], resolved: Record<string, string>): boolean {\n const { extensions } = parseArgs(argv);\n if (!extensions || extensions.length === 0) return false;\n const composed = new Set(Object.values(resolved).map(realPath));\n return extensions.some((entry) => composed.has(realPath(entry)));\n}\n\n/**\n * Opens this process's session from what sync pinned.\n *\n * The synced record is decoded underneath the live environment, so an inherited\n * value still wins: a launcher run, a nested session, or a CI override is more\n * specific than anything sync wrote. The result becomes the session's state\n * file, which is the authority from here on; the environment it publishes is\n * for the bash hooks and the packages that can only read one.\n */\nexport async function startSyncedSession(\n state: SyncState,\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<HarnessState> {\n const directory = await prepareRunDirectory(repoRoot, environment);\n const harness: HarnessState = {\n ...readHarnessState({ ...state.env, ...environment }),\n // Recorded separately because no variable carries them.\n ...state.fileState,\n // Every synced process gets private mutable staging. The source content\n // identity is unchanged because the projection fingerprint excludes it.\n mcpProjection: {\n ...state.fileState.mcpProjection,\n stagingDirectory: directory,\n },\n temporaryDirectory: directory,\n };\n createHarnessSession(harness, { directory, environment });\n // The profile's own defaults, which the launcher applies at spawn. They are\n // recorded as a map rather than as variables so /profile can tell which ones\n // it owns when it swaps them out later.\n for (const [key, value] of Object.entries(harness.profileEnvironment)) {\n if (environment[key] === undefined) environment[key] = value;\n }\n return harness;\n}\n\n/**\n * Reads the doom flags off the command line.\n *\n * Pi collects unrecognized flags for extensions but hands their values back\n * only after every extension has loaded, which is too late to decide what to\n * load. Parsing argv with Pi's own parser keeps the value rules identical.\n */\nexport function readStartupFlags(argv: string[]): StartupFlags {\n const { unknownFlags } = parseArgs(argv);\n const text = (name: string): string | undefined => {\n const value = unknownFlags.get(name);\n return typeof value === 'string' && value.trim() ? value.trim() : undefined;\n };\n const domains = text(DOOM_FLAGS.domains);\n return {\n majorMode: text(DOOM_FLAGS.majorMode),\n domains: domains\n ?.split(DOMAIN_SEPARATOR)\n .map((name) => name.trim())\n .filter(Boolean),\n profile: text(DOOM_FLAGS.profile),\n mute: unknownFlags.get(DOOM_FLAGS.mute) !== undefined,\n removedLayer: unknownFlags.get(REMOVED_LAYER_FLAG) !== undefined,\n };\n}\n\n/** Registers the flags so Pi accepts them and lists them in `pi --help`. */\nexport function registerDoomFlags(pi: Pick<ExtensionAPI, 'registerFlag'>): void {\n pi.registerFlag(DOOM_FLAGS.majorMode, {\n type: 'string',\n description: 'Named major mode from .doom/modes.yaml',\n });\n pi.registerFlag(DOOM_FLAGS.domains, { type: 'string', description: 'Comma-separated content domains' });\n pi.registerFlag(DOOM_FLAGS.profile, { type: 'string', description: 'Persona and env from .doom/profiles.yaml' });\n pi.registerFlag(DOOM_FLAGS.mute, { type: 'boolean', description: 'Disable the notification extension' });\n}\n\n/**\n * Applies the startup flags through the same switchers the slash commands use.\n *\n * A bad value is collected rather than thrown: a factory that throws takes the\n * whole doom setup down with it, which is far worse than one unapplied flag.\n */\nexport async function applyStartupFlags(\n flags: StartupFlags,\n majorModesConfig: MajorModesConfig,\n repoRoot: string,\n problems: string[],\n): Promise<void> {\n // Reported rather than ignored: Pi hands unrecognized flags to extensions, so\n // a stale `--layer dev` would otherwise start a session on the wrong mode\n // without saying anything.\n if (flags.removedLayer) problems.push(`--${REMOVED_LAYER_FLAG} was replaced by --${DOOM_FLAGS.majorMode}`);\n if (flags.majorMode) {\n try {\n const { applyMajorMode } = await import(SELECTION_SWITCH_MODULE);\n applyMajorMode(majorModesConfig, flags.majorMode, getHarnessState());\n } catch (error) {\n problems.push(`--${DOOM_FLAGS.majorMode} ${flags.majorMode}: ${message(error)}`);\n }\n }\n if (flags.domains && flags.domains.length > 0) {\n try {\n const { applyDomains } = await import(DOMAIN_APPLY_MODULE);\n await applyDomains(flags.domains, getHarnessState());\n } catch (error) {\n problems.push(`--domains ${flags.domains.join(DOMAIN_SEPARATOR)}: ${message(error)}`);\n }\n }\n if (flags.profile) {\n try {\n const [{ applyProfile }, { resolveProfile }] = await Promise.all([\n import(SELECTION_SWITCH_MODULE),\n import('@agimon-ai/doompi-config/profiles'),\n ]);\n await applyProfile(resolveProfile(repoRoot, flags.profile), getHarnessState());\n } catch (error) {\n problems.push(`--profile ${flags.profile}: ${message(error)}`);\n }\n }\n}\n\n/**\n * Points harness state at a per-process directory before anything writes there.\n *\n * The synced directory is a baseline that every session reads; live switches\n * materialize into this one instead, so a second session in the same repository\n * cannot overwrite what the first is using.\n */\nexport async function prepareRunDirectory(\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<string> {\n const directory = runDirectory(repoRoot, process.pid, environment.HOME ?? os.homedir());\n await fs.promises.mkdir(directory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n // Through the store rather than the environment: the state file is the\n // authority, and a switcher reading a stale directory would write a live\n // session's resources into the wrong place.\n updateHarnessState({ temporaryDirectory: directory }, environment);\n return directory;\n}\n\n/** Removes this session's scratch directory. */\nexport async function cleanupRunDirectory(\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<void> {\n await fs.promises.rm(runDirectory(repoRoot, process.pid, environment.HOME ?? os.homedir()), {\n recursive: true,\n force: true,\n });\n}\n\n/**\n * Builds the ordered list of extensions to load for the current state.\n *\n * Returns entries in actual Pi factory activation order. CLI argument adaptation\n * remains a launcher concern and synchronized bundles preserve this same order.\n */\nexport interface ComposedRuntimeLoadPlan {\n readonly entries: string[];\n readonly fingerprint: string;\n}\n\n/** Builds the canonical synchronized activation plan. */\nexport async function composeRuntimeLoadPlan(\n state: SyncState,\n harness: HarnessState = getHarnessState(),\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<ComposedRuntimeLoadPlan> {\n const repoRoot = harness.root ?? state.root;\n const resolvers = createMapResolvers(state.resolved, state.compiled);\n const context = {\n agents: harness.agents,\n // Exiting when the agent settles is a launcher decision with no synced\n // equivalent: a session started by hand is meant to stay open.\n autoStop: false,\n preset: state.selection.preset,\n personaEntry: resolvers.packageEntry(PERSONA_ENTRY),\n majorMode: harness.majorMode,\n layers: [...harness.layers],\n majorModesConfig: loadMajorModesConfig(repoRoot, environment.HOME ?? os.homedir(), environment),\n resolvers,\n };\n\n // A state field, so it goes through the store: Doom Team reads it from the\n // environment the store publishes when it builds a child's spawn snapshot.\n const composition = resolveExtensionComposition({\n ...context,\n mute: environment[MUTE_ENV] === ENABLED_FLAG,\n });\n updateHarnessState(\n {\n childExtensions: [...composition.childActivation],\n compositionFingerprint: composition.fingerprint,\n packageAttribution: packageAttribution(composition),\n },\n environment,\n );\n if (harness.agents) {\n environment[SUBAGENT_AGENT_DIRS_ENV] = harness.agentDirectories.join(path.delimiter);\n environment[SUBAGENT_SKILL_DIRS_ENV] = harness.skillDirectories.join(path.delimiter);\n }\n const entries = [...composition.parentActivation];\n const bundle = state.bundles?.[composition.fingerprint];\n return {\n entries: bundle ? [bundle] : entries,\n fingerprint: composition.fingerprint,\n };\n}\n\n/** Compatibility view for callers that only need Pi extension entries. */\nexport async function composeLoadOrder(\n state: SyncState,\n harness: HarnessState = getHarnessState(),\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<string[]> {\n return (await composeRuntimeLoadPlan(state, harness, environment)).entries;\n}\n\n/**\n * Imports each extension and hands it a source-aware view of the Pi API.\n *\n * Every Pi extension entry is a factory of the same shape, so one extension can\n * activate the rest. The view delegates to the same host API while retaining the\n * entry path for tools registered through a composed loader. Failures are per\n * entry: one unloadable module must not cost the session everything after it.\n */\nexport async function loadComposedExtensions(\n pi: ExtensionAPI,\n entries: string[],\n problems: string[],\n): Promise<string[]> {\n const loaded: string[] = [];\n for (const entry of entries) {\n const importStartedAt = performance.now();\n let module: { default?: unknown };\n try {\n module = (await import(pathToFileURL(entry).href)) as { default?: unknown };\n if (TIMING_ENABLED) {\n process.stderr.write(`[doompi:timing] import ${(performance.now() - importStartedAt).toFixed(1)}ms ${entry}\\n`);\n }\n } catch (error) {\n problems.push(`${entry}: ${message(error)}`);\n continue;\n }\n if (typeof module.default !== 'function') {\n problems.push(`${entry}: does not export an extension factory`);\n continue;\n }\n const factoryStartedAt = performance.now();\n try {\n await (module.default as (api: ExtensionAPI) => void | Promise<void>)(withExtensionSource(pi, entry));\n if (TIMING_ENABLED) {\n process.stderr.write(\n `[doompi:timing] factory ${(performance.now() - factoryStartedAt).toFixed(1)}ms ${entry}\\n`,\n );\n }\n loaded.push(entry);\n } catch (error) {\n problems.push(`${entry}: ${message(error)}`);\n }\n }\n return loaded;\n}\n\nexport interface ComposeOptions {\n cwd?: string;\n argv?: string[];\n environment?: NodeJS.ProcessEnv;\n}\n\n/**\n * Runs the whole synced startup for one Pi session.\n *\n * Never throws: whatever fails is reported through the outcome so the session\n * still starts and can say what is wrong.\n */\nexport async function composeDoomSession(pi: ExtensionAPI, options: ComposeOptions = {}): Promise<ComposeOutcome> {\n const environment = options.environment ?? process.env;\n const problems: string[] = [];\n const homeDirectory = environment.HOME ?? os.homedir();\n const repoRoot = findSyncedRoot(options.cwd ?? process.cwd(), homeDirectory);\n if (!repoRoot) return { problems: [NOT_SYNCED], stale: false, loaded: [] };\n\n let state: SyncState | undefined;\n try {\n state = readSyncState(repoRoot, homeDirectory);\n } catch (error) {\n return { problems: [message(error)], stale: false, loaded: [] };\n }\n if (!state) return { problems: [NOT_SYNCED], stale: false, loaded: [] };\n\n const argv = options.argv ?? process.argv.slice(2);\n // The launcher and Doom Team children compose the set themselves and pass it\n // in, so this entry has nothing left to do but stay out of the way.\n if (\n environment[EXTERNAL_EXTENSIONS_ENV] === ENABLED_FLAG ||\n extensionsProvidedExternally(argv, { ...state.resolved, ...state.bundles })\n ) {\n return { problems: [], stale: false, loaded: [] };\n }\n\n configurePreset({ preset: state.selection.preset as HarnessPreset, piArgs: [] }, environment);\n\n // Only the first load opens a session. A reload finds the pointer this set\n // and reads the file back, which is what carries a live /domains or /profile\n // switch across the reload instead of resetting it to what sync pinned.\n if (!alreadyComposed(environment)) {\n environment[COMPOSED_ENV] = ENABLED_FLAG;\n await startSyncedSession(state, repoRoot, environment);\n const flags = readStartupFlags(argv);\n if (flags.mute) environment[MUTE_ENV] = ENABLED_FLAG;\n await applyStartupFlags(flags, loadMajorModesConfig(repoRoot, homeDirectory, environment), repoRoot, problems);\n }\n\n let loadPlan: ComposedRuntimeLoadPlan;\n try {\n // Read back through the store rather than from this process: the startup\n // flags may have switched the layer or the domains a moment ago, and the\n // environment being managed is not always this process's own.\n loadPlan = await composeRuntimeLoadPlan(state, loadHarnessState(environment).state, environment);\n } catch (error) {\n return { problems: [...problems, message(error)], stale: false, loaded: [] };\n }\n\n const selectedBundle = state.bundles?.[loadPlan.fingerprint];\n if (selectedBundle) {\n try {\n const status = readBundleStatus(repoRoot, loadPlan.fingerprint, homeDirectory);\n if (!status.fresh || status.bundle !== selectedBundle) {\n return { problems: [...problems, UNUSABLE_STATE_MESSAGE], stale: false, loaded: [] };\n }\n } catch {\n return { problems: [...problems, UNUSABLE_STATE_MESSAGE], stale: false, loaded: [] };\n }\n }\n\n const loaded = await loadComposedExtensions(pi, loadPlan.entries, problems);\n return {\n problems,\n stale:\n computeInputsHash(repoRoot, state.selection, homeDirectory, environment) !== state.inputsHash ||\n (loadHarnessState(environment).state.majorMode === state.selection.majorMode &&\n loadPlan.fingerprint !== state.compositionFingerprint),\n loaded,\n };\n}\n"],"mappings":"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"}
@@ -58,7 +58,7 @@ function resolveHarnessProfile(options) {
58
58
  async function buildHarnessContext(options, telemetry = (0, _agimon_ai_doompi_core_runtimeLogSinkTelemetry.createHarnessTelemetry)()) {
59
59
  const configRoot = options.configRoot ?? options.repoRoot;
60
60
  const configured = await telemetry.runInSpan(`doom_pi.${RESOLVE_CONFIG_PHASE}`, { "harness.phase": RESOLVE_CONFIG_PHASE }, async () => {
61
- const majorModesConfig = (0, _agimon_ai_doompi_config_majorModes.loadMajorModesConfig)(configRoot, options.homeDirectory);
61
+ const majorModesConfig = (0, _agimon_ai_doompi_config_majorModes.loadMajorModesConfig)(configRoot, options.homeDirectory, options.environment);
62
62
  const selectedLayers = (0, _agimon_ai_doompi_config_majorModes.filterHookDisabledLayers)(majorModesConfig, (0, _agimon_ai_doompi_config_majorModes.resolveLayers)(majorModesConfig, options.majorMode), options.hooks);
63
63
  const profile = resolveHarnessProfile(options);
64
64
  if (!node_fs.default.existsSync(options.cwd) || !node_fs.default.statSync(options.cwd).isDirectory()) throw new Error(`Working directory does not exist: ${options.cwd}`);
@@ -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 = { ...(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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"}
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, options.environment);\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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"}
@@ -53,7 +53,7 @@ function resolveHarnessProfile(options) {
53
53
  async function buildHarnessContext(options, telemetry = createHarnessTelemetry()) {
54
54
  const configRoot = options.configRoot ?? options.repoRoot;
55
55
  const configured = await telemetry.runInSpan(`doom_pi.${RESOLVE_CONFIG_PHASE}`, { "harness.phase": RESOLVE_CONFIG_PHASE }, async () => {
56
- const majorModesConfig = loadMajorModesConfig(configRoot, options.homeDirectory);
56
+ const majorModesConfig = loadMajorModesConfig(configRoot, options.homeDirectory, options.environment);
57
57
  const selectedLayers = filterHookDisabledLayers(majorModesConfig, resolveLayers(majorModesConfig, options.majorMode), options.hooks);
58
58
  const profile = resolveHarnessProfile(options);
59
59
  if (!fs.existsSync(options.cwd) || !fs.statSync(options.cwd).isDirectory()) throw new Error(`Working directory does not exist: ${options.cwd}`);