@agimon-ai/doompi 0.0.1-alpha.103 → 0.0.1-alpha.106
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/builders/cli/harnessContext.cjs +1 -1
- package/dist/src/builders/cli/harnessContext.cjs.map +1 -1
- package/dist/src/builders/cli/harnessContext.mjs +1 -1
- package/dist/src/builders/cli/harnessContext.mjs.map +1 -1
- package/dist/src/builders/server/runtime.cjs +25 -9
- package/dist/src/builders/server/runtime.cjs.map +1 -1
- package/dist/src/builders/server/runtime.mjs +25 -9
- package/dist/src/builders/server/runtime.mjs.map +1 -1
- package/dist/src/builders/server/runtimeResources.cjs +72 -0
- package/dist/src/builders/server/runtimeResources.cjs.map +1 -0
- package/dist/src/builders/server/runtimeResources.mjs +71 -0
- package/dist/src/builders/server/runtimeResources.mjs.map +1 -0
- package/dist/src/cli/harnessOptions.cjs +2 -1
- package/dist/src/cli/harnessOptions.cjs.map +1 -1
- package/dist/src/cli/harnessOptions.mjs +2 -1
- package/dist/src/cli/harnessOptions.mjs.map +1 -1
- package/dist/src/composition/layerPackageInstaller/index.cjs +4 -2
- package/dist/src/composition/layerPackageInstaller/index.cjs.map +1 -1
- package/dist/src/composition/layerPackageInstaller/index.d.mts.map +1 -1
- package/dist/src/composition/layerPackageInstaller/index.mjs +4 -2
- package/dist/src/composition/layerPackageInstaller/index.mjs.map +1 -1
- package/dist/src/composition/types/harness.d.mts +2 -0
- package/dist/src/composition/types/harness.d.mts.map +1 -1
- package/package.json +18 -18
- package/dist/src/builders/server/executionBudget.cjs +0 -64
- package/dist/src/builders/server/executionBudget.cjs.map +0 -1
- package/dist/src/builders/server/executionBudget.mjs +0 -64
- package/dist/src/builders/server/executionBudget.mjs.map +0 -1
|
@@ -103,7 +103,7 @@ async function buildHarnessContext(options, telemetry = (0, _agimon_ai_doompi_co
|
|
|
103
103
|
});
|
|
104
104
|
try {
|
|
105
105
|
const defaultThemePath = await (0, _agimon_ai_doompi_ui_theme.writeDefaultTheme)(resources.temporaryDirectory);
|
|
106
|
-
const environment = { ...process.env };
|
|
106
|
+
const environment = { ...options.environment ?? process.env };
|
|
107
107
|
delete environment[_agimon_ai_doompi_core_mcpSession.DOOM_MCP_SESSION_ENV_VAR];
|
|
108
108
|
const profileEnvironment = (0, _agimon_ai_doompi_config_profiles.applyProfileEnvironment)(environment, profile?.env ?? {});
|
|
109
109
|
configurePreset(options, environment);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harnessContext.cjs","names":["resolveProfile","createHarnessTelemetry","loadMajorModesConfig","filterHookDisabledLayers","resolveLayers","fs","layerHookGroups","materializePluginEntries","resolvePluginEntries","resolveMcpAllowlist","resolveSharedSkills","HARNESS_EVENT","collectResources","path","resolveSyncLocation","os","globalDoomConfigDirectory","writeDefaultTheme","DOOM_MCP_SESSION_ENV_VAR","applyProfileEnvironment","DEFAULT_THEME_NAME","packageEntry","PERSONA_ENTRY","buildPersonaPrompt"],"sources":["../../../../src/builders/cli/harnessContext.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { globalDoomConfigDirectory } from '@agimon-ai/doompi-config/config';\nimport { resolvePluginEntries, resolveSharedSkills } from '@agimon-ai/doompi-config/domains';\nimport type { DomainMcpAllowlist, PluginEntry } from '@agimon-ai/doompi-config/domains';\nimport {\n filterHookDisabledLayers,\n layerHookGroups,\n loadMajorModesConfig,\n resolveLayers,\n} from '@agimon-ai/doompi-config/majorModes';\nimport type { MajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { applyProfileEnvironment, buildPersonaPrompt, resolveProfile } from '@agimon-ai/doompi-config/profiles';\nimport type { AgentProfile } from '@agimon-ai/doompi-config/profiles';\nimport { DOOM_MCP_SESSION_ENV_VAR } from '@agimon-ai/doompi-core/mcpSession';\nimport { packageEntry } from '@agimon-ai/doompi-core/moduleResolution';\nimport {\n createHarnessTelemetry,\n HARNESS_EVENT,\n type HarnessTelemetry,\n} from '@agimon-ai/doompi-core/runtimeLogSinkTelemetry';\nimport { resolveSyncLocation } from '@agimon-ai/doompi-core/syncLocation';\nimport { resolveMcpAllowlist } from '@agimon-ai/doompi-domain/mcp';\nimport { materializePluginEntries } from '@agimon-ai/doompi-domain/plugins';\nimport { collectResources, type HarnessResources } from '@agimon-ai/doompi-domain/resources';\nimport { DEFAULT_THEME_NAME, writeDefaultTheme } from '@agimon-ai/doompi-ui/theme';\n\nimport { createHarnessSession } from '../../composition/harnessState';\nimport type { HarnessOptions } from '../../composition/types/harness';\nimport { PERSONA_ENTRY } from './extensionAssembler';\n\nconst OLLAMA_PRESET = 'ollama';\nconst PRIVATE_FILE_MODE = 0o600;\nconst RESOLVE_CONFIG_PHASE = 'resolve_config';\nconst COLLECT_RESOURCES_PHASE = 'collect_resources';\nconst BUILD_CONTEXT_PHASE = 'build_context';\n\n/**\n * Everything a command needs, resolved once.\n *\n * All three commands need the same expensive preparation (domains resolved,\n * plugins collected into a temp directory, environment assembled), so it is\n * built here and handed to whichever command runs.\n */\nexport interface HarnessContext {\n options: HarnessOptions;\n environment: NodeJS.ProcessEnv;\n resources: HarnessResources;\n plugins: PluginEntry[];\n profile?: string;\n personaDirectory?: string;\n /** Root personaDirectory resolves against, when a profile supplied one. */\n personaRoot?: string;\n mcpAllowlist?: DomainMcpAllowlist;\n majorModesConfig: MajorModesConfig;\n selectedLayers: string[];\n hookGroups: string[];\n personaEntry?: string;\n defaultThemePath: string;\n cleanup(): Promise<void>;\n}\n\n/**\n * Applies the provider environment a preset needs.\n *\n * Exported because the synced Pi path has no launcher to do it: the doom-pi\n * extension applies the same rules in-process before anything reads them.\n * `piArgs` is only consulted to spot a selected cloud model.\n */\nexport function configurePreset(\n options: Pick<HarnessOptions, 'preset' | 'piArgs'>,\n environment: NodeJS.ProcessEnv,\n): void {\n if (options.preset === 'kimi') {\n if (!environment.KIMI_API_KEY && environment.KIMI_CODE_API_KEY) {\n environment.KIMI_API_KEY = environment.KIMI_CODE_API_KEY;\n }\n if (!environment.KIMI_API_KEY) {\n throw new Error('KIMI_API_KEY or KIMI_CODE_API_KEY is required for the kimi preset');\n }\n }\n if (options.preset === OLLAMA_PRESET) {\n const selected = options.piArgs.find((argument) => argument.startsWith(`${OLLAMA_PRESET}/`));\n const cloud = selected?.endsWith(':cloud') ?? false;\n if (cloud && !environment.OLLAMA_API_KEY) {\n throw new Error('OLLAMA_API_KEY is required for Ollama cloud models');\n }\n environment.OLLAMA_API_KEY ||= OLLAMA_PRESET;\n environment.DOOMPI_OLLAMA_BASE_URL =\n environment.OLLAMA_BASE_URL ?? environment.ANTHROPIC_BASE_URL ?? 'http://localhost:11434/v1';\n }\n}\n\n/** Resolves the optional persona and environment profile selected for this run. */\nexport function resolveHarnessProfile(options: HarnessOptions): AgentProfile | undefined {\n return options.profile ? resolveProfile(options.configRoot ?? options.repoRoot, options.profile) : undefined;\n}\n\n/**\n * Resolves the full matrix and stages every resource Pi will need.\n *\n * The caller owns the returned cleanup(): the temporary directory holding\n * skills, agents, and the generated MCP config has to outlive this function\n * and be removed once the selected command is done with it.\n */\nexport async function buildHarnessContext(\n options: HarnessOptions,\n telemetry: HarnessTelemetry = createHarnessTelemetry(),\n): Promise<HarnessContext> {\n // A worktree reads its workspace's configuration; resources still come from the checkout.\n const configRoot = options.configRoot ?? options.repoRoot;\n // Everything up to collectResources reads configuration off disk, so a single\n // malformed modes.yaml or profiles.yaml surfaces here as a failed launch\n // with no other record.\n const configured = await telemetry\n .runInSpan(`doom_pi.${RESOLVE_CONFIG_PHASE}`, { 'harness.phase': RESOLVE_CONFIG_PHASE }, async () => {\n const majorModesConfig = loadMajorModesConfig(configRoot, options.homeDirectory);\n const selectedLayers = filterHookDisabledLayers(\n majorModesConfig,\n resolveLayers(majorModesConfig, options.majorMode),\n options.hooks,\n );\n const profile = resolveHarnessProfile(options);\n if (!fs.existsSync(options.cwd) || !fs.statSync(options.cwd).isDirectory()) {\n throw new Error(`Working directory does not exist: ${options.cwd}`);\n }\n return {\n majorModesConfig,\n selectedLayers,\n hookGroups: layerHookGroups(majorModesConfig, selectedLayers),\n profile,\n plugins: await materializePluginEntries(\n resolvePluginEntries(configRoot, options.domains, options.pluginDirectories),\n ),\n mcpAllowlist: resolveMcpAllowlist(configRoot, options.domains),\n sharedSkills: resolveSharedSkills(configRoot, options.domains),\n };\n })\n .catch(async (error: unknown) => {\n await telemetry.recordError(HARNESS_EVENT.configLoadFailed, error, {\n 'harness.phase': RESOLVE_CONFIG_PHASE,\n 'harness.major_mode': options.majorMode,\n 'harness.domain_count': options.domains.length,\n });\n throw error;\n });\n\n const { majorModesConfig, selectedLayers, hookGroups, profile, plugins, mcpAllowlist } = configured;\n const personaDirectory = profile?.persona;\n // A global profile's persona lives beside the global config, not in the repo.\n const personaRoot = profile?.personaRoot ?? configRoot;\n const resources = await telemetry\n .runInSpan(\n `doom_pi.${COLLECT_RESOURCES_PHASE}`,\n { 'harness.plugin.count': plugins.length, 'harness.agents': options.agents, 'harness.mcp': options.mcp },\n () =>\n collectResources(options.repoRoot, plugins, {\n agents: options.agents,\n mcp: options.mcp,\n // Set by `doom-pi sync`, which stages into a directory that outlives\n // the process rather than a temporary one.\n temporaryDirectory: options.resourceDirectory,\n skillCacheDirectory: path.join(\n options.resourceDirectory ??\n resolveSyncLocation(options.repoRoot, options.homeDirectory ?? os.homedir()).directory,\n 'cache',\n 'skills',\n ),\n mcpAllowlist,\n sharedSkills: configured.sharedSkills,\n pluginDataRoot: path.join(globalDoomConfigDirectory(options.homeDirectory ?? os.homedir()), 'plugin-data'),\n }),\n )\n .catch(async (error: unknown) => {\n // Conflicting skills, agents, or MCP servers all fail here, and the plugin\n // that caused it is only identifiable from the message.\n await telemetry.recordError(HARNESS_EVENT.resourceCollectionFailed, error, {\n 'harness.phase': COLLECT_RESOURCES_PHASE,\n 'harness.plugin.count': plugins.length,\n });\n throw error;\n });\n\n try {\n const defaultThemePath = await writeDefaultTheme(resources.temporaryDirectory);\n const environment = { ...process.env };\n // The file-backed projection published through Cordis is authoritative.\n // Never let a legacy parent-process projection leak into this Doom session.\n delete environment[DOOM_MCP_SESSION_ENV_VAR];\n const profileEnvironment = applyProfileEnvironment(environment, profile?.env ?? {});\n\n configurePreset(options, environment);\n environment.NX_DAEMON ??= 'false';\n environment.DOOMPI_THEME ||= DEFAULT_THEME_NAME;\n environment.CLAUDE_PROJECT_DIR = options.repoRoot;\n environment.CODEX_REPO_ROOT = options.repoRoot;\n environment.ORIGINAL_REPO_PATH ||= options.repoRoot;\n environment.MCP_UI_VIEWER = options.automation ? 'none' : (environment.MCP_UI_VIEWER ?? '');\n\n // Always loaded, even with no persona at launch: the extension no-ops\n // without a persona file, and /profile needs it present to load one later.\n // Pi freezes the --extension list at startup, so it cannot be added on demand.\n const personaEntry = packageEntry(PERSONA_ENTRY, import.meta.url);\n const personaPrompt = personaDirectory ? buildPersonaPrompt(personaRoot, personaDirectory) : undefined;\n let personaFile: string | undefined;\n if (personaPrompt) {\n personaFile = path.join(resources.temporaryDirectory, 'persona.md');\n await fs.promises.writeFile(personaFile, `${personaPrompt}\\n`, { mode: PRIVATE_FILE_MODE });\n }\n\n // One write, describing the child rather than this process. The state file\n // is the child's authority and the environment is what it projects for the\n // hooks and packages that can only read an environment.\n createHarnessSession(\n {\n root: options.repoRoot,\n majorMode: options.majorMode,\n temporaryDirectory: resources.temporaryDirectory,\n domains: options.domains,\n layers: selectedLayers,\n profile: profile?.name,\n profileIdentity: profile?.identity,\n profileVoice: profile?.voice,\n profileEnvironment,\n personaFile,\n skillDirectories: resources.skillDirectories,\n agentDirectories: resources.agentDirectories,\n additionalDirectories: options.additionalDirectories,\n childExtensions: [],\n pluginDirectories: options.pluginDirectories,\n pluginHooks: resources.pluginHooks,\n hookGroups,\n hooks: options.hooks,\n agents: options.agents,\n mcp: options.mcp,\n mcpConfigPath: resources.mcpConfigPath,\n mcpProjection: resources.mcpProjection,\n allowProtectedWrites: options.allowProtectedWrites,\n },\n { directory: resources.temporaryDirectory, environment },\n );\n if (options.agents) {\n environment.PI_SUBAGENT_EXTRA_AGENT_DIRS = resources.agentDirectories.join(path.delimiter);\n environment.PI_SUBAGENT_EXTRA_SKILL_DIRS = resources.skillDirectories.join(path.delimiter);\n environment.PI_SUBAGENT_PI_BINARY = path.join(options.repoRoot, 'pi.sh');\n }\n\n // Core hook groups load regardless; hookGroups selects the rest. The initial\n // state write includes an empty list deliberately: unset means every group.\n\n return {\n options,\n environment,\n resources,\n plugins,\n profile: profile?.name,\n personaDirectory,\n personaRoot,\n mcpAllowlist,\n majorModesConfig,\n selectedLayers,\n hookGroups,\n personaEntry,\n defaultThemePath,\n cleanup: resources.cleanup.bind(resources),\n };\n } catch (error) {\n // Preparation failed after the temp directory existed, so nothing will be\n // handed back to clean it up.\n await telemetry.recordError(HARNESS_EVENT.contextBuildFailed, error, {\n 'harness.phase': BUILD_CONTEXT_PHASE,\n 'harness.major_mode': options.majorMode,\n ...(profile?.name ? { 'harness.profile': profile.name } : {}),\n });\n await resources.cleanup();\n throw error;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAiCA,MAAM,gBAAgB;AACtB,MAAM,oBAAoB;AAC1B,MAAM,uBAAuB;AAC7B,MAAM,0BAA0B;AAChC,MAAM,sBAAsB;;;;;;;;AAkC5B,SAAgB,gBACd,SACA,aACM;CACN,IAAI,QAAQ,WAAW,QAAQ;EAC7B,IAAI,CAAC,YAAY,gBAAgB,YAAY,mBAC3C,YAAY,eAAe,YAAY;EAEzC,IAAI,CAAC,YAAY,cACf,MAAM,IAAI,MAAM,mEAAmE;CAEvF;CACA,IAAI,QAAQ,WAAW,eAAe;EAGpC,KAFiB,QAAQ,OAAO,MAAM,aAAa,SAAS,WAAW,GAAG,cAAc,EAAE,CACrE,CAAC,EAAE,SAAS,QAAQ,KAAK,UACjC,CAAC,YAAY,gBACxB,MAAM,IAAI,MAAM,oDAAoD;EAEtE,YAAY,mBAAmB;EAC/B,YAAY,yBACV,YAAY,mBAAmB,YAAY,sBAAsB;CACrE;AACF;;AAGA,SAAgB,sBAAsB,SAAmD;CACvF,OAAO,QAAQ,WAAA,GAAUA,kCAAAA,eAAAA,CAAe,QAAQ,cAAc,QAAQ,UAAU,QAAQ,OAAO,IAAI,KAAA;AACrG;;;;;;;;AASA,eAAsB,oBACpB,SACA,aAAA,GAA8BC,+CAAAA,uBAAAA,CAAuB,GAC5B;CAEzB,MAAM,aAAa,QAAQ,cAAc,QAAQ;CAIjD,MAAM,aAAa,MAAM,UACtB,UAAU,WAAW,wBAAwB,EAAE,iBAAiB,qBAAqB,GAAG,YAAY;EACnG,MAAM,oBAAA,GAAmBC,oCAAAA,qBAAAA,CAAqB,YAAY,QAAQ,aAAa;EAC/E,MAAM,kBAAA,GAAiBC,oCAAAA,yBAAAA,CACrB,mBAAA,GACAC,oCAAAA,cAAAA,CAAc,kBAAkB,QAAQ,SAAS,GACjD,QAAQ,KACV;EACA,MAAM,UAAU,sBAAsB,OAAO;EAC7C,IAAI,CAACC,QAAAA,QAAG,WAAW,QAAQ,GAAG,KAAK,CAACA,QAAAA,QAAG,SAAS,QAAQ,GAAG,CAAC,CAAC,YAAY,GACvE,MAAM,IAAI,MAAM,qCAAqC,QAAQ,KAAK;EAEpE,OAAO;GACL;GACA;GACA,aAAA,GAAYC,oCAAAA,gBAAAA,CAAgB,kBAAkB,cAAc;GAC5D;GACA,SAAS,OAAA,GAAMC,iCAAAA,yBAAAA,EAAAA,GACbC,iCAAAA,qBAAAA,CAAqB,YAAY,QAAQ,SAAS,QAAQ,iBAAiB,CAC7E;GACA,eAAA,GAAcC,6BAAAA,oBAAAA,CAAoB,YAAY,QAAQ,OAAO;GAC7D,eAAA,GAAcC,iCAAAA,oBAAAA,CAAoB,YAAY,QAAQ,OAAO;EAC/D;CACF,CAAC,CAAC,CACD,MAAM,OAAO,UAAmB;EAC/B,MAAM,UAAU,YAAYC,+CAAAA,cAAc,kBAAkB,OAAO;GACjE,iBAAiB;GACjB,sBAAsB,QAAQ;GAC9B,wBAAwB,QAAQ,QAAQ;EAC1C,CAAC;EACD,MAAM;CACR,CAAC;CAEH,MAAM,EAAE,kBAAkB,gBAAgB,YAAY,SAAS,SAAS,iBAAiB;CACzF,MAAM,mBAAmB,SAAS;CAElC,MAAM,cAAc,SAAS,eAAe;CAC5C,MAAM,YAAY,MAAM,UACrB,UACC,WAAW,2BACX;EAAE,wBAAwB,QAAQ;EAAQ,kBAAkB,QAAQ;EAAQ,eAAe,QAAQ;CAAI,UAAA,GAErGC,mCAAAA,iBAAAA,CAAiB,QAAQ,UAAU,SAAS;EAC1C,QAAQ,QAAQ;EAChB,KAAK,QAAQ;EAGb,oBAAoB,QAAQ;EAC5B,qBAAqBC,UAAAA,QAAK,KACxB,QAAQ,sBAAA,GACNC,oCAAAA,oBAAAA,CAAoB,QAAQ,UAAU,QAAQ,iBAAiBC,QAAAA,QAAG,QAAQ,CAAC,CAAC,CAAC,WAC/E,SACA,QACF;EACA;EACA,cAAc,WAAW;EACzB,gBAAgBF,UAAAA,QAAK,MAAA,GAAKG,gCAAAA,0BAAAA,CAA0B,QAAQ,iBAAiBD,QAAAA,QAAG,QAAQ,CAAC,GAAG,aAAa;CAC3G,CAAC,CACL,CAAC,CACA,MAAM,OAAO,UAAmB;EAG/B,MAAM,UAAU,YAAYJ,+CAAAA,cAAc,0BAA0B,OAAO;GACzE,iBAAiB;GACjB,wBAAwB,QAAQ;EAClC,CAAC;EACD,MAAM;CACR,CAAC;CAEH,IAAI;EACF,MAAM,mBAAmB,OAAA,GAAMM,2BAAAA,kBAAAA,CAAkB,UAAU,kBAAkB;EAC7E,MAAM,cAAc,EAAE,GAAG,QAAQ,IAAI;EAGrC,OAAO,YAAYC,kCAAAA;EACnB,MAAM,sBAAA,GAAqBC,kCAAAA,wBAAAA,CAAwB,aAAa,SAAS,OAAO,CAAC,CAAC;EAElF,gBAAgB,SAAS,WAAW;EACpC,YAAY,cAAc;EAC1B,YAAY,iBAAiBC,2BAAAA;EAC7B,YAAY,qBAAqB,QAAQ;EACzC,YAAY,kBAAkB,QAAQ;EACtC,YAAY,uBAAuB,QAAQ;EAC3C,YAAY,gBAAgB,QAAQ,aAAa,SAAU,YAAY,iBAAiB;EAKxF,MAAM,gBAAA,GAAeC,wCAAAA,aAAAA,CAAaC,8CAAAA,eAAAA,QAAAA,KAAAA,CAAAA,CAAAA,cAAAA,UAAAA,CAAAA,CAAAA,IAA8B;EAChE,MAAM,gBAAgB,oBAAA,GAAmBC,kCAAAA,mBAAAA,CAAmB,aAAa,gBAAgB,IAAI,KAAA;EAC7F,IAAI;EACJ,IAAI,eAAe;GACjB,cAAcV,UAAAA,QAAK,KAAK,UAAU,oBAAoB,YAAY;GAClE,MAAMR,QAAAA,QAAG,SAAS,UAAU,aAAa,GAAG,cAAc,KAAK,EAAE,MAAM,kBAAkB,CAAC;EAC5F;EAKA,CAAA,GAAA,sCAAA,qBAAA,CACE;GACE,MAAM,QAAQ;GACd,WAAW,QAAQ;GACnB,oBAAoB,UAAU;GAC9B,SAAS,QAAQ;GACjB,QAAQ;GACR,SAAS,SAAS;GAClB,iBAAiB,SAAS;GAC1B,cAAc,SAAS;GACvB;GACA;GACA,kBAAkB,UAAU;GAC5B,kBAAkB,UAAU;GAC5B,uBAAuB,QAAQ;GAC/B,iBAAiB,CAAC;GAClB,mBAAmB,QAAQ;GAC3B,aAAa,UAAU;GACvB;GACA,OAAO,QAAQ;GACf,QAAQ,QAAQ;GAChB,KAAK,QAAQ;GACb,eAAe,UAAU;GACzB,eAAe,UAAU;GACzB,sBAAsB,QAAQ;EAChC,GACA;GAAE,WAAW,UAAU;GAAoB;EAAY,CACzD;EACA,IAAI,QAAQ,QAAQ;GAClB,YAAY,+BAA+B,UAAU,iBAAiB,KAAKQ,UAAAA,QAAK,SAAS;GACzF,YAAY,+BAA+B,UAAU,iBAAiB,KAAKA,UAAAA,QAAK,SAAS;GACzF,YAAY,wBAAwBA,UAAAA,QAAK,KAAK,QAAQ,UAAU,OAAO;EACzE;EAKA,OAAO;GACL;GACA;GACA;GACA;GACA,SAAS,SAAS;GAClB;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,SAAS,UAAU,QAAQ,KAAK,SAAS;EAC3C;CACF,SAAS,OAAO;EAGd,MAAM,UAAU,YAAYF,+CAAAA,cAAc,oBAAoB,OAAO;GACnE,iBAAiB;GACjB,sBAAsB,QAAQ;GAC9B,GAAI,SAAS,OAAO,EAAE,mBAAmB,QAAQ,KAAK,IAAI,CAAC;EAC7D,CAAC;EACD,MAAM,UAAU,QAAQ;EACxB,MAAM;CACR;AACF"}
|
|
1
|
+
{"version":3,"file":"harnessContext.cjs","names":["resolveProfile","createHarnessTelemetry","loadMajorModesConfig","filterHookDisabledLayers","resolveLayers","fs","layerHookGroups","materializePluginEntries","resolvePluginEntries","resolveMcpAllowlist","resolveSharedSkills","HARNESS_EVENT","collectResources","path","resolveSyncLocation","os","globalDoomConfigDirectory","writeDefaultTheme","DOOM_MCP_SESSION_ENV_VAR","applyProfileEnvironment","DEFAULT_THEME_NAME","packageEntry","PERSONA_ENTRY","buildPersonaPrompt"],"sources":["../../../../src/builders/cli/harnessContext.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { globalDoomConfigDirectory } from '@agimon-ai/doompi-config/config';\nimport { resolvePluginEntries, resolveSharedSkills } from '@agimon-ai/doompi-config/domains';\nimport type { DomainMcpAllowlist, PluginEntry } from '@agimon-ai/doompi-config/domains';\nimport {\n filterHookDisabledLayers,\n layerHookGroups,\n loadMajorModesConfig,\n resolveLayers,\n} from '@agimon-ai/doompi-config/majorModes';\nimport type { MajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { applyProfileEnvironment, buildPersonaPrompt, resolveProfile } from '@agimon-ai/doompi-config/profiles';\nimport type { AgentProfile } from '@agimon-ai/doompi-config/profiles';\nimport { DOOM_MCP_SESSION_ENV_VAR } from '@agimon-ai/doompi-core/mcpSession';\nimport { packageEntry } from '@agimon-ai/doompi-core/moduleResolution';\nimport {\n createHarnessTelemetry,\n HARNESS_EVENT,\n type HarnessTelemetry,\n} from '@agimon-ai/doompi-core/runtimeLogSinkTelemetry';\nimport { resolveSyncLocation } from '@agimon-ai/doompi-core/syncLocation';\nimport { resolveMcpAllowlist } from '@agimon-ai/doompi-domain/mcp';\nimport { materializePluginEntries } from '@agimon-ai/doompi-domain/plugins';\nimport { collectResources, type HarnessResources } from '@agimon-ai/doompi-domain/resources';\nimport { DEFAULT_THEME_NAME, writeDefaultTheme } from '@agimon-ai/doompi-ui/theme';\n\nimport { createHarnessSession } from '../../composition/harnessState';\nimport type { HarnessOptions } from '../../composition/types/harness';\nimport { PERSONA_ENTRY } from './extensionAssembler';\n\nconst OLLAMA_PRESET = 'ollama';\nconst PRIVATE_FILE_MODE = 0o600;\nconst RESOLVE_CONFIG_PHASE = 'resolve_config';\nconst COLLECT_RESOURCES_PHASE = 'collect_resources';\nconst BUILD_CONTEXT_PHASE = 'build_context';\n\n/**\n * Everything a command needs, resolved once.\n *\n * All three commands need the same expensive preparation (domains resolved,\n * plugins collected into a temp directory, environment assembled), so it is\n * built here and handed to whichever command runs.\n */\nexport interface HarnessContext {\n options: HarnessOptions;\n environment: NodeJS.ProcessEnv;\n resources: HarnessResources;\n plugins: PluginEntry[];\n profile?: string;\n personaDirectory?: string;\n /** Root personaDirectory resolves against, when a profile supplied one. */\n personaRoot?: string;\n mcpAllowlist?: DomainMcpAllowlist;\n majorModesConfig: MajorModesConfig;\n selectedLayers: string[];\n hookGroups: string[];\n personaEntry?: string;\n defaultThemePath: string;\n cleanup(): Promise<void>;\n}\n\n/**\n * Applies the provider environment a preset needs.\n *\n * Exported because the synced Pi path has no launcher to do it: the doom-pi\n * extension applies the same rules in-process before anything reads them.\n * `piArgs` is only consulted to spot a selected cloud model.\n */\nexport function configurePreset(\n options: Pick<HarnessOptions, 'preset' | 'piArgs'>,\n environment: NodeJS.ProcessEnv,\n): void {\n if (options.preset === 'kimi') {\n if (!environment.KIMI_API_KEY && environment.KIMI_CODE_API_KEY) {\n environment.KIMI_API_KEY = environment.KIMI_CODE_API_KEY;\n }\n if (!environment.KIMI_API_KEY) {\n throw new Error('KIMI_API_KEY or KIMI_CODE_API_KEY is required for the kimi preset');\n }\n }\n if (options.preset === OLLAMA_PRESET) {\n const selected = options.piArgs.find((argument) => argument.startsWith(`${OLLAMA_PRESET}/`));\n const cloud = selected?.endsWith(':cloud') ?? false;\n if (cloud && !environment.OLLAMA_API_KEY) {\n throw new Error('OLLAMA_API_KEY is required for Ollama cloud models');\n }\n environment.OLLAMA_API_KEY ||= OLLAMA_PRESET;\n environment.DOOMPI_OLLAMA_BASE_URL =\n environment.OLLAMA_BASE_URL ?? environment.ANTHROPIC_BASE_URL ?? 'http://localhost:11434/v1';\n }\n}\n\n/** Resolves the optional persona and environment profile selected for this run. */\nexport function resolveHarnessProfile(options: HarnessOptions): AgentProfile | undefined {\n return options.profile ? resolveProfile(options.configRoot ?? options.repoRoot, options.profile) : undefined;\n}\n\n/**\n * Resolves the full matrix and stages every resource Pi will need.\n *\n * The caller owns the returned cleanup(): the temporary directory holding\n * skills, agents, and the generated MCP config has to outlive this function\n * and be removed once the selected command is done with it.\n */\nexport async function buildHarnessContext(\n options: HarnessOptions,\n telemetry: HarnessTelemetry = createHarnessTelemetry(),\n): Promise<HarnessContext> {\n // A worktree reads its workspace's configuration; resources still come from the checkout.\n const configRoot = options.configRoot ?? options.repoRoot;\n // Everything up to collectResources reads configuration off disk, so a single\n // malformed modes.yaml or profiles.yaml surfaces here as a failed launch\n // with no other record.\n const configured = await telemetry\n .runInSpan(`doom_pi.${RESOLVE_CONFIG_PHASE}`, { 'harness.phase': RESOLVE_CONFIG_PHASE }, async () => {\n const majorModesConfig = loadMajorModesConfig(configRoot, options.homeDirectory);\n const selectedLayers = filterHookDisabledLayers(\n majorModesConfig,\n resolveLayers(majorModesConfig, options.majorMode),\n options.hooks,\n );\n const profile = resolveHarnessProfile(options);\n if (!fs.existsSync(options.cwd) || !fs.statSync(options.cwd).isDirectory()) {\n throw new Error(`Working directory does not exist: ${options.cwd}`);\n }\n return {\n majorModesConfig,\n selectedLayers,\n hookGroups: layerHookGroups(majorModesConfig, selectedLayers),\n profile,\n plugins: await materializePluginEntries(\n resolvePluginEntries(configRoot, options.domains, options.pluginDirectories),\n ),\n mcpAllowlist: resolveMcpAllowlist(configRoot, options.domains),\n sharedSkills: resolveSharedSkills(configRoot, options.domains),\n };\n })\n .catch(async (error: unknown) => {\n await telemetry.recordError(HARNESS_EVENT.configLoadFailed, error, {\n 'harness.phase': RESOLVE_CONFIG_PHASE,\n 'harness.major_mode': options.majorMode,\n 'harness.domain_count': options.domains.length,\n });\n throw error;\n });\n\n const { majorModesConfig, selectedLayers, hookGroups, profile, plugins, mcpAllowlist } = configured;\n const personaDirectory = profile?.persona;\n // A global profile's persona lives beside the global config, not in the repo.\n const personaRoot = profile?.personaRoot ?? configRoot;\n const resources = await telemetry\n .runInSpan(\n `doom_pi.${COLLECT_RESOURCES_PHASE}`,\n { 'harness.plugin.count': plugins.length, 'harness.agents': options.agents, 'harness.mcp': options.mcp },\n () =>\n collectResources(options.repoRoot, plugins, {\n agents: options.agents,\n mcp: options.mcp,\n // Set by `doom-pi sync`, which stages into a directory that outlives\n // the process rather than a temporary one.\n temporaryDirectory: options.resourceDirectory,\n skillCacheDirectory: path.join(\n options.resourceDirectory ??\n resolveSyncLocation(options.repoRoot, options.homeDirectory ?? os.homedir()).directory,\n 'cache',\n 'skills',\n ),\n mcpAllowlist,\n sharedSkills: configured.sharedSkills,\n pluginDataRoot: path.join(globalDoomConfigDirectory(options.homeDirectory ?? os.homedir()), 'plugin-data'),\n }),\n )\n .catch(async (error: unknown) => {\n // Conflicting skills, agents, or MCP servers all fail here, and the plugin\n // that caused it is only identifiable from the message.\n await telemetry.recordError(HARNESS_EVENT.resourceCollectionFailed, error, {\n 'harness.phase': COLLECT_RESOURCES_PHASE,\n 'harness.plugin.count': plugins.length,\n });\n throw error;\n });\n\n try {\n const defaultThemePath = await writeDefaultTheme(resources.temporaryDirectory);\n const environment = { ...(options.environment ?? process.env) };\n // The file-backed projection published through Cordis is authoritative.\n // Never let a legacy parent-process projection leak into this Doom session.\n delete environment[DOOM_MCP_SESSION_ENV_VAR];\n const profileEnvironment = applyProfileEnvironment(environment, profile?.env ?? {});\n\n configurePreset(options, environment);\n environment.NX_DAEMON ??= 'false';\n environment.DOOMPI_THEME ||= DEFAULT_THEME_NAME;\n environment.CLAUDE_PROJECT_DIR = options.repoRoot;\n environment.CODEX_REPO_ROOT = options.repoRoot;\n environment.ORIGINAL_REPO_PATH ||= options.repoRoot;\n environment.MCP_UI_VIEWER = options.automation ? 'none' : (environment.MCP_UI_VIEWER ?? '');\n\n // Always loaded, even with no persona at launch: the extension no-ops\n // without a persona file, and /profile needs it present to load one later.\n // Pi freezes the --extension list at startup, so it cannot be added on demand.\n const personaEntry = packageEntry(PERSONA_ENTRY, import.meta.url);\n const personaPrompt = personaDirectory ? buildPersonaPrompt(personaRoot, personaDirectory) : undefined;\n let personaFile: string | undefined;\n if (personaPrompt) {\n personaFile = path.join(resources.temporaryDirectory, 'persona.md');\n await fs.promises.writeFile(personaFile, `${personaPrompt}\\n`, { mode: PRIVATE_FILE_MODE });\n }\n\n // One write, describing the child rather than this process. The state file\n // is the child's authority and the environment is what it projects for the\n // hooks and packages that can only read an environment.\n createHarnessSession(\n {\n root: options.repoRoot,\n majorMode: options.majorMode,\n temporaryDirectory: resources.temporaryDirectory,\n domains: options.domains,\n layers: selectedLayers,\n profile: profile?.name,\n profileIdentity: profile?.identity,\n profileVoice: profile?.voice,\n profileEnvironment,\n personaFile,\n skillDirectories: resources.skillDirectories,\n agentDirectories: resources.agentDirectories,\n additionalDirectories: options.additionalDirectories,\n childExtensions: [],\n pluginDirectories: options.pluginDirectories,\n pluginHooks: resources.pluginHooks,\n hookGroups,\n hooks: options.hooks,\n agents: options.agents,\n mcp: options.mcp,\n mcpConfigPath: resources.mcpConfigPath,\n mcpProjection: resources.mcpProjection,\n allowProtectedWrites: options.allowProtectedWrites,\n },\n { directory: resources.temporaryDirectory, environment },\n );\n if (options.agents) {\n environment.PI_SUBAGENT_EXTRA_AGENT_DIRS = resources.agentDirectories.join(path.delimiter);\n environment.PI_SUBAGENT_EXTRA_SKILL_DIRS = resources.skillDirectories.join(path.delimiter);\n environment.PI_SUBAGENT_PI_BINARY = path.join(options.repoRoot, 'pi.sh');\n }\n\n // Core hook groups load regardless; hookGroups selects the rest. The initial\n // state write includes an empty list deliberately: unset means every group.\n\n return {\n options,\n environment,\n resources,\n plugins,\n profile: profile?.name,\n personaDirectory,\n personaRoot,\n mcpAllowlist,\n majorModesConfig,\n selectedLayers,\n hookGroups,\n personaEntry,\n defaultThemePath,\n cleanup: resources.cleanup.bind(resources),\n };\n } catch (error) {\n // Preparation failed after the temp directory existed, so nothing will be\n // handed back to clean it up.\n await telemetry.recordError(HARNESS_EVENT.contextBuildFailed, error, {\n 'harness.phase': BUILD_CONTEXT_PHASE,\n 'harness.major_mode': options.majorMode,\n ...(profile?.name ? { 'harness.profile': profile.name } : {}),\n });\n await resources.cleanup();\n throw error;\n }\n}\n"],"mappings":"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"}
|
|
@@ -98,7 +98,7 @@ async function buildHarnessContext(options, telemetry = createHarnessTelemetry()
|
|
|
98
98
|
});
|
|
99
99
|
try {
|
|
100
100
|
const defaultThemePath = await writeDefaultTheme(resources.temporaryDirectory);
|
|
101
|
-
const environment = { ...process.env };
|
|
101
|
+
const environment = { ...options.environment ?? process.env };
|
|
102
102
|
delete environment[DOOM_MCP_SESSION_ENV_VAR];
|
|
103
103
|
const profileEnvironment = applyProfileEnvironment(environment, profile?.env ?? {});
|
|
104
104
|
configurePreset(options, environment);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harnessContext.mjs","names":[],"sources":["../../../../src/builders/cli/harnessContext.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { globalDoomConfigDirectory } from '@agimon-ai/doompi-config/config';\nimport { resolvePluginEntries, resolveSharedSkills } from '@agimon-ai/doompi-config/domains';\nimport type { DomainMcpAllowlist, PluginEntry } from '@agimon-ai/doompi-config/domains';\nimport {\n filterHookDisabledLayers,\n layerHookGroups,\n loadMajorModesConfig,\n resolveLayers,\n} from '@agimon-ai/doompi-config/majorModes';\nimport type { MajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { applyProfileEnvironment, buildPersonaPrompt, resolveProfile } from '@agimon-ai/doompi-config/profiles';\nimport type { AgentProfile } from '@agimon-ai/doompi-config/profiles';\nimport { DOOM_MCP_SESSION_ENV_VAR } from '@agimon-ai/doompi-core/mcpSession';\nimport { packageEntry } from '@agimon-ai/doompi-core/moduleResolution';\nimport {\n createHarnessTelemetry,\n HARNESS_EVENT,\n type HarnessTelemetry,\n} from '@agimon-ai/doompi-core/runtimeLogSinkTelemetry';\nimport { resolveSyncLocation } from '@agimon-ai/doompi-core/syncLocation';\nimport { resolveMcpAllowlist } from '@agimon-ai/doompi-domain/mcp';\nimport { materializePluginEntries } from '@agimon-ai/doompi-domain/plugins';\nimport { collectResources, type HarnessResources } from '@agimon-ai/doompi-domain/resources';\nimport { DEFAULT_THEME_NAME, writeDefaultTheme } from '@agimon-ai/doompi-ui/theme';\n\nimport { createHarnessSession } from '../../composition/harnessState';\nimport type { HarnessOptions } from '../../composition/types/harness';\nimport { PERSONA_ENTRY } from './extensionAssembler';\n\nconst OLLAMA_PRESET = 'ollama';\nconst PRIVATE_FILE_MODE = 0o600;\nconst RESOLVE_CONFIG_PHASE = 'resolve_config';\nconst COLLECT_RESOURCES_PHASE = 'collect_resources';\nconst BUILD_CONTEXT_PHASE = 'build_context';\n\n/**\n * Everything a command needs, resolved once.\n *\n * All three commands need the same expensive preparation (domains resolved,\n * plugins collected into a temp directory, environment assembled), so it is\n * built here and handed to whichever command runs.\n */\nexport interface HarnessContext {\n options: HarnessOptions;\n environment: NodeJS.ProcessEnv;\n resources: HarnessResources;\n plugins: PluginEntry[];\n profile?: string;\n personaDirectory?: string;\n /** Root personaDirectory resolves against, when a profile supplied one. */\n personaRoot?: string;\n mcpAllowlist?: DomainMcpAllowlist;\n majorModesConfig: MajorModesConfig;\n selectedLayers: string[];\n hookGroups: string[];\n personaEntry?: string;\n defaultThemePath: string;\n cleanup(): Promise<void>;\n}\n\n/**\n * Applies the provider environment a preset needs.\n *\n * Exported because the synced Pi path has no launcher to do it: the doom-pi\n * extension applies the same rules in-process before anything reads them.\n * `piArgs` is only consulted to spot a selected cloud model.\n */\nexport function configurePreset(\n options: Pick<HarnessOptions, 'preset' | 'piArgs'>,\n environment: NodeJS.ProcessEnv,\n): void {\n if (options.preset === 'kimi') {\n if (!environment.KIMI_API_KEY && environment.KIMI_CODE_API_KEY) {\n environment.KIMI_API_KEY = environment.KIMI_CODE_API_KEY;\n }\n if (!environment.KIMI_API_KEY) {\n throw new Error('KIMI_API_KEY or KIMI_CODE_API_KEY is required for the kimi preset');\n }\n }\n if (options.preset === OLLAMA_PRESET) {\n const selected = options.piArgs.find((argument) => argument.startsWith(`${OLLAMA_PRESET}/`));\n const cloud = selected?.endsWith(':cloud') ?? false;\n if (cloud && !environment.OLLAMA_API_KEY) {\n throw new Error('OLLAMA_API_KEY is required for Ollama cloud models');\n }\n environment.OLLAMA_API_KEY ||= OLLAMA_PRESET;\n environment.DOOMPI_OLLAMA_BASE_URL =\n environment.OLLAMA_BASE_URL ?? environment.ANTHROPIC_BASE_URL ?? 'http://localhost:11434/v1';\n }\n}\n\n/** Resolves the optional persona and environment profile selected for this run. */\nexport function resolveHarnessProfile(options: HarnessOptions): AgentProfile | undefined {\n return options.profile ? resolveProfile(options.configRoot ?? options.repoRoot, options.profile) : undefined;\n}\n\n/**\n * Resolves the full matrix and stages every resource Pi will need.\n *\n * The caller owns the returned cleanup(): the temporary directory holding\n * skills, agents, and the generated MCP config has to outlive this function\n * and be removed once the selected command is done with it.\n */\nexport async function buildHarnessContext(\n options: HarnessOptions,\n telemetry: HarnessTelemetry = createHarnessTelemetry(),\n): Promise<HarnessContext> {\n // A worktree reads its workspace's configuration; resources still come from the checkout.\n const configRoot = options.configRoot ?? options.repoRoot;\n // Everything up to collectResources reads configuration off disk, so a single\n // malformed modes.yaml or profiles.yaml surfaces here as a failed launch\n // with no other record.\n const configured = await telemetry\n .runInSpan(`doom_pi.${RESOLVE_CONFIG_PHASE}`, { 'harness.phase': RESOLVE_CONFIG_PHASE }, async () => {\n const majorModesConfig = loadMajorModesConfig(configRoot, options.homeDirectory);\n const selectedLayers = filterHookDisabledLayers(\n majorModesConfig,\n resolveLayers(majorModesConfig, options.majorMode),\n options.hooks,\n );\n const profile = resolveHarnessProfile(options);\n if (!fs.existsSync(options.cwd) || !fs.statSync(options.cwd).isDirectory()) {\n throw new Error(`Working directory does not exist: ${options.cwd}`);\n }\n return {\n majorModesConfig,\n selectedLayers,\n hookGroups: layerHookGroups(majorModesConfig, selectedLayers),\n profile,\n plugins: await materializePluginEntries(\n resolvePluginEntries(configRoot, options.domains, options.pluginDirectories),\n ),\n mcpAllowlist: resolveMcpAllowlist(configRoot, options.domains),\n sharedSkills: resolveSharedSkills(configRoot, options.domains),\n };\n })\n .catch(async (error: unknown) => {\n await telemetry.recordError(HARNESS_EVENT.configLoadFailed, error, {\n 'harness.phase': RESOLVE_CONFIG_PHASE,\n 'harness.major_mode': options.majorMode,\n 'harness.domain_count': options.domains.length,\n });\n throw error;\n });\n\n const { majorModesConfig, selectedLayers, hookGroups, profile, plugins, mcpAllowlist } = configured;\n const personaDirectory = profile?.persona;\n // A global profile's persona lives beside the global config, not in the repo.\n const personaRoot = profile?.personaRoot ?? configRoot;\n const resources = await telemetry\n .runInSpan(\n `doom_pi.${COLLECT_RESOURCES_PHASE}`,\n { 'harness.plugin.count': plugins.length, 'harness.agents': options.agents, 'harness.mcp': options.mcp },\n () =>\n collectResources(options.repoRoot, plugins, {\n agents: options.agents,\n mcp: options.mcp,\n // Set by `doom-pi sync`, which stages into a directory that outlives\n // the process rather than a temporary one.\n temporaryDirectory: options.resourceDirectory,\n skillCacheDirectory: path.join(\n options.resourceDirectory ??\n resolveSyncLocation(options.repoRoot, options.homeDirectory ?? os.homedir()).directory,\n 'cache',\n 'skills',\n ),\n mcpAllowlist,\n sharedSkills: configured.sharedSkills,\n pluginDataRoot: path.join(globalDoomConfigDirectory(options.homeDirectory ?? os.homedir()), 'plugin-data'),\n }),\n )\n .catch(async (error: unknown) => {\n // Conflicting skills, agents, or MCP servers all fail here, and the plugin\n // that caused it is only identifiable from the message.\n await telemetry.recordError(HARNESS_EVENT.resourceCollectionFailed, error, {\n 'harness.phase': COLLECT_RESOURCES_PHASE,\n 'harness.plugin.count': plugins.length,\n });\n throw error;\n });\n\n try {\n const defaultThemePath = await writeDefaultTheme(resources.temporaryDirectory);\n const environment = { ...process.env };\n // The file-backed projection published through Cordis is authoritative.\n // Never let a legacy parent-process projection leak into this Doom session.\n delete environment[DOOM_MCP_SESSION_ENV_VAR];\n const profileEnvironment = applyProfileEnvironment(environment, profile?.env ?? {});\n\n configurePreset(options, environment);\n environment.NX_DAEMON ??= 'false';\n environment.DOOMPI_THEME ||= DEFAULT_THEME_NAME;\n environment.CLAUDE_PROJECT_DIR = options.repoRoot;\n environment.CODEX_REPO_ROOT = options.repoRoot;\n environment.ORIGINAL_REPO_PATH ||= options.repoRoot;\n environment.MCP_UI_VIEWER = options.automation ? 'none' : (environment.MCP_UI_VIEWER ?? '');\n\n // Always loaded, even with no persona at launch: the extension no-ops\n // without a persona file, and /profile needs it present to load one later.\n // Pi freezes the --extension list at startup, so it cannot be added on demand.\n const personaEntry = packageEntry(PERSONA_ENTRY, import.meta.url);\n const personaPrompt = personaDirectory ? buildPersonaPrompt(personaRoot, personaDirectory) : undefined;\n let personaFile: string | undefined;\n if (personaPrompt) {\n personaFile = path.join(resources.temporaryDirectory, 'persona.md');\n await fs.promises.writeFile(personaFile, `${personaPrompt}\\n`, { mode: PRIVATE_FILE_MODE });\n }\n\n // One write, describing the child rather than this process. The state file\n // is the child's authority and the environment is what it projects for the\n // hooks and packages that can only read an environment.\n createHarnessSession(\n {\n root: options.repoRoot,\n majorMode: options.majorMode,\n temporaryDirectory: resources.temporaryDirectory,\n domains: options.domains,\n layers: selectedLayers,\n profile: profile?.name,\n profileIdentity: profile?.identity,\n profileVoice: profile?.voice,\n profileEnvironment,\n personaFile,\n skillDirectories: resources.skillDirectories,\n agentDirectories: resources.agentDirectories,\n additionalDirectories: options.additionalDirectories,\n childExtensions: [],\n pluginDirectories: options.pluginDirectories,\n pluginHooks: resources.pluginHooks,\n hookGroups,\n hooks: options.hooks,\n agents: options.agents,\n mcp: options.mcp,\n mcpConfigPath: resources.mcpConfigPath,\n mcpProjection: resources.mcpProjection,\n allowProtectedWrites: options.allowProtectedWrites,\n },\n { directory: resources.temporaryDirectory, environment },\n );\n if (options.agents) {\n environment.PI_SUBAGENT_EXTRA_AGENT_DIRS = resources.agentDirectories.join(path.delimiter);\n environment.PI_SUBAGENT_EXTRA_SKILL_DIRS = resources.skillDirectories.join(path.delimiter);\n environment.PI_SUBAGENT_PI_BINARY = path.join(options.repoRoot, 'pi.sh');\n }\n\n // Core hook groups load regardless; hookGroups selects the rest. The initial\n // state write includes an empty list deliberately: unset means every group.\n\n return {\n options,\n environment,\n resources,\n plugins,\n profile: profile?.name,\n personaDirectory,\n personaRoot,\n mcpAllowlist,\n majorModesConfig,\n selectedLayers,\n hookGroups,\n personaEntry,\n defaultThemePath,\n cleanup: resources.cleanup.bind(resources),\n };\n } catch (error) {\n // Preparation failed after the temp directory existed, so nothing will be\n // handed back to clean it up.\n await telemetry.recordError(HARNESS_EVENT.contextBuildFailed, error, {\n 'harness.phase': BUILD_CONTEXT_PHASE,\n 'harness.major_mode': options.majorMode,\n ...(profile?.name ? { 'harness.profile': profile.name } : {}),\n });\n await resources.cleanup();\n throw error;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"harnessContext.mjs","names":[],"sources":["../../../../src/builders/cli/harnessContext.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { globalDoomConfigDirectory } from '@agimon-ai/doompi-config/config';\nimport { resolvePluginEntries, resolveSharedSkills } from '@agimon-ai/doompi-config/domains';\nimport type { DomainMcpAllowlist, PluginEntry } from '@agimon-ai/doompi-config/domains';\nimport {\n filterHookDisabledLayers,\n layerHookGroups,\n loadMajorModesConfig,\n resolveLayers,\n} from '@agimon-ai/doompi-config/majorModes';\nimport type { MajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { applyProfileEnvironment, buildPersonaPrompt, resolveProfile } from '@agimon-ai/doompi-config/profiles';\nimport type { AgentProfile } from '@agimon-ai/doompi-config/profiles';\nimport { DOOM_MCP_SESSION_ENV_VAR } from '@agimon-ai/doompi-core/mcpSession';\nimport { packageEntry } from '@agimon-ai/doompi-core/moduleResolution';\nimport {\n createHarnessTelemetry,\n HARNESS_EVENT,\n type HarnessTelemetry,\n} from '@agimon-ai/doompi-core/runtimeLogSinkTelemetry';\nimport { resolveSyncLocation } from '@agimon-ai/doompi-core/syncLocation';\nimport { resolveMcpAllowlist } from '@agimon-ai/doompi-domain/mcp';\nimport { materializePluginEntries } from '@agimon-ai/doompi-domain/plugins';\nimport { collectResources, type HarnessResources } from '@agimon-ai/doompi-domain/resources';\nimport { DEFAULT_THEME_NAME, writeDefaultTheme } from '@agimon-ai/doompi-ui/theme';\n\nimport { createHarnessSession } from '../../composition/harnessState';\nimport type { HarnessOptions } from '../../composition/types/harness';\nimport { PERSONA_ENTRY } from './extensionAssembler';\n\nconst OLLAMA_PRESET = 'ollama';\nconst PRIVATE_FILE_MODE = 0o600;\nconst RESOLVE_CONFIG_PHASE = 'resolve_config';\nconst COLLECT_RESOURCES_PHASE = 'collect_resources';\nconst BUILD_CONTEXT_PHASE = 'build_context';\n\n/**\n * Everything a command needs, resolved once.\n *\n * All three commands need the same expensive preparation (domains resolved,\n * plugins collected into a temp directory, environment assembled), so it is\n * built here and handed to whichever command runs.\n */\nexport interface HarnessContext {\n options: HarnessOptions;\n environment: NodeJS.ProcessEnv;\n resources: HarnessResources;\n plugins: PluginEntry[];\n profile?: string;\n personaDirectory?: string;\n /** Root personaDirectory resolves against, when a profile supplied one. */\n personaRoot?: string;\n mcpAllowlist?: DomainMcpAllowlist;\n majorModesConfig: MajorModesConfig;\n selectedLayers: string[];\n hookGroups: string[];\n personaEntry?: string;\n defaultThemePath: string;\n cleanup(): Promise<void>;\n}\n\n/**\n * Applies the provider environment a preset needs.\n *\n * Exported because the synced Pi path has no launcher to do it: the doom-pi\n * extension applies the same rules in-process before anything reads them.\n * `piArgs` is only consulted to spot a selected cloud model.\n */\nexport function configurePreset(\n options: Pick<HarnessOptions, 'preset' | 'piArgs'>,\n environment: NodeJS.ProcessEnv,\n): void {\n if (options.preset === 'kimi') {\n if (!environment.KIMI_API_KEY && environment.KIMI_CODE_API_KEY) {\n environment.KIMI_API_KEY = environment.KIMI_CODE_API_KEY;\n }\n if (!environment.KIMI_API_KEY) {\n throw new Error('KIMI_API_KEY or KIMI_CODE_API_KEY is required for the kimi preset');\n }\n }\n if (options.preset === OLLAMA_PRESET) {\n const selected = options.piArgs.find((argument) => argument.startsWith(`${OLLAMA_PRESET}/`));\n const cloud = selected?.endsWith(':cloud') ?? false;\n if (cloud && !environment.OLLAMA_API_KEY) {\n throw new Error('OLLAMA_API_KEY is required for Ollama cloud models');\n }\n environment.OLLAMA_API_KEY ||= OLLAMA_PRESET;\n environment.DOOMPI_OLLAMA_BASE_URL =\n environment.OLLAMA_BASE_URL ?? environment.ANTHROPIC_BASE_URL ?? 'http://localhost:11434/v1';\n }\n}\n\n/** Resolves the optional persona and environment profile selected for this run. */\nexport function resolveHarnessProfile(options: HarnessOptions): AgentProfile | undefined {\n return options.profile ? resolveProfile(options.configRoot ?? options.repoRoot, options.profile) : undefined;\n}\n\n/**\n * Resolves the full matrix and stages every resource Pi will need.\n *\n * The caller owns the returned cleanup(): the temporary directory holding\n * skills, agents, and the generated MCP config has to outlive this function\n * and be removed once the selected command is done with it.\n */\nexport async function buildHarnessContext(\n options: HarnessOptions,\n telemetry: HarnessTelemetry = createHarnessTelemetry(),\n): Promise<HarnessContext> {\n // A worktree reads its workspace's configuration; resources still come from the checkout.\n const configRoot = options.configRoot ?? options.repoRoot;\n // Everything up to collectResources reads configuration off disk, so a single\n // malformed modes.yaml or profiles.yaml surfaces here as a failed launch\n // with no other record.\n const configured = await telemetry\n .runInSpan(`doom_pi.${RESOLVE_CONFIG_PHASE}`, { 'harness.phase': RESOLVE_CONFIG_PHASE }, async () => {\n const majorModesConfig = loadMajorModesConfig(configRoot, options.homeDirectory);\n const selectedLayers = filterHookDisabledLayers(\n majorModesConfig,\n resolveLayers(majorModesConfig, options.majorMode),\n options.hooks,\n );\n const profile = resolveHarnessProfile(options);\n if (!fs.existsSync(options.cwd) || !fs.statSync(options.cwd).isDirectory()) {\n throw new Error(`Working directory does not exist: ${options.cwd}`);\n }\n return {\n majorModesConfig,\n selectedLayers,\n hookGroups: layerHookGroups(majorModesConfig, selectedLayers),\n profile,\n plugins: await materializePluginEntries(\n resolvePluginEntries(configRoot, options.domains, options.pluginDirectories),\n ),\n mcpAllowlist: resolveMcpAllowlist(configRoot, options.domains),\n sharedSkills: resolveSharedSkills(configRoot, options.domains),\n };\n })\n .catch(async (error: unknown) => {\n await telemetry.recordError(HARNESS_EVENT.configLoadFailed, error, {\n 'harness.phase': RESOLVE_CONFIG_PHASE,\n 'harness.major_mode': options.majorMode,\n 'harness.domain_count': options.domains.length,\n });\n throw error;\n });\n\n const { majorModesConfig, selectedLayers, hookGroups, profile, plugins, mcpAllowlist } = configured;\n const personaDirectory = profile?.persona;\n // A global profile's persona lives beside the global config, not in the repo.\n const personaRoot = profile?.personaRoot ?? configRoot;\n const resources = await telemetry\n .runInSpan(\n `doom_pi.${COLLECT_RESOURCES_PHASE}`,\n { 'harness.plugin.count': plugins.length, 'harness.agents': options.agents, 'harness.mcp': options.mcp },\n () =>\n collectResources(options.repoRoot, plugins, {\n agents: options.agents,\n mcp: options.mcp,\n // Set by `doom-pi sync`, which stages into a directory that outlives\n // the process rather than a temporary one.\n temporaryDirectory: options.resourceDirectory,\n skillCacheDirectory: path.join(\n options.resourceDirectory ??\n resolveSyncLocation(options.repoRoot, options.homeDirectory ?? os.homedir()).directory,\n 'cache',\n 'skills',\n ),\n mcpAllowlist,\n sharedSkills: configured.sharedSkills,\n pluginDataRoot: path.join(globalDoomConfigDirectory(options.homeDirectory ?? os.homedir()), 'plugin-data'),\n }),\n )\n .catch(async (error: unknown) => {\n // Conflicting skills, agents, or MCP servers all fail here, and the plugin\n // that caused it is only identifiable from the message.\n await telemetry.recordError(HARNESS_EVENT.resourceCollectionFailed, error, {\n 'harness.phase': COLLECT_RESOURCES_PHASE,\n 'harness.plugin.count': plugins.length,\n });\n throw error;\n });\n\n try {\n const defaultThemePath = await writeDefaultTheme(resources.temporaryDirectory);\n const environment = { ...(options.environment ?? process.env) };\n // The file-backed projection published through Cordis is authoritative.\n // Never let a legacy parent-process projection leak into this Doom session.\n delete environment[DOOM_MCP_SESSION_ENV_VAR];\n const profileEnvironment = applyProfileEnvironment(environment, profile?.env ?? {});\n\n configurePreset(options, environment);\n environment.NX_DAEMON ??= 'false';\n environment.DOOMPI_THEME ||= DEFAULT_THEME_NAME;\n environment.CLAUDE_PROJECT_DIR = options.repoRoot;\n environment.CODEX_REPO_ROOT = options.repoRoot;\n environment.ORIGINAL_REPO_PATH ||= options.repoRoot;\n environment.MCP_UI_VIEWER = options.automation ? 'none' : (environment.MCP_UI_VIEWER ?? '');\n\n // Always loaded, even with no persona at launch: the extension no-ops\n // without a persona file, and /profile needs it present to load one later.\n // Pi freezes the --extension list at startup, so it cannot be added on demand.\n const personaEntry = packageEntry(PERSONA_ENTRY, import.meta.url);\n const personaPrompt = personaDirectory ? buildPersonaPrompt(personaRoot, personaDirectory) : undefined;\n let personaFile: string | undefined;\n if (personaPrompt) {\n personaFile = path.join(resources.temporaryDirectory, 'persona.md');\n await fs.promises.writeFile(personaFile, `${personaPrompt}\\n`, { mode: PRIVATE_FILE_MODE });\n }\n\n // One write, describing the child rather than this process. The state file\n // is the child's authority and the environment is what it projects for the\n // hooks and packages that can only read an environment.\n createHarnessSession(\n {\n root: options.repoRoot,\n majorMode: options.majorMode,\n temporaryDirectory: resources.temporaryDirectory,\n domains: options.domains,\n layers: selectedLayers,\n profile: profile?.name,\n profileIdentity: profile?.identity,\n profileVoice: profile?.voice,\n profileEnvironment,\n personaFile,\n skillDirectories: resources.skillDirectories,\n agentDirectories: resources.agentDirectories,\n additionalDirectories: options.additionalDirectories,\n childExtensions: [],\n pluginDirectories: options.pluginDirectories,\n pluginHooks: resources.pluginHooks,\n hookGroups,\n hooks: options.hooks,\n agents: options.agents,\n mcp: options.mcp,\n mcpConfigPath: resources.mcpConfigPath,\n mcpProjection: resources.mcpProjection,\n allowProtectedWrites: options.allowProtectedWrites,\n },\n { directory: resources.temporaryDirectory, environment },\n );\n if (options.agents) {\n environment.PI_SUBAGENT_EXTRA_AGENT_DIRS = resources.agentDirectories.join(path.delimiter);\n environment.PI_SUBAGENT_EXTRA_SKILL_DIRS = resources.skillDirectories.join(path.delimiter);\n environment.PI_SUBAGENT_PI_BINARY = path.join(options.repoRoot, 'pi.sh');\n }\n\n // Core hook groups load regardless; hookGroups selects the rest. The initial\n // state write includes an empty list deliberately: unset means every group.\n\n return {\n options,\n environment,\n resources,\n plugins,\n profile: profile?.name,\n personaDirectory,\n personaRoot,\n mcpAllowlist,\n majorModesConfig,\n selectedLayers,\n hookGroups,\n personaEntry,\n defaultThemePath,\n cleanup: resources.cleanup.bind(resources),\n };\n } catch (error) {\n // Preparation failed after the temp directory existed, so nothing will be\n // handed back to clean it up.\n await telemetry.recordError(HARNESS_EVENT.contextBuildFailed, error, {\n 'harness.phase': BUILD_CONTEXT_PHASE,\n 'harness.major_mode': options.majorMode,\n ...(profile?.name ? { 'harness.profile': profile.name } : {}),\n });\n await resources.cleanup();\n throw error;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAiCA,MAAM,gBAAgB;AACtB,MAAM,oBAAoB;AAC1B,MAAM,uBAAuB;AAC7B,MAAM,0BAA0B;AAChC,MAAM,sBAAsB;;;;;;;;AAkC5B,SAAgB,gBACd,SACA,aACM;CACN,IAAI,QAAQ,WAAW,QAAQ;EAC7B,IAAI,CAAC,YAAY,gBAAgB,YAAY,mBAC3C,YAAY,eAAe,YAAY;EAEzC,IAAI,CAAC,YAAY,cACf,MAAM,IAAI,MAAM,mEAAmE;CAEvF;CACA,IAAI,QAAQ,WAAW,eAAe;EAGpC,KAFiB,QAAQ,OAAO,MAAM,aAAa,SAAS,WAAW,GAAG,cAAc,EAAE,CACrE,CAAC,EAAE,SAAS,QAAQ,KAAK,UACjC,CAAC,YAAY,gBACxB,MAAM,IAAI,MAAM,oDAAoD;EAEtE,YAAY,mBAAmB;EAC/B,YAAY,yBACV,YAAY,mBAAmB,YAAY,sBAAsB;CACrE;AACF;;AAGA,SAAgB,sBAAsB,SAAmD;CACvF,OAAO,QAAQ,UAAU,eAAe,QAAQ,cAAc,QAAQ,UAAU,QAAQ,OAAO,IAAI,KAAA;AACrG;;;;;;;;AASA,eAAsB,oBACpB,SACA,YAA8B,uBAAuB,GAC5B;CAEzB,MAAM,aAAa,QAAQ,cAAc,QAAQ;CAIjD,MAAM,aAAa,MAAM,UACtB,UAAU,WAAW,wBAAwB,EAAE,iBAAiB,qBAAqB,GAAG,YAAY;EACnG,MAAM,mBAAmB,qBAAqB,YAAY,QAAQ,aAAa;EAC/E,MAAM,iBAAiB,yBACrB,kBACA,cAAc,kBAAkB,QAAQ,SAAS,GACjD,QAAQ,KACV;EACA,MAAM,UAAU,sBAAsB,OAAO;EAC7C,IAAI,CAAC,GAAG,WAAW,QAAQ,GAAG,KAAK,CAAC,GAAG,SAAS,QAAQ,GAAG,CAAC,CAAC,YAAY,GACvE,MAAM,IAAI,MAAM,qCAAqC,QAAQ,KAAK;EAEpE,OAAO;GACL;GACA;GACA,YAAY,gBAAgB,kBAAkB,cAAc;GAC5D;GACA,SAAS,MAAM,yBACb,qBAAqB,YAAY,QAAQ,SAAS,QAAQ,iBAAiB,CAC7E;GACA,cAAc,oBAAoB,YAAY,QAAQ,OAAO;GAC7D,cAAc,oBAAoB,YAAY,QAAQ,OAAO;EAC/D;CACF,CAAC,CAAC,CACD,MAAM,OAAO,UAAmB;EAC/B,MAAM,UAAU,YAAY,cAAc,kBAAkB,OAAO;GACjE,iBAAiB;GACjB,sBAAsB,QAAQ;GAC9B,wBAAwB,QAAQ,QAAQ;EAC1C,CAAC;EACD,MAAM;CACR,CAAC;CAEH,MAAM,EAAE,kBAAkB,gBAAgB,YAAY,SAAS,SAAS,iBAAiB;CACzF,MAAM,mBAAmB,SAAS;CAElC,MAAM,cAAc,SAAS,eAAe;CAC5C,MAAM,YAAY,MAAM,UACrB,UACC,WAAW,2BACX;EAAE,wBAAwB,QAAQ;EAAQ,kBAAkB,QAAQ;EAAQ,eAAe,QAAQ;CAAI,SAErG,iBAAiB,QAAQ,UAAU,SAAS;EAC1C,QAAQ,QAAQ;EAChB,KAAK,QAAQ;EAGb,oBAAoB,QAAQ;EAC5B,qBAAqB,KAAK,KACxB,QAAQ,qBACN,oBAAoB,QAAQ,UAAU,QAAQ,iBAAiB,GAAG,QAAQ,CAAC,CAAC,CAAC,WAC/E,SACA,QACF;EACA;EACA,cAAc,WAAW;EACzB,gBAAgB,KAAK,KAAK,0BAA0B,QAAQ,iBAAiB,GAAG,QAAQ,CAAC,GAAG,aAAa;CAC3G,CAAC,CACL,CAAC,CACA,MAAM,OAAO,UAAmB;EAG/B,MAAM,UAAU,YAAY,cAAc,0BAA0B,OAAO;GACzE,iBAAiB;GACjB,wBAAwB,QAAQ;EAClC,CAAC;EACD,MAAM;CACR,CAAC;CAEH,IAAI;EACF,MAAM,mBAAmB,MAAM,kBAAkB,UAAU,kBAAkB;EAC7E,MAAM,cAAc,EAAE,GAAI,QAAQ,eAAe,QAAQ,IAAK;EAG9D,OAAO,YAAY;EACnB,MAAM,qBAAqB,wBAAwB,aAAa,SAAS,OAAO,CAAC,CAAC;EAElF,gBAAgB,SAAS,WAAW;EACpC,YAAY,cAAc;EAC1B,YAAY,iBAAiB;EAC7B,YAAY,qBAAqB,QAAQ;EACzC,YAAY,kBAAkB,QAAQ;EACtC,YAAY,uBAAuB,QAAQ;EAC3C,YAAY,gBAAgB,QAAQ,aAAa,SAAU,YAAY,iBAAiB;EAKxF,MAAM,eAAe,aAAa,eAAe,YAAY,GAAG;EAChE,MAAM,gBAAgB,mBAAmB,mBAAmB,aAAa,gBAAgB,IAAI,KAAA;EAC7F,IAAI;EACJ,IAAI,eAAe;GACjB,cAAc,KAAK,KAAK,UAAU,oBAAoB,YAAY;GAClE,MAAM,GAAG,SAAS,UAAU,aAAa,GAAG,cAAc,KAAK,EAAE,MAAM,kBAAkB,CAAC;EAC5F;EAKA,qBACE;GACE,MAAM,QAAQ;GACd,WAAW,QAAQ;GACnB,oBAAoB,UAAU;GAC9B,SAAS,QAAQ;GACjB,QAAQ;GACR,SAAS,SAAS;GAClB,iBAAiB,SAAS;GAC1B,cAAc,SAAS;GACvB;GACA;GACA,kBAAkB,UAAU;GAC5B,kBAAkB,UAAU;GAC5B,uBAAuB,QAAQ;GAC/B,iBAAiB,CAAC;GAClB,mBAAmB,QAAQ;GAC3B,aAAa,UAAU;GACvB;GACA,OAAO,QAAQ;GACf,QAAQ,QAAQ;GAChB,KAAK,QAAQ;GACb,eAAe,UAAU;GACzB,eAAe,UAAU;GACzB,sBAAsB,QAAQ;EAChC,GACA;GAAE,WAAW,UAAU;GAAoB;EAAY,CACzD;EACA,IAAI,QAAQ,QAAQ;GAClB,YAAY,+BAA+B,UAAU,iBAAiB,KAAK,KAAK,SAAS;GACzF,YAAY,+BAA+B,UAAU,iBAAiB,KAAK,KAAK,SAAS;GACzF,YAAY,wBAAwB,KAAK,KAAK,QAAQ,UAAU,OAAO;EACzE;EAKA,OAAO;GACL;GACA;GACA;GACA;GACA,SAAS,SAAS;GAClB;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,SAAS,UAAU,QAAQ,KAAK,SAAS;EAC3C;CACF,SAAS,OAAO;EAGd,MAAM,UAAU,YAAY,cAAc,oBAAoB,OAAO;GACnE,iBAAiB;GACjB,sBAAsB,QAAQ;GAC9B,GAAI,SAAS,OAAO,EAAE,mBAAmB,QAAQ,KAAK,IAAI,CAAC;EAC7D,CAAC;EACD,MAAM,UAAU,QAAQ;EACxB,MAAM;CACR;AACF"}
|
|
@@ -7,8 +7,8 @@ const require_index$4 = require("../../composition/syncDrift/index.cjs");
|
|
|
7
7
|
const require_harnessContext = require("../cli/harnessContext.cjs");
|
|
8
8
|
const require_computerUseBinding = require("./computerUseBinding.cjs");
|
|
9
9
|
const require_defaultWorkspace = require("./defaultWorkspace.cjs");
|
|
10
|
-
const require_executionBudget = require("./executionBudget.cjs");
|
|
11
10
|
const require_logSink = require("./logSink.cjs");
|
|
11
|
+
const require_runtimeResources = require("./runtimeResources.cjs");
|
|
12
12
|
const require_selectionStatus = require("./selectionStatus.cjs");
|
|
13
13
|
const require_sessionArguments = require("./sessionArguments.cjs");
|
|
14
14
|
const require_sessionArtifact = require("./sessionArtifact.cjs");
|
|
@@ -35,6 +35,7 @@ let _agimon_ai_doompi_core_headlessHub = require("@agimon-ai/doompi-core/headles
|
|
|
35
35
|
let _agimon_ai_doompi_core_headlessServer = require("@agimon-ai/doompi-core/headlessServer");
|
|
36
36
|
let _agimon_ai_doompi_core_headlessSessionManager = require("@agimon-ai/doompi-core/headlessSessionManager");
|
|
37
37
|
let _agimon_ai_doompi_core_history = require("@agimon-ai/doompi-core/history");
|
|
38
|
+
let _agimon_ai_doompi_core_packageApi = require("@agimon-ai/doompi-core/packageApi");
|
|
38
39
|
let _agimon_ai_doompi_core_packageApiServer = require("@agimon-ai/doompi-core/packageApiServer");
|
|
39
40
|
let _agimon_ai_doompi_core_piSettings = require("@agimon-ai/doompi-core/piSettings");
|
|
40
41
|
let _agimon_ai_doompi_core_remoteRuntime = require("@agimon-ai/doompi-core/remoteRuntime");
|
|
@@ -58,7 +59,7 @@ async function runServerRuntime(options, runtime) {
|
|
|
58
59
|
...incomingEnvironment,
|
|
59
60
|
LOG_SINK_INSTANCE: "global"
|
|
60
61
|
};
|
|
61
|
-
const executionBudget =
|
|
62
|
+
const executionBudget = (0, _agimon_ai_doompi_core_packageApi.createServerExecutionBudget)(baseEnvironment);
|
|
62
63
|
await require_logSink.ensureGlobalLogSink({
|
|
63
64
|
cwd: baseCwd,
|
|
64
65
|
env: baseEnvironment,
|
|
@@ -276,6 +277,19 @@ async function runServerRuntime(options, runtime) {
|
|
|
276
277
|
let remoteRuntime;
|
|
277
278
|
let attachToken;
|
|
278
279
|
let webCompositions;
|
|
280
|
+
const stopResourceMonitor = require_runtimeResources.monitorRuntimeResources({
|
|
281
|
+
record: (attributes) => telemetry.recordEvent("doompi_server.resources", attributes),
|
|
282
|
+
counts: () => {
|
|
283
|
+
const budget = executionBudget.getSnapshot();
|
|
284
|
+
return {
|
|
285
|
+
sessions: sessionManager.sessions().length,
|
|
286
|
+
running_jobs: budget.running,
|
|
287
|
+
queued_jobs: budget.queued,
|
|
288
|
+
job_limit: budget.limit
|
|
289
|
+
};
|
|
290
|
+
},
|
|
291
|
+
warn: notice
|
|
292
|
+
});
|
|
279
293
|
try {
|
|
280
294
|
await telemetry.runInSpan("doompi_server.startup", {}, async () => {
|
|
281
295
|
const token = node_fs.default.readFileSync(options.tokenFile, "utf8").trim();
|
|
@@ -300,26 +314,27 @@ async function runServerRuntime(options, runtime) {
|
|
|
300
314
|
activeLayers: (0, _agimon_ai_doompi_config_majorModes.resolveLayers)(config, majorMode)
|
|
301
315
|
};
|
|
302
316
|
})();
|
|
303
|
-
return (0, _agimon_ai_doompi_core_serverFacet.loadServerBundle)(scope, {
|
|
317
|
+
return telemetry.runInSpan("doompi_server.composition_load", { scope }, () => (0, _agimon_ai_doompi_core_serverFacet.loadServerBundle)(scope, {
|
|
304
318
|
...source,
|
|
305
319
|
...selected,
|
|
306
320
|
retainCandidates: scope === "session",
|
|
307
321
|
onNotice: notice
|
|
308
|
-
});
|
|
322
|
+
}));
|
|
309
323
|
};
|
|
310
324
|
const loadSessionMcp = async (root, selection, pinnedRegistration) => {
|
|
311
325
|
const registration = pinnedRegistration ?? (0, _agimon_ai_doompi_core_syncRegistration.readSyncRegistration)(root, homeDirectory);
|
|
312
326
|
if (registration?.mcpBundle === void 0) throw new Error(`Run the scoped DoomPi sync for '${root}' before opening its MCP runtime.`);
|
|
313
|
-
|
|
314
|
-
|
|
327
|
+
const bundle = registration.mcpBundle;
|
|
328
|
+
return telemetry.runInSpan("doompi_server.mcp_bundle_load", {}, () => (0, _agimon_ai_doompi_core_mcpFacet.loadMcpBundle)({
|
|
329
|
+
directory: node_path.default.dirname(bundle.path),
|
|
315
330
|
generation: registration.generation,
|
|
316
|
-
fingerprint:
|
|
317
|
-
descriptorSha256:
|
|
331
|
+
fingerprint: bundle.fingerprint,
|
|
332
|
+
descriptorSha256: bundle.sha256,
|
|
318
333
|
majorMode: selection.majorMode,
|
|
319
334
|
activeLayers: selection.activeLayers,
|
|
320
335
|
retainCandidates: true,
|
|
321
336
|
onNotice: notice
|
|
322
|
-
});
|
|
337
|
+
}));
|
|
323
338
|
};
|
|
324
339
|
const sharedApiContext = {
|
|
325
340
|
homeDirectory,
|
|
@@ -961,6 +976,7 @@ async function runServerRuntime(options, runtime) {
|
|
|
961
976
|
}
|
|
962
977
|
} finally {
|
|
963
978
|
shuttingDown = true;
|
|
979
|
+
stopResourceMonitor();
|
|
964
980
|
executionBudget.close();
|
|
965
981
|
stopPersistSessionNames();
|
|
966
982
|
clearInterval(eventLoopMonitor);
|