@agimon-ai/doompi 0.0.1-alpha.94 → 0.0.1-alpha.96
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 +7 -6
- package/dist/src/builders/cli/harnessContext.cjs.map +1 -1
- package/dist/src/builders/cli/harnessContext.mjs +7 -6
- package/dist/src/builders/cli/harnessContext.mjs.map +1 -1
- package/dist/src/builders/server/runtime.cjs +81 -61
- package/dist/src/builders/server/runtime.cjs.map +1 -1
- package/dist/src/builders/server/runtime.mjs +82 -62
- package/dist/src/builders/server/runtime.mjs.map +1 -1
- package/dist/src/builders/server/sessionArtifact.cjs +7 -3
- package/dist/src/builders/server/sessionArtifact.cjs.map +1 -1
- package/dist/src/builders/server/sessionArtifact.mjs +7 -3
- package/dist/src/builders/server/sessionArtifact.mjs.map +1 -1
- package/dist/src/cli/harnessOptions.cjs +5 -2
- package/dist/src/cli/harnessOptions.cjs.map +1 -1
- package/dist/src/cli/harnessOptions.d.mts +2 -0
- package/dist/src/cli/harnessOptions.d.mts.map +1 -1
- package/dist/src/cli/harnessOptions.mjs +5 -2
- package/dist/src/cli/harnessOptions.mjs.map +1 -1
- package/dist/src/compiler/index.cjs +2 -1
- package/dist/src/compiler/index.cjs.map +1 -1
- package/dist/src/compiler/index.mjs +2 -1
- package/dist/src/compiler/index.mjs.map +1 -1
- package/dist/src/composition/types/harness.d.mts +6 -0
- package/dist/src/composition/types/harness.d.mts.map +1 -1
- package/package.json +21 -21
|
@@ -46,7 +46,7 @@ function configurePreset(options, environment) {
|
|
|
46
46
|
}
|
|
47
47
|
/** Resolves the optional persona and environment profile selected for this run. */
|
|
48
48
|
function resolveHarnessProfile(options) {
|
|
49
|
-
return options.profile ? (0, _agimon_ai_doompi_config_profiles.resolveProfile)(options.repoRoot, options.profile) : void 0;
|
|
49
|
+
return options.profile ? (0, _agimon_ai_doompi_config_profiles.resolveProfile)(options.configRoot ?? options.repoRoot, options.profile) : void 0;
|
|
50
50
|
}
|
|
51
51
|
/**
|
|
52
52
|
* Resolves the full matrix and stages every resource Pi will need.
|
|
@@ -56,8 +56,9 @@ function resolveHarnessProfile(options) {
|
|
|
56
56
|
* and be removed once the selected command is done with it.
|
|
57
57
|
*/
|
|
58
58
|
async function buildHarnessContext(options, telemetry = (0, _agimon_ai_doompi_core_runtimeLogSinkTelemetry.createHarnessTelemetry)()) {
|
|
59
|
+
const configRoot = options.configRoot ?? options.repoRoot;
|
|
59
60
|
const configured = await telemetry.runInSpan(`doom_pi.${RESOLVE_CONFIG_PHASE}`, { "harness.phase": RESOLVE_CONFIG_PHASE }, async () => {
|
|
60
|
-
const majorModesConfig = (0, _agimon_ai_doompi_config_majorModes.loadMajorModesConfig)(
|
|
61
|
+
const majorModesConfig = (0, _agimon_ai_doompi_config_majorModes.loadMajorModesConfig)(configRoot, options.homeDirectory);
|
|
61
62
|
const selectedLayers = (0, _agimon_ai_doompi_config_majorModes.filterHookDisabledLayers)(majorModesConfig, (0, _agimon_ai_doompi_config_majorModes.resolveLayers)(majorModesConfig, options.majorMode), options.hooks);
|
|
62
63
|
const profile = resolveHarnessProfile(options);
|
|
63
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}`);
|
|
@@ -66,9 +67,9 @@ async function buildHarnessContext(options, telemetry = (0, _agimon_ai_doompi_co
|
|
|
66
67
|
selectedLayers,
|
|
67
68
|
hookGroups: (0, _agimon_ai_doompi_config_majorModes.layerHookGroups)(majorModesConfig, selectedLayers),
|
|
68
69
|
profile,
|
|
69
|
-
plugins: await (0, _agimon_ai_doompi_domain_plugins.materializePluginEntries)((0, _agimon_ai_doompi_config_domains.resolvePluginEntries)(
|
|
70
|
-
mcpAllowlist: (0, _agimon_ai_doompi_domain_mcp.resolveMcpAllowlist)(
|
|
71
|
-
sharedSkills: (0, _agimon_ai_doompi_config_domains.resolveSharedSkills)(
|
|
70
|
+
plugins: await (0, _agimon_ai_doompi_domain_plugins.materializePluginEntries)((0, _agimon_ai_doompi_config_domains.resolvePluginEntries)(configRoot, options.domains, options.pluginDirectories)),
|
|
71
|
+
mcpAllowlist: (0, _agimon_ai_doompi_domain_mcp.resolveMcpAllowlist)(configRoot, options.domains),
|
|
72
|
+
sharedSkills: (0, _agimon_ai_doompi_config_domains.resolveSharedSkills)(configRoot, options.domains)
|
|
72
73
|
};
|
|
73
74
|
}).catch(async (error) => {
|
|
74
75
|
await telemetry.recordError(_agimon_ai_doompi_core_runtimeLogSinkTelemetry.HARNESS_EVENT.configLoadFailed, error, {
|
|
@@ -80,7 +81,7 @@ async function buildHarnessContext(options, telemetry = (0, _agimon_ai_doompi_co
|
|
|
80
81
|
});
|
|
81
82
|
const { majorModesConfig, selectedLayers, hookGroups, profile, plugins, mcpAllowlist } = configured;
|
|
82
83
|
const personaDirectory = profile?.persona;
|
|
83
|
-
const personaRoot = profile?.personaRoot ??
|
|
84
|
+
const personaRoot = profile?.personaRoot ?? configRoot;
|
|
84
85
|
const resources = await telemetry.runInSpan(`doom_pi.${COLLECT_RESOURCES_PHASE}`, {
|
|
85
86
|
"harness.plugin.count": plugins.length,
|
|
86
87
|
"harness.agents": options.agents,
|
|
@@ -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.repoRoot, options.profile) : undefined;\n}\n\n/**\n * Resolves the full matrix and stages every resource Pi will need.\n *\n * The caller owns the returned cleanup(): the temporary directory holding\n * skills, agents, and the generated MCP config has to outlive this function\n * and be removed once the selected command is done with it.\n */\nexport async function buildHarnessContext(\n options: HarnessOptions,\n telemetry: HarnessTelemetry = createHarnessTelemetry(),\n): Promise<HarnessContext> {\n // Everything up to collectResources reads configuration off disk, so a single\n // malformed modes.yaml or profiles.yaml surfaces here as a failed launch\n // with no other record.\n const configured = await telemetry\n .runInSpan(`doom_pi.${RESOLVE_CONFIG_PHASE}`, { 'harness.phase': RESOLVE_CONFIG_PHASE }, async () => {\n const majorModesConfig = loadMajorModesConfig(options.repoRoot, options.homeDirectory);\n const selectedLayers = filterHookDisabledLayers(\n majorModesConfig,\n resolveLayers(majorModesConfig, options.majorMode),\n options.hooks,\n );\n const profile = resolveHarnessProfile(options);\n if (!fs.existsSync(options.cwd) || !fs.statSync(options.cwd).isDirectory()) {\n throw new Error(`Working directory does not exist: ${options.cwd}`);\n }\n return {\n majorModesConfig,\n selectedLayers,\n hookGroups: layerHookGroups(majorModesConfig, selectedLayers),\n profile,\n plugins: await materializePluginEntries(\n resolvePluginEntries(options.repoRoot, options.domains, options.pluginDirectories),\n ),\n mcpAllowlist: resolveMcpAllowlist(options.repoRoot, options.domains),\n sharedSkills: resolveSharedSkills(options.repoRoot, options.domains),\n };\n })\n .catch(async (error: unknown) => {\n await telemetry.recordError(HARNESS_EVENT.configLoadFailed, error, {\n 'harness.phase': RESOLVE_CONFIG_PHASE,\n 'harness.major_mode': options.majorMode,\n 'harness.domain_count': options.domains.length,\n });\n throw error;\n });\n\n const { majorModesConfig, selectedLayers, hookGroups, profile, plugins, mcpAllowlist } = configured;\n const personaDirectory = profile?.persona;\n // A global profile's persona lives beside the global config, not in the repo.\n const personaRoot = profile?.personaRoot ?? options.repoRoot;\n const resources = await telemetry\n .runInSpan(\n `doom_pi.${COLLECT_RESOURCES_PHASE}`,\n { 'harness.plugin.count': plugins.length, 'harness.agents': options.agents, 'harness.mcp': options.mcp },\n () =>\n collectResources(options.repoRoot, plugins, {\n agents: options.agents,\n mcp: options.mcp,\n // Set by `doom-pi sync`, which stages into a directory that outlives\n // the process rather than a temporary one.\n temporaryDirectory: options.resourceDirectory,\n skillCacheDirectory: path.join(\n options.resourceDirectory ??\n resolveSyncLocation(options.repoRoot, options.homeDirectory ?? os.homedir()).directory,\n 'cache',\n 'skills',\n ),\n mcpAllowlist,\n sharedSkills: configured.sharedSkills,\n pluginDataRoot: path.join(globalDoomConfigDirectory(options.homeDirectory ?? os.homedir()), 'plugin-data'),\n }),\n )\n .catch(async (error: unknown) => {\n // Conflicting skills, agents, or MCP servers all fail here, and the plugin\n // that caused it is only identifiable from the message.\n await telemetry.recordError(HARNESS_EVENT.resourceCollectionFailed, error, {\n 'harness.phase': COLLECT_RESOURCES_PHASE,\n 'harness.plugin.count': plugins.length,\n });\n throw error;\n });\n\n try {\n const defaultThemePath = await writeDefaultTheme(resources.temporaryDirectory);\n const environment = { ...process.env };\n // The file-backed projection published through Cordis is authoritative.\n // Never let a legacy parent-process projection leak into this Doom session.\n delete environment[DOOM_MCP_SESSION_ENV_VAR];\n const profileEnvironment = applyProfileEnvironment(environment, profile?.env ?? {});\n\n configurePreset(options, environment);\n environment.NX_DAEMON ??= 'false';\n environment.DOOMPI_THEME ||= DEFAULT_THEME_NAME;\n environment.CLAUDE_PROJECT_DIR = options.repoRoot;\n environment.CODEX_REPO_ROOT = options.repoRoot;\n environment.ORIGINAL_REPO_PATH ||= options.repoRoot;\n environment.MCP_UI_VIEWER = options.automation ? 'none' : (environment.MCP_UI_VIEWER ?? '');\n\n // Always loaded, even with no persona at launch: the extension no-ops\n // without a persona file, and /profile needs it present to load one later.\n // Pi freezes the --extension list at startup, so it cannot be added on demand.\n const personaEntry = packageEntry(PERSONA_ENTRY, import.meta.url);\n const personaPrompt = personaDirectory ? buildPersonaPrompt(personaRoot, personaDirectory) : undefined;\n let personaFile: string | undefined;\n if (personaPrompt) {\n personaFile = path.join(resources.temporaryDirectory, 'persona.md');\n await fs.promises.writeFile(personaFile, `${personaPrompt}\\n`, { mode: PRIVATE_FILE_MODE });\n }\n\n // One write, describing the child rather than this process. The state file\n // is the child's authority and the environment is what it projects for the\n // hooks and packages that can only read an environment.\n createHarnessSession(\n {\n root: options.repoRoot,\n majorMode: options.majorMode,\n temporaryDirectory: resources.temporaryDirectory,\n domains: options.domains,\n layers: selectedLayers,\n profile: profile?.name,\n profileIdentity: profile?.identity,\n profileVoice: profile?.voice,\n profileEnvironment,\n personaFile,\n skillDirectories: resources.skillDirectories,\n agentDirectories: resources.agentDirectories,\n additionalDirectories: options.additionalDirectories,\n childExtensions: [],\n pluginDirectories: options.pluginDirectories,\n pluginHooks: resources.pluginHooks,\n hookGroups,\n hooks: options.hooks,\n agents: options.agents,\n mcp: options.mcp,\n mcpConfigPath: resources.mcpConfigPath,\n mcpProjection: resources.mcpProjection,\n allowProtectedWrites: options.allowProtectedWrites,\n },\n { directory: resources.temporaryDirectory, environment },\n );\n if (options.agents) {\n environment.PI_SUBAGENT_EXTRA_AGENT_DIRS = resources.agentDirectories.join(path.delimiter);\n environment.PI_SUBAGENT_EXTRA_SKILL_DIRS = resources.skillDirectories.join(path.delimiter);\n environment.PI_SUBAGENT_PI_BINARY = path.join(options.repoRoot, 'pi.sh');\n }\n\n // Core hook groups load regardless; hookGroups selects the rest. The initial\n // state write includes an empty list deliberately: unset means every group.\n\n return {\n options,\n environment,\n resources,\n plugins,\n profile: profile?.name,\n personaDirectory,\n personaRoot,\n mcpAllowlist,\n majorModesConfig,\n selectedLayers,\n hookGroups,\n personaEntry,\n defaultThemePath,\n cleanup: resources.cleanup.bind(resources),\n };\n } catch (error) {\n // Preparation failed after the temp directory existed, so nothing will be\n // handed back to clean it up.\n await telemetry.recordError(HARNESS_EVENT.contextBuildFailed, error, {\n 'harness.phase': BUILD_CONTEXT_PHASE,\n 'harness.major_mode': options.majorMode,\n ...(profile?.name ? { 'harness.profile': profile.name } : {}),\n });\n await resources.cleanup();\n throw error;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"harnessContext.cjs","names":["resolveProfile","createHarnessTelemetry","loadMajorModesConfig","filterHookDisabledLayers","resolveLayers","fs","layerHookGroups","materializePluginEntries","resolvePluginEntries","resolveMcpAllowlist","resolveSharedSkills","HARNESS_EVENT","collectResources","path","resolveSyncLocation","os","globalDoomConfigDirectory","writeDefaultTheme","DOOM_MCP_SESSION_ENV_VAR","applyProfileEnvironment","DEFAULT_THEME_NAME","packageEntry","PERSONA_ENTRY","buildPersonaPrompt"],"sources":["../../../../src/builders/cli/harnessContext.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { globalDoomConfigDirectory } from '@agimon-ai/doompi-config/config';\nimport { resolvePluginEntries, resolveSharedSkills } from '@agimon-ai/doompi-config/domains';\nimport type { DomainMcpAllowlist, PluginEntry } from '@agimon-ai/doompi-config/domains';\nimport {\n filterHookDisabledLayers,\n layerHookGroups,\n loadMajorModesConfig,\n resolveLayers,\n} from '@agimon-ai/doompi-config/majorModes';\nimport type { MajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { applyProfileEnvironment, buildPersonaPrompt, resolveProfile } from '@agimon-ai/doompi-config/profiles';\nimport type { AgentProfile } from '@agimon-ai/doompi-config/profiles';\nimport { DOOM_MCP_SESSION_ENV_VAR } from '@agimon-ai/doompi-core/mcpSession';\nimport { packageEntry } from '@agimon-ai/doompi-core/moduleResolution';\nimport {\n createHarnessTelemetry,\n HARNESS_EVENT,\n type HarnessTelemetry,\n} from '@agimon-ai/doompi-core/runtimeLogSinkTelemetry';\nimport { resolveSyncLocation } from '@agimon-ai/doompi-core/syncLocation';\nimport { resolveMcpAllowlist } from '@agimon-ai/doompi-domain/mcp';\nimport { materializePluginEntries } from '@agimon-ai/doompi-domain/plugins';\nimport { collectResources, type HarnessResources } from '@agimon-ai/doompi-domain/resources';\nimport { DEFAULT_THEME_NAME, writeDefaultTheme } from '@agimon-ai/doompi-ui/theme';\n\nimport { createHarnessSession } from '../../composition/harnessState';\nimport type { HarnessOptions } from '../../composition/types/harness';\nimport { PERSONA_ENTRY } from './extensionAssembler';\n\nconst OLLAMA_PRESET = 'ollama';\nconst PRIVATE_FILE_MODE = 0o600;\nconst RESOLVE_CONFIG_PHASE = 'resolve_config';\nconst COLLECT_RESOURCES_PHASE = 'collect_resources';\nconst BUILD_CONTEXT_PHASE = 'build_context';\n\n/**\n * Everything a command needs, resolved once.\n *\n * All three commands need the same expensive preparation (domains resolved,\n * plugins collected into a temp directory, environment assembled), so it is\n * built here and handed to whichever command runs.\n */\nexport interface HarnessContext {\n options: HarnessOptions;\n environment: NodeJS.ProcessEnv;\n resources: HarnessResources;\n plugins: PluginEntry[];\n profile?: string;\n personaDirectory?: string;\n /** Root personaDirectory resolves against, when a profile supplied one. */\n personaRoot?: string;\n mcpAllowlist?: DomainMcpAllowlist;\n majorModesConfig: MajorModesConfig;\n selectedLayers: string[];\n hookGroups: string[];\n personaEntry?: string;\n defaultThemePath: string;\n cleanup(): Promise<void>;\n}\n\n/**\n * Applies the provider environment a preset needs.\n *\n * Exported because the synced Pi path has no launcher to do it: the doom-pi\n * extension applies the same rules in-process before anything reads them.\n * `piArgs` is only consulted to spot a selected cloud model.\n */\nexport function configurePreset(\n options: Pick<HarnessOptions, 'preset' | 'piArgs'>,\n environment: NodeJS.ProcessEnv,\n): void {\n if (options.preset === 'kimi') {\n if (!environment.KIMI_API_KEY && environment.KIMI_CODE_API_KEY) {\n environment.KIMI_API_KEY = environment.KIMI_CODE_API_KEY;\n }\n if (!environment.KIMI_API_KEY) {\n throw new Error('KIMI_API_KEY or KIMI_CODE_API_KEY is required for the kimi preset');\n }\n }\n if (options.preset === OLLAMA_PRESET) {\n const selected = options.piArgs.find((argument) => argument.startsWith(`${OLLAMA_PRESET}/`));\n const cloud = selected?.endsWith(':cloud') ?? false;\n if (cloud && !environment.OLLAMA_API_KEY) {\n throw new Error('OLLAMA_API_KEY is required for Ollama cloud models');\n }\n environment.OLLAMA_API_KEY ||= OLLAMA_PRESET;\n environment.DOOMPI_OLLAMA_BASE_URL =\n environment.OLLAMA_BASE_URL ?? environment.ANTHROPIC_BASE_URL ?? 'http://localhost:11434/v1';\n }\n}\n\n/** Resolves the optional persona and environment profile selected for this run. */\nexport function resolveHarnessProfile(options: HarnessOptions): AgentProfile | undefined {\n return options.profile ? resolveProfile(options.configRoot ?? options.repoRoot, options.profile) : undefined;\n}\n\n/**\n * Resolves the full matrix and stages every resource Pi will need.\n *\n * The caller owns the returned cleanup(): the temporary directory holding\n * skills, agents, and the generated MCP config has to outlive this function\n * and be removed once the selected command is done with it.\n */\nexport async function buildHarnessContext(\n options: HarnessOptions,\n telemetry: HarnessTelemetry = createHarnessTelemetry(),\n): Promise<HarnessContext> {\n // A worktree reads its workspace's configuration; resources still come from the checkout.\n const configRoot = options.configRoot ?? options.repoRoot;\n // Everything up to collectResources reads configuration off disk, so a single\n // malformed modes.yaml or profiles.yaml surfaces here as a failed launch\n // with no other record.\n const configured = await telemetry\n .runInSpan(`doom_pi.${RESOLVE_CONFIG_PHASE}`, { 'harness.phase': RESOLVE_CONFIG_PHASE }, async () => {\n const majorModesConfig = loadMajorModesConfig(configRoot, options.homeDirectory);\n const selectedLayers = filterHookDisabledLayers(\n majorModesConfig,\n resolveLayers(majorModesConfig, options.majorMode),\n options.hooks,\n );\n const profile = resolveHarnessProfile(options);\n if (!fs.existsSync(options.cwd) || !fs.statSync(options.cwd).isDirectory()) {\n throw new Error(`Working directory does not exist: ${options.cwd}`);\n }\n return {\n majorModesConfig,\n selectedLayers,\n hookGroups: layerHookGroups(majorModesConfig, selectedLayers),\n profile,\n plugins: await materializePluginEntries(\n resolvePluginEntries(configRoot, options.domains, options.pluginDirectories),\n ),\n mcpAllowlist: resolveMcpAllowlist(configRoot, options.domains),\n sharedSkills: resolveSharedSkills(configRoot, options.domains),\n };\n })\n .catch(async (error: unknown) => {\n await telemetry.recordError(HARNESS_EVENT.configLoadFailed, error, {\n 'harness.phase': RESOLVE_CONFIG_PHASE,\n 'harness.major_mode': options.majorMode,\n 'harness.domain_count': options.domains.length,\n });\n throw error;\n });\n\n const { majorModesConfig, selectedLayers, hookGroups, profile, plugins, mcpAllowlist } = configured;\n const personaDirectory = profile?.persona;\n // A global profile's persona lives beside the global config, not in the repo.\n const personaRoot = profile?.personaRoot ?? configRoot;\n const resources = await telemetry\n .runInSpan(\n `doom_pi.${COLLECT_RESOURCES_PHASE}`,\n { 'harness.plugin.count': plugins.length, 'harness.agents': options.agents, 'harness.mcp': options.mcp },\n () =>\n collectResources(options.repoRoot, plugins, {\n agents: options.agents,\n mcp: options.mcp,\n // Set by `doom-pi sync`, which stages into a directory that outlives\n // the process rather than a temporary one.\n temporaryDirectory: options.resourceDirectory,\n skillCacheDirectory: path.join(\n options.resourceDirectory ??\n resolveSyncLocation(options.repoRoot, options.homeDirectory ?? os.homedir()).directory,\n 'cache',\n 'skills',\n ),\n mcpAllowlist,\n sharedSkills: configured.sharedSkills,\n pluginDataRoot: path.join(globalDoomConfigDirectory(options.homeDirectory ?? os.homedir()), 'plugin-data'),\n }),\n )\n .catch(async (error: unknown) => {\n // Conflicting skills, agents, or MCP servers all fail here, and the plugin\n // that caused it is only identifiable from the message.\n await telemetry.recordError(HARNESS_EVENT.resourceCollectionFailed, error, {\n 'harness.phase': COLLECT_RESOURCES_PHASE,\n 'harness.plugin.count': plugins.length,\n });\n throw error;\n });\n\n try {\n const defaultThemePath = await writeDefaultTheme(resources.temporaryDirectory);\n const environment = { ...process.env };\n // The file-backed projection published through Cordis is authoritative.\n // Never let a legacy parent-process projection leak into this Doom session.\n delete environment[DOOM_MCP_SESSION_ENV_VAR];\n const profileEnvironment = applyProfileEnvironment(environment, profile?.env ?? {});\n\n configurePreset(options, environment);\n environment.NX_DAEMON ??= 'false';\n environment.DOOMPI_THEME ||= DEFAULT_THEME_NAME;\n environment.CLAUDE_PROJECT_DIR = options.repoRoot;\n environment.CODEX_REPO_ROOT = options.repoRoot;\n environment.ORIGINAL_REPO_PATH ||= options.repoRoot;\n environment.MCP_UI_VIEWER = options.automation ? 'none' : (environment.MCP_UI_VIEWER ?? '');\n\n // Always loaded, even with no persona at launch: the extension no-ops\n // without a persona file, and /profile needs it present to load one later.\n // Pi freezes the --extension list at startup, so it cannot be added on demand.\n const personaEntry = packageEntry(PERSONA_ENTRY, import.meta.url);\n const personaPrompt = personaDirectory ? buildPersonaPrompt(personaRoot, personaDirectory) : undefined;\n let personaFile: string | undefined;\n if (personaPrompt) {\n personaFile = path.join(resources.temporaryDirectory, 'persona.md');\n await fs.promises.writeFile(personaFile, `${personaPrompt}\\n`, { mode: PRIVATE_FILE_MODE });\n }\n\n // One write, describing the child rather than this process. The state file\n // is the child's authority and the environment is what it projects for the\n // hooks and packages that can only read an environment.\n createHarnessSession(\n {\n root: options.repoRoot,\n majorMode: options.majorMode,\n temporaryDirectory: resources.temporaryDirectory,\n domains: options.domains,\n layers: selectedLayers,\n profile: profile?.name,\n profileIdentity: profile?.identity,\n profileVoice: profile?.voice,\n profileEnvironment,\n personaFile,\n skillDirectories: resources.skillDirectories,\n agentDirectories: resources.agentDirectories,\n additionalDirectories: options.additionalDirectories,\n childExtensions: [],\n pluginDirectories: options.pluginDirectories,\n pluginHooks: resources.pluginHooks,\n hookGroups,\n hooks: options.hooks,\n agents: options.agents,\n mcp: options.mcp,\n mcpConfigPath: resources.mcpConfigPath,\n mcpProjection: resources.mcpProjection,\n allowProtectedWrites: options.allowProtectedWrites,\n },\n { directory: resources.temporaryDirectory, environment },\n );\n if (options.agents) {\n environment.PI_SUBAGENT_EXTRA_AGENT_DIRS = resources.agentDirectories.join(path.delimiter);\n environment.PI_SUBAGENT_EXTRA_SKILL_DIRS = resources.skillDirectories.join(path.delimiter);\n environment.PI_SUBAGENT_PI_BINARY = path.join(options.repoRoot, 'pi.sh');\n }\n\n // Core hook groups load regardless; hookGroups selects the rest. The initial\n // state write includes an empty list deliberately: unset means every group.\n\n return {\n options,\n environment,\n resources,\n plugins,\n profile: profile?.name,\n personaDirectory,\n personaRoot,\n mcpAllowlist,\n majorModesConfig,\n selectedLayers,\n hookGroups,\n personaEntry,\n defaultThemePath,\n cleanup: resources.cleanup.bind(resources),\n };\n } catch (error) {\n // Preparation failed after the temp directory existed, so nothing will be\n // handed back to clean it up.\n await telemetry.recordError(HARNESS_EVENT.contextBuildFailed, error, {\n 'harness.phase': BUILD_CONTEXT_PHASE,\n 'harness.major_mode': options.majorMode,\n ...(profile?.name ? { 'harness.profile': profile.name } : {}),\n });\n await resources.cleanup();\n throw error;\n }\n}\n"],"mappings":"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"}
|
|
@@ -41,7 +41,7 @@ function configurePreset(options, environment) {
|
|
|
41
41
|
}
|
|
42
42
|
/** Resolves the optional persona and environment profile selected for this run. */
|
|
43
43
|
function resolveHarnessProfile(options) {
|
|
44
|
-
return options.profile ? resolveProfile(options.repoRoot, options.profile) : void 0;
|
|
44
|
+
return options.profile ? resolveProfile(options.configRoot ?? options.repoRoot, options.profile) : void 0;
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
47
|
* Resolves the full matrix and stages every resource Pi will need.
|
|
@@ -51,8 +51,9 @@ function resolveHarnessProfile(options) {
|
|
|
51
51
|
* and be removed once the selected command is done with it.
|
|
52
52
|
*/
|
|
53
53
|
async function buildHarnessContext(options, telemetry = createHarnessTelemetry()) {
|
|
54
|
+
const configRoot = options.configRoot ?? options.repoRoot;
|
|
54
55
|
const configured = await telemetry.runInSpan(`doom_pi.${RESOLVE_CONFIG_PHASE}`, { "harness.phase": RESOLVE_CONFIG_PHASE }, async () => {
|
|
55
|
-
const majorModesConfig = loadMajorModesConfig(
|
|
56
|
+
const majorModesConfig = loadMajorModesConfig(configRoot, options.homeDirectory);
|
|
56
57
|
const selectedLayers = filterHookDisabledLayers(majorModesConfig, resolveLayers(majorModesConfig, options.majorMode), options.hooks);
|
|
57
58
|
const profile = resolveHarnessProfile(options);
|
|
58
59
|
if (!fs.existsSync(options.cwd) || !fs.statSync(options.cwd).isDirectory()) throw new Error(`Working directory does not exist: ${options.cwd}`);
|
|
@@ -61,9 +62,9 @@ async function buildHarnessContext(options, telemetry = createHarnessTelemetry()
|
|
|
61
62
|
selectedLayers,
|
|
62
63
|
hookGroups: layerHookGroups(majorModesConfig, selectedLayers),
|
|
63
64
|
profile,
|
|
64
|
-
plugins: await materializePluginEntries(resolvePluginEntries(
|
|
65
|
-
mcpAllowlist: resolveMcpAllowlist(
|
|
66
|
-
sharedSkills: resolveSharedSkills(
|
|
65
|
+
plugins: await materializePluginEntries(resolvePluginEntries(configRoot, options.domains, options.pluginDirectories)),
|
|
66
|
+
mcpAllowlist: resolveMcpAllowlist(configRoot, options.domains),
|
|
67
|
+
sharedSkills: resolveSharedSkills(configRoot, options.domains)
|
|
67
68
|
};
|
|
68
69
|
}).catch(async (error) => {
|
|
69
70
|
await telemetry.recordError(HARNESS_EVENT.configLoadFailed, error, {
|
|
@@ -75,7 +76,7 @@ async function buildHarnessContext(options, telemetry = createHarnessTelemetry()
|
|
|
75
76
|
});
|
|
76
77
|
const { majorModesConfig, selectedLayers, hookGroups, profile, plugins, mcpAllowlist } = configured;
|
|
77
78
|
const personaDirectory = profile?.persona;
|
|
78
|
-
const personaRoot = profile?.personaRoot ??
|
|
79
|
+
const personaRoot = profile?.personaRoot ?? configRoot;
|
|
79
80
|
const resources = await telemetry.runInSpan(`doom_pi.${COLLECT_RESOURCES_PHASE}`, {
|
|
80
81
|
"harness.plugin.count": plugins.length,
|
|
81
82
|
"harness.agents": options.agents,
|
|
@@ -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.repoRoot, options.profile) : undefined;\n}\n\n/**\n * Resolves the full matrix and stages every resource Pi will need.\n *\n * The caller owns the returned cleanup(): the temporary directory holding\n * skills, agents, and the generated MCP config has to outlive this function\n * and be removed once the selected command is done with it.\n */\nexport async function buildHarnessContext(\n options: HarnessOptions,\n telemetry: HarnessTelemetry = createHarnessTelemetry(),\n): Promise<HarnessContext> {\n // Everything up to collectResources reads configuration off disk, so a single\n // malformed modes.yaml or profiles.yaml surfaces here as a failed launch\n // with no other record.\n const configured = await telemetry\n .runInSpan(`doom_pi.${RESOLVE_CONFIG_PHASE}`, { 'harness.phase': RESOLVE_CONFIG_PHASE }, async () => {\n const majorModesConfig = loadMajorModesConfig(options.repoRoot, options.homeDirectory);\n const selectedLayers = filterHookDisabledLayers(\n majorModesConfig,\n resolveLayers(majorModesConfig, options.majorMode),\n options.hooks,\n );\n const profile = resolveHarnessProfile(options);\n if (!fs.existsSync(options.cwd) || !fs.statSync(options.cwd).isDirectory()) {\n throw new Error(`Working directory does not exist: ${options.cwd}`);\n }\n return {\n majorModesConfig,\n selectedLayers,\n hookGroups: layerHookGroups(majorModesConfig, selectedLayers),\n profile,\n plugins: await materializePluginEntries(\n resolvePluginEntries(options.repoRoot, options.domains, options.pluginDirectories),\n ),\n mcpAllowlist: resolveMcpAllowlist(options.repoRoot, options.domains),\n sharedSkills: resolveSharedSkills(options.repoRoot, options.domains),\n };\n })\n .catch(async (error: unknown) => {\n await telemetry.recordError(HARNESS_EVENT.configLoadFailed, error, {\n 'harness.phase': RESOLVE_CONFIG_PHASE,\n 'harness.major_mode': options.majorMode,\n 'harness.domain_count': options.domains.length,\n });\n throw error;\n });\n\n const { majorModesConfig, selectedLayers, hookGroups, profile, plugins, mcpAllowlist } = configured;\n const personaDirectory = profile?.persona;\n // A global profile's persona lives beside the global config, not in the repo.\n const personaRoot = profile?.personaRoot ?? options.repoRoot;\n const resources = await telemetry\n .runInSpan(\n `doom_pi.${COLLECT_RESOURCES_PHASE}`,\n { 'harness.plugin.count': plugins.length, 'harness.agents': options.agents, 'harness.mcp': options.mcp },\n () =>\n collectResources(options.repoRoot, plugins, {\n agents: options.agents,\n mcp: options.mcp,\n // Set by `doom-pi sync`, which stages into a directory that outlives\n // the process rather than a temporary one.\n temporaryDirectory: options.resourceDirectory,\n skillCacheDirectory: path.join(\n options.resourceDirectory ??\n resolveSyncLocation(options.repoRoot, options.homeDirectory ?? os.homedir()).directory,\n 'cache',\n 'skills',\n ),\n mcpAllowlist,\n sharedSkills: configured.sharedSkills,\n pluginDataRoot: path.join(globalDoomConfigDirectory(options.homeDirectory ?? os.homedir()), 'plugin-data'),\n }),\n )\n .catch(async (error: unknown) => {\n // Conflicting skills, agents, or MCP servers all fail here, and the plugin\n // that caused it is only identifiable from the message.\n await telemetry.recordError(HARNESS_EVENT.resourceCollectionFailed, error, {\n 'harness.phase': COLLECT_RESOURCES_PHASE,\n 'harness.plugin.count': plugins.length,\n });\n throw error;\n });\n\n try {\n const defaultThemePath = await writeDefaultTheme(resources.temporaryDirectory);\n const environment = { ...process.env };\n // The file-backed projection published through Cordis is authoritative.\n // Never let a legacy parent-process projection leak into this Doom session.\n delete environment[DOOM_MCP_SESSION_ENV_VAR];\n const profileEnvironment = applyProfileEnvironment(environment, profile?.env ?? {});\n\n configurePreset(options, environment);\n environment.NX_DAEMON ??= 'false';\n environment.DOOMPI_THEME ||= DEFAULT_THEME_NAME;\n environment.CLAUDE_PROJECT_DIR = options.repoRoot;\n environment.CODEX_REPO_ROOT = options.repoRoot;\n environment.ORIGINAL_REPO_PATH ||= options.repoRoot;\n environment.MCP_UI_VIEWER = options.automation ? 'none' : (environment.MCP_UI_VIEWER ?? '');\n\n // Always loaded, even with no persona at launch: the extension no-ops\n // without a persona file, and /profile needs it present to load one later.\n // Pi freezes the --extension list at startup, so it cannot be added on demand.\n const personaEntry = packageEntry(PERSONA_ENTRY, import.meta.url);\n const personaPrompt = personaDirectory ? buildPersonaPrompt(personaRoot, personaDirectory) : undefined;\n let personaFile: string | undefined;\n if (personaPrompt) {\n personaFile = path.join(resources.temporaryDirectory, 'persona.md');\n await fs.promises.writeFile(personaFile, `${personaPrompt}\\n`, { mode: PRIVATE_FILE_MODE });\n }\n\n // One write, describing the child rather than this process. The state file\n // is the child's authority and the environment is what it projects for the\n // hooks and packages that can only read an environment.\n createHarnessSession(\n {\n root: options.repoRoot,\n majorMode: options.majorMode,\n temporaryDirectory: resources.temporaryDirectory,\n domains: options.domains,\n layers: selectedLayers,\n profile: profile?.name,\n profileIdentity: profile?.identity,\n profileVoice: profile?.voice,\n profileEnvironment,\n personaFile,\n skillDirectories: resources.skillDirectories,\n agentDirectories: resources.agentDirectories,\n additionalDirectories: options.additionalDirectories,\n childExtensions: [],\n pluginDirectories: options.pluginDirectories,\n pluginHooks: resources.pluginHooks,\n hookGroups,\n hooks: options.hooks,\n agents: options.agents,\n mcp: options.mcp,\n mcpConfigPath: resources.mcpConfigPath,\n mcpProjection: resources.mcpProjection,\n allowProtectedWrites: options.allowProtectedWrites,\n },\n { directory: resources.temporaryDirectory, environment },\n );\n if (options.agents) {\n environment.PI_SUBAGENT_EXTRA_AGENT_DIRS = resources.agentDirectories.join(path.delimiter);\n environment.PI_SUBAGENT_EXTRA_SKILL_DIRS = resources.skillDirectories.join(path.delimiter);\n environment.PI_SUBAGENT_PI_BINARY = path.join(options.repoRoot, 'pi.sh');\n }\n\n // Core hook groups load regardless; hookGroups selects the rest. The initial\n // state write includes an empty list deliberately: unset means every group.\n\n return {\n options,\n environment,\n resources,\n plugins,\n profile: profile?.name,\n personaDirectory,\n personaRoot,\n mcpAllowlist,\n majorModesConfig,\n selectedLayers,\n hookGroups,\n personaEntry,\n defaultThemePath,\n cleanup: resources.cleanup.bind(resources),\n };\n } catch (error) {\n // Preparation failed after the temp directory existed, so nothing will be\n // handed back to clean it up.\n await telemetry.recordError(HARNESS_EVENT.contextBuildFailed, error, {\n 'harness.phase': BUILD_CONTEXT_PHASE,\n 'harness.major_mode': options.majorMode,\n ...(profile?.name ? { 'harness.profile': profile.name } : {}),\n });\n await resources.cleanup();\n throw error;\n }\n}\n"],"mappings":"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"}
|
|
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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"}
|
|
@@ -149,17 +149,17 @@ async function runServerRuntime(options, runtime) {
|
|
|
149
149
|
});
|
|
150
150
|
const failures = [];
|
|
151
151
|
try {
|
|
152
|
-
await
|
|
152
|
+
await baseSessionManager.closeSession(sessionId);
|
|
153
153
|
} catch (error) {
|
|
154
154
|
failures.push(error);
|
|
155
155
|
}
|
|
156
156
|
try {
|
|
157
|
-
await artifacts?.
|
|
157
|
+
await artifacts?.apis.close();
|
|
158
158
|
} catch (error) {
|
|
159
159
|
failures.push(error);
|
|
160
160
|
}
|
|
161
161
|
try {
|
|
162
|
-
await
|
|
162
|
+
await artifacts?.cleanup();
|
|
163
163
|
} catch (error) {
|
|
164
164
|
failures.push(error);
|
|
165
165
|
}
|
|
@@ -335,6 +335,7 @@ async function runServerRuntime(options, runtime) {
|
|
|
335
335
|
remoteRuntime = (0, _agimon_ai_doompi_core_remoteRuntime.createRemoteRuntime)({
|
|
336
336
|
homeDirectory,
|
|
337
337
|
registrationToken: token,
|
|
338
|
+
reservedPorts: () => [options.webPort],
|
|
338
339
|
bundleTrust: () => webCompositions?.shellTrust(),
|
|
339
340
|
onNotice: notice,
|
|
340
341
|
forward: async (request) => {
|
|
@@ -362,21 +363,27 @@ async function runServerRuntime(options, runtime) {
|
|
|
362
363
|
});
|
|
363
364
|
webCompositions.publish({ scope: "global" }, (0, _agimon_ai_doompi_core_syncRegistration.readSyncRegistration)(globalRoot, homeDirectory), hub.channelTypes());
|
|
364
365
|
const admissions = /* @__PURE__ */ new Map();
|
|
366
|
+
const isMemberCheckout = (from, workspace) => node_fs.default.realpathSync(require_index$1.findRepositoryRoot(from)) !== workspace.root;
|
|
365
367
|
admitWorkspace = async (from) => {
|
|
366
|
-
const
|
|
367
|
-
const id = (0,
|
|
368
|
+
const checkoutRoot = node_fs.default.realpathSync(require_index$1.findRepositoryRoot(from));
|
|
369
|
+
const { id, root, moved } = (0, _agimon_ai_doompi_core_history.identifyWorkspace)({
|
|
370
|
+
records: workspaces.list(),
|
|
371
|
+
checkoutRoot,
|
|
372
|
+
marker: (0, _agimon_ai_doompi_core_history.readWorkspaceMarker)(checkoutRoot)
|
|
373
|
+
});
|
|
368
374
|
const existing = hub.workspaces().find((workspace) => workspace.id === id);
|
|
369
|
-
if (existing !== void 0 && existing.available !== false) return {
|
|
375
|
+
if (existing !== void 0 && existing.available !== false && existing.root === root) return {
|
|
370
376
|
...existing,
|
|
371
377
|
available: true
|
|
372
378
|
};
|
|
373
379
|
const pending = admissions.get(id);
|
|
374
380
|
if (pending) return pending;
|
|
375
381
|
const wasRemembered = workspaces.list().some((workspace) => workspace.id === id);
|
|
376
|
-
if (!wasRemembered) workspaces.add({
|
|
382
|
+
if (!wasRemembered || moved) workspaces.add({
|
|
377
383
|
id,
|
|
378
384
|
root
|
|
379
385
|
});
|
|
386
|
+
if (moved) notice(`Workspace '${id}' moved to '${root}'.`);
|
|
380
387
|
hub.registerWorkspace({
|
|
381
388
|
id,
|
|
382
389
|
root,
|
|
@@ -402,6 +409,11 @@ async function runServerRuntime(options, runtime) {
|
|
|
402
409
|
scope: "workspace",
|
|
403
410
|
workspaceId: id
|
|
404
411
|
}, (0, _agimon_ai_doompi_core_syncRegistration.readSyncRegistration)(root, homeDirectory), hub.channelTypes());
|
|
412
|
+
try {
|
|
413
|
+
if ((0, _agimon_ai_doompi_core_history.readWorkspaceMarker)(root) === void 0) (0, _agimon_ai_doompi_core_history.writeWorkspaceMarker)(root, id);
|
|
414
|
+
} catch (error) {
|
|
415
|
+
notice(`Workspace '${root}' id could not be recorded in its git data: ${error instanceof Error ? error.message : String(error)}`);
|
|
416
|
+
}
|
|
405
417
|
return {
|
|
406
418
|
id,
|
|
407
419
|
root,
|
|
@@ -424,12 +436,10 @@ async function runServerRuntime(options, runtime) {
|
|
|
424
436
|
for (const record of openSessions.list()) {
|
|
425
437
|
if (workspaces.list().some((workspace) => workspace.id === record.workspaceId)) continue;
|
|
426
438
|
try {
|
|
427
|
-
const candidate = record.groupingRoot ??
|
|
428
|
-
if (!candidate) throw new Error("Parent workspace location is unavailable.");
|
|
439
|
+
const candidate = record.groupingRoot ?? record.artifact?.root ?? record.cwd;
|
|
429
440
|
const root = node_fs.default.realpathSync(require_index$1.findRepositoryRoot(candidate));
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
id,
|
|
441
|
+
if ((record.groupingRoot === void 0 || root === candidate) && !workspaces.list().some((workspace) => workspace.root === root)) workspaces.add({
|
|
442
|
+
id: record.workspaceId,
|
|
433
443
|
root
|
|
434
444
|
});
|
|
435
445
|
else notice(`Session '${record.sessionId}' no longer resolves to its recorded workspace; skipping registration.`);
|
|
@@ -443,9 +453,7 @@ async function runServerRuntime(options, runtime) {
|
|
|
443
453
|
available: false
|
|
444
454
|
});
|
|
445
455
|
try {
|
|
446
|
-
|
|
447
|
-
const id = (0, _agimon_ai_doompi_core_syncLocation.resolveSyncLocation)(root, homeDirectory).identity.worktreeId;
|
|
448
|
-
if (root !== workspace.root || id !== workspace.id) {
|
|
456
|
+
if (node_fs.default.realpathSync(require_index$1.findRepositoryRoot(workspace.root)) !== workspace.root) {
|
|
449
457
|
notice(`Workspace '${workspace.root}' no longer resolves to its recorded identity; leaving it unavailable.`);
|
|
450
458
|
continue;
|
|
451
459
|
}
|
|
@@ -454,7 +462,7 @@ async function runServerRuntime(options, runtime) {
|
|
|
454
462
|
notice(`Workspace '${workspace.root}' could not be restored: ${error instanceof Error ? error.message : String(error)}`);
|
|
455
463
|
}
|
|
456
464
|
}
|
|
457
|
-
const sessionHostOptions = (context, bundle, mcpBundle, registration, identity,
|
|
465
|
+
const sessionHostOptions = (context, bundle, mcpBundle, registration, identity, workspace) => {
|
|
458
466
|
const policyOptions = context.options;
|
|
459
467
|
const sessionSelection = {
|
|
460
468
|
root: policyOptions.repoRoot,
|
|
@@ -470,9 +478,9 @@ async function runServerRuntime(options, runtime) {
|
|
|
470
478
|
cwd: policyOptions.cwd,
|
|
471
479
|
repoRoot: policyOptions.repoRoot,
|
|
472
480
|
sessionId: identity.sessionId,
|
|
473
|
-
workspaceId:
|
|
474
|
-
groupingRoot:
|
|
475
|
-
...
|
|
481
|
+
workspaceId: workspace.id,
|
|
482
|
+
groupingRoot: workspace.root,
|
|
483
|
+
...isMemberCheckout(policyOptions.repoRoot, workspace) ? { inheritedArtifact: registration } : {},
|
|
476
484
|
sessionName: identity.sessionName,
|
|
477
485
|
webComposition: webCompositions?.publish({
|
|
478
486
|
scope: "session",
|
|
@@ -498,7 +506,8 @@ async function runServerRuntime(options, runtime) {
|
|
|
498
506
|
const defaults = resolveHarnessOptions({
|
|
499
507
|
args: ["--cwd", policyOptions.cwd],
|
|
500
508
|
cwd: policyOptions.cwd,
|
|
501
|
-
environment
|
|
509
|
+
environment,
|
|
510
|
+
...policyOptions.configRoot === void 0 ? {} : { configRoot: policyOptions.configRoot }
|
|
502
511
|
});
|
|
503
512
|
return {
|
|
504
513
|
majorMode: defaults.majorMode,
|
|
@@ -509,7 +518,7 @@ async function runServerRuntime(options, runtime) {
|
|
|
509
518
|
candidates: bundle.descriptor.entries,
|
|
510
519
|
mcpPlugins: mcpBundle.plugins,
|
|
511
520
|
resolveSelection: (requested) => {
|
|
512
|
-
const config = (0, _agimon_ai_doompi_config_majorModes.loadMajorModesConfig)(policyOptions.repoRoot, policyOptions.homeDirectory);
|
|
521
|
+
const config = (0, _agimon_ai_doompi_config_majorModes.loadMajorModesConfig)(policyOptions.configRoot ?? policyOptions.repoRoot, policyOptions.homeDirectory);
|
|
513
522
|
return {
|
|
514
523
|
...requested,
|
|
515
524
|
activeLayers: (0, _agimon_ai_doompi_config_majorModes.filterHookDisabledLayers)(config, (0, _agimon_ai_doompi_config_majorModes.resolveLayers)(config, requested.majorMode), policyOptions.hooks)
|
|
@@ -544,6 +553,7 @@ async function runServerRuntime(options, runtime) {
|
|
|
544
553
|
facets: pendingSessions.get(sessionOptions.sessionId)?.bundle.facets ?? [],
|
|
545
554
|
workspaceId: sessionOptions.workspaceId,
|
|
546
555
|
workspaceRoot: sessionOptions.repoRoot,
|
|
556
|
+
...host.sessionContext === void 0 ? {} : { sessionContext: host.sessionContext },
|
|
547
557
|
homeDirectory,
|
|
548
558
|
mountChannel: (channel) => {
|
|
549
559
|
return {
|
|
@@ -582,9 +592,9 @@ async function runServerRuntime(options, runtime) {
|
|
|
582
592
|
}), harnessTelemetry);
|
|
583
593
|
const activeHarnessContext = harnessContext;
|
|
584
594
|
try {
|
|
585
|
-
await admitWorkspace(harnessContext.options.repoRoot);
|
|
586
|
-
const initialRegistration = (0, _agimon_ai_doompi_core_syncRegistration.readSyncRegistration)(
|
|
587
|
-
if (initialRegistration === void 0) throw new Error(`Run the scoped DoomPi sync for '${
|
|
595
|
+
const initialWorkspace = await admitWorkspace(harnessContext.options.repoRoot);
|
|
596
|
+
const initialRegistration = (0, _agimon_ai_doompi_core_syncRegistration.readSyncRegistration)(initialWorkspace.root, homeDirectory);
|
|
597
|
+
if (initialRegistration === void 0) throw new Error(`Run the scoped DoomPi sync for '${initialWorkspace.root}' before opening it.`);
|
|
588
598
|
const initialBundle = await loadComposition(harnessContext.options.repoRoot, "session", {
|
|
589
599
|
root: harnessContext.options.repoRoot,
|
|
590
600
|
majorMode: harnessContext.options.majorMode,
|
|
@@ -600,7 +610,7 @@ async function runServerRuntime(options, runtime) {
|
|
|
600
610
|
mcpBundle: initialMcpBundle,
|
|
601
611
|
registration: initialRegistration
|
|
602
612
|
});
|
|
603
|
-
await hub.create(sessionHostOptions(harnessContext, initialBundle, initialMcpBundle, initialRegistration, resolved.identity));
|
|
613
|
+
await hub.create(sessionHostOptions(harnessContext, initialBundle, initialMcpBundle, initialRegistration, resolved.identity, initialWorkspace));
|
|
604
614
|
} catch (error) {
|
|
605
615
|
pendingSessions.delete(resolved.identity.sessionId);
|
|
606
616
|
await activeHarnessContext.cleanup().catch((cleanupError) => notice(String(cleanupError)));
|
|
@@ -639,14 +649,15 @@ async function runServerRuntime(options, runtime) {
|
|
|
639
649
|
parentSessionId: request.parentSessionId,
|
|
640
650
|
sessionProvenance: request.sessionProvenance
|
|
641
651
|
};
|
|
642
|
-
const
|
|
643
|
-
const inheritedWorkspaceId = isWorktree && request.parentSessionId ? groupingWorkspaceId ?? hub.session(request.parentSessionId)?.workspaceId ?? openSessions.list().find((record) => record.sessionId === request.parentSessionId)?.workspaceId : void 0;
|
|
644
|
-
if (isWorktree && inheritedWorkspaceId === void 0) throw new Error("Worktree session requires its parent workspace.");
|
|
652
|
+
const inheritedWorkspaceId = groupingWorkspaceId ?? (request.parentSessionId ? hub.session(request.parentSessionId)?.workspaceId ?? openSessions.list().find((record) => record.sessionId === request.parentSessionId)?.workspaceId : void 0);
|
|
645
653
|
const parentArtifact = request.parentSessionId === void 0 ? void 0 : sessionArtifacts.get(request.parentSessionId)?.registration ?? pendingSessions.get(request.parentSessionId)?.registration ?? openSessions.list().find((record) => record.sessionId === request.parentSessionId)?.artifact;
|
|
646
654
|
const start = (async () => {
|
|
647
655
|
const childIdentity = require_sessionArguments.resolveSessionIdentity([], identity);
|
|
648
656
|
const childEnvironment = { ...baseEnvironment };
|
|
649
657
|
for (const key of [_agimon_ai_doompi_config_harnessStore.HARNESS_STATE_POINTER, ...Object.values(_agimon_ai_doompi_config_harnessState.HARNESS_STATE_KEYS)]) delete childEnvironment[key];
|
|
658
|
+
const workspace = inheritedWorkspaceId === void 0 ? await admitWorkspace(request.cwd) : hub.workspaces().find((candidate) => candidate.id === inheritedWorkspaceId && candidate.available !== false);
|
|
659
|
+
if (workspace === void 0) throw new Error("The session workspace is unavailable.");
|
|
660
|
+
const member = isMemberCheckout(request.cwd, workspace);
|
|
650
661
|
const childContext = await require_harnessContext.buildHarnessContext(resolveHarnessOptions({
|
|
651
662
|
args: [
|
|
652
663
|
"--cwd",
|
|
@@ -654,20 +665,18 @@ async function runServerRuntime(options, runtime) {
|
|
|
654
665
|
...childIdentity.agentArgs
|
|
655
666
|
],
|
|
656
667
|
cwd: request.cwd,
|
|
657
|
-
environment: childEnvironment
|
|
668
|
+
environment: childEnvironment,
|
|
669
|
+
...member ? { configRoot: workspace.root } : {}
|
|
658
670
|
}), harnessTelemetry);
|
|
659
671
|
try {
|
|
660
|
-
if (inheritedWorkspaceId !== void 0) {
|
|
661
|
-
if (hub.workspaces().find((workspace) => workspace.id === inheritedWorkspaceId && workspace.available !== false) === void 0) throw new Error("Worktree parent workspace is unavailable.");
|
|
662
|
-
}
|
|
663
672
|
const registration = await require_sessionArtifact.resolveSessionArtifact({
|
|
664
|
-
|
|
673
|
+
member,
|
|
665
674
|
pinned: pinnedArtifact,
|
|
666
675
|
parent: parentArtifact,
|
|
676
|
+
workspace: () => (0, _agimon_ai_doompi_core_syncRegistration.readSyncRegistration)(workspace.root, homeDirectory),
|
|
667
677
|
prepareCurrent: async () => {
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
if (current === void 0) throw new Error(`Run the scoped DoomPi sync for '${childContext.options.repoRoot}' before opening it.`);
|
|
678
|
+
const current = (0, _agimon_ai_doompi_core_syncRegistration.readSyncRegistration)(workspace.root, homeDirectory);
|
|
679
|
+
if (current === void 0) throw new Error(`Run the scoped DoomPi sync for '${workspace.root}' before opening it.`);
|
|
671
680
|
return current;
|
|
672
681
|
}
|
|
673
682
|
});
|
|
@@ -687,7 +696,7 @@ async function runServerRuntime(options, runtime) {
|
|
|
687
696
|
mcpBundle,
|
|
688
697
|
registration
|
|
689
698
|
});
|
|
690
|
-
const created = await hub.create(sessionHostOptions(childContext, bundle, mcpBundle, registration, identity,
|
|
699
|
+
const created = await hub.create(sessionHostOptions(childContext, bundle, mcpBundle, registration, identity, workspace));
|
|
691
700
|
request.signal?.throwIfAborted();
|
|
692
701
|
if (created.workspaceId !== void 0) {
|
|
693
702
|
if (openSessions.add({
|
|
@@ -695,12 +704,12 @@ async function runServerRuntime(options, runtime) {
|
|
|
695
704
|
workspaceId: created.workspaceId,
|
|
696
705
|
cwd: created.cwd,
|
|
697
706
|
repoRoot: childContext.options.repoRoot,
|
|
698
|
-
groupingRoot:
|
|
707
|
+
groupingRoot: workspace.root,
|
|
699
708
|
name: created.name,
|
|
700
709
|
createdAt: created.createdAt,
|
|
701
710
|
...created.parentSessionId === void 0 ? {} : { parentSessionId: created.parentSessionId },
|
|
702
711
|
...created.sessionProvenance === void 0 ? {} : { sessionProvenance: created.sessionProvenance },
|
|
703
|
-
...
|
|
712
|
+
...member ? { artifact: registration } : {}
|
|
704
713
|
}) === false) throw new Error("The new session could not be durably recorded.");
|
|
705
714
|
}
|
|
706
715
|
return {
|
|
@@ -735,11 +744,11 @@ async function runServerRuntime(options, runtime) {
|
|
|
735
744
|
const root = hub.workspaces().find((workspace) => workspace.id === workspaceId && workspace.available !== false)?.root;
|
|
736
745
|
if (!root || target.groupingRoot !== root || target.workspaceId && target.workspaceId !== workspaceId) throw new Error("Saved session does not belong to this workspace.");
|
|
737
746
|
if (node_fs.default.realpathSync(require_index$1.findRepositoryRoot(target.cwd)) !== target.repoRoot) throw new Error("Saved session checkout is unavailable.");
|
|
738
|
-
if (target.
|
|
739
|
-
if (
|
|
740
|
-
const artifact = (0, _agimon_ai_doompi_core_syncRegistration.parseSyncRegistration)(target.inheritedArtifact, "saved
|
|
747
|
+
if (target.repoRoot === root) return void 0;
|
|
748
|
+
if (target.inheritedArtifact === void 0) throw new Error("Saved member checkout generation is unavailable.");
|
|
749
|
+
const artifact = (0, _agimon_ai_doompi_core_syncRegistration.parseSyncRegistration)(target.inheritedArtifact, "saved member checkout generation");
|
|
741
750
|
(0, _agimon_ai_doompi_core_syncRegistration.validateSyncRegistration)(artifact, (0, _agimon_ai_doompi_core_syncLocation.resolveSyncLocation)(artifact.root, homeDirectory));
|
|
742
|
-
if (artifact.root !== root) throw new Error("Saved
|
|
751
|
+
if (artifact.root !== root) throw new Error("Saved member checkout generation belongs to another workspace.");
|
|
743
752
|
return artifact;
|
|
744
753
|
};
|
|
745
754
|
cockpit = await (0, _agimon_ai_doompi_core_headlessServer.serveHeadlessServer)({
|
|
@@ -764,7 +773,7 @@ async function runServerRuntime(options, runtime) {
|
|
|
764
773
|
name: target.summary.name ?? "untitled",
|
|
765
774
|
...target.execution.parentSessionId ? { parentSessionId: target.execution.parentSessionId } : {},
|
|
766
775
|
...target.execution.sessionProvenance ? { sessionProvenance: target.execution.sessionProvenance } : {}
|
|
767
|
-
}, target.summary.id, artifact,
|
|
776
|
+
}, target.summary.id, artifact, workspaceId);
|
|
768
777
|
return target.summary.id;
|
|
769
778
|
})();
|
|
770
779
|
workspaceResumes.set(key, resume);
|
|
@@ -792,15 +801,15 @@ async function runServerRuntime(options, runtime) {
|
|
|
792
801
|
});
|
|
793
802
|
},
|
|
794
803
|
restartSession: async (session) => {
|
|
795
|
-
const worktree = session.sessionProvenance === "worktree";
|
|
796
804
|
const record = openSessions.list().find((entry) => entry.sessionId === session.id);
|
|
797
|
-
const
|
|
805
|
+
const member = record?.artifact !== void 0;
|
|
806
|
+
const ownership = member ? require_sessionArtifact.resolveWorktreeRestart({
|
|
798
807
|
session,
|
|
799
808
|
record,
|
|
800
809
|
artifact: sessionArtifacts.get(session.id)?.registration,
|
|
801
810
|
workspaces: hub.workspaces()
|
|
802
811
|
}) : void 0;
|
|
803
|
-
if (!
|
|
812
|
+
if (!member) {
|
|
804
813
|
const workspaceRoot = hub.workspaces().find((workspace) => workspace.id === session.workspaceId)?.root;
|
|
805
814
|
if (!workspaceRoot) throw new Error("Session workspace not found.");
|
|
806
815
|
const syncEnvironment = {
|
|
@@ -816,12 +825,17 @@ async function runServerRuntime(options, runtime) {
|
|
|
816
825
|
}
|
|
817
826
|
}
|
|
818
827
|
await hub.closeSession(session.id);
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
828
|
+
try {
|
|
829
|
+
await openSession({
|
|
830
|
+
cwd: session.cwd,
|
|
831
|
+
name: session.name,
|
|
832
|
+
...session.parentSessionId === void 0 ? {} : { parentSessionId: session.parentSessionId },
|
|
833
|
+
...session.sessionProvenance === void 0 ? {} : { sessionProvenance: session.sessionProvenance }
|
|
834
|
+
}, session.id, ownership?.artifact, ownership?.workspaceId ?? session.workspaceId);
|
|
835
|
+
} catch (error) {
|
|
836
|
+
if (record !== void 0) openSessions.add(record);
|
|
837
|
+
throw error;
|
|
838
|
+
}
|
|
825
839
|
},
|
|
826
840
|
resumeSession: async (session, targetSessionId) => {
|
|
827
841
|
const workspaceId = session.workspaceId;
|
|
@@ -829,13 +843,19 @@ async function runServerRuntime(options, runtime) {
|
|
|
829
843
|
const target = (await savedHistory(workspaceId)).find((item) => item.summary.id === targetSessionId);
|
|
830
844
|
if (!target) throw new Error("Saved Pi thread not found in this workspace.");
|
|
831
845
|
const artifact = validatedExecution(target.execution, workspaceId);
|
|
846
|
+
const closedRecord = openSessions.list().find((entry) => entry.sessionId === session.id);
|
|
832
847
|
await hub.closeSession(session.id);
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
848
|
+
try {
|
|
849
|
+
await openSession({
|
|
850
|
+
cwd: target.execution.cwd,
|
|
851
|
+
name: target.summary.name ?? "untitled",
|
|
852
|
+
...target.execution.parentSessionId ? { parentSessionId: target.execution.parentSessionId } : {},
|
|
853
|
+
...target.execution.sessionProvenance ? { sessionProvenance: target.execution.sessionProvenance } : {}
|
|
854
|
+
}, target.summary.id, artifact, workspaceId);
|
|
855
|
+
} catch (error) {
|
|
856
|
+
if (closedRecord !== void 0) openSessions.add(closedRecord);
|
|
857
|
+
throw error;
|
|
858
|
+
}
|
|
839
859
|
return target.summary.id;
|
|
840
860
|
},
|
|
841
861
|
dormantSessions: () => openSessions.list(),
|
|
@@ -844,13 +864,13 @@ async function runServerRuntime(options, runtime) {
|
|
|
844
864
|
const groupingRoot = hub.workspaces().find((workspace) => workspace.id === record.workspaceId && workspace.available !== false)?.root;
|
|
845
865
|
if (!groupingRoot || record.groupingRoot !== void 0 && groupingRoot !== record.groupingRoot) throw new Error("The session workspace is unavailable.");
|
|
846
866
|
if (record.repoRoot !== void 0 && node_fs.default.realpathSync(require_index$1.findRepositoryRoot(record.cwd)) !== record.repoRoot) throw new Error("The session checkout has changed.");
|
|
847
|
-
if (record.
|
|
867
|
+
if (record.artifact !== void 0 && record.artifact.root !== groupingRoot) throw new Error("The inherited workspace generation is unavailable.");
|
|
848
868
|
await openSession({
|
|
849
869
|
cwd: record.cwd,
|
|
850
870
|
name: record.name,
|
|
851
871
|
...record.parentSessionId === void 0 ? {} : { parentSessionId: record.parentSessionId },
|
|
852
872
|
...record.sessionProvenance === void 0 ? {} : { sessionProvenance: record.sessionProvenance }
|
|
853
|
-
}, record.sessionId, record.
|
|
873
|
+
}, record.sessionId, record.artifact, record.workspaceId);
|
|
854
874
|
},
|
|
855
875
|
requestAsset: (request) => webCompositions?.request(request) ?? Promise.resolve(void 0),
|
|
856
876
|
compositions: () => ({
|