@agimon-ai/doompi 0.0.1-alpha.2 → 0.0.1-alpha.4

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.
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../_virtual/_rolldown/runtime.cjs`),t=require(`../config/harnessState.cjs`),n=require(`../utils/repository.cjs`);require(`../utils/index.cjs`);const r=require(`./extensionAssembler.cjs`),i=require(`./resourceCollector.cjs`),a=require(`./harnessContext.cjs`),o=require(`./extensionCompiler.cjs`),s=require(`./syncState.cjs`),c=require(`./matrixSwitcher.cjs`);let l=require(`node:path`);l=e.__toESM(l,1);let u=require(`@agimon-ai/doompi-config/profiles`),d=require(`@agimon-ai/doompi-config/majorModes`),f=require(`node:fs`);f=e.__toESM(f,1);let p=require(`node:url`),m=require(`@earendil-works/pi-coding-agent`),h=require(`@agimon-ai/doompi-config`);const g=`DOOMPI_COMPOSED`,_=`DOOMPI_MUTE`,v=`doompi is configured for Pi but this repository was never synced. Run doompi sync.`,y={majorMode:`major-mode`,domains:`domains`,profile:`profile`,mute:`mute`},b=`layer`;function x(e){return e instanceof Error?e.message:String(e)}function S(e=process.env){return e[g]===`1`}function C(e){try{return f.default.realpathSync(e)}catch{return l.default.resolve(e)}}function w(e,t){let{extensions:n}=(0,m.parseArgs)(e);if(!n||n.length===0)return!1;let r=new Set(Object.values(t).map(C));return n.some(e=>r.has(C(e)))}function T(e){if(f.default.existsSync(s.syncStatePath(e)))return e;try{let t=n.findRepositoryRoot(e);return f.default.existsSync(s.syncStatePath(t))?t:void 0}catch{return}}async function E(e,n,r=process.env){let i=await A(n),a={...t.readHarnessState({...e.env,...r}),...e.fileState,temporaryDirectory:i};(0,h.createHarnessSession)(a,{directory:i,environment:r});for(let[e,t]of Object.entries(a.profileEnvironment))r[e]===void 0&&(r[e]=t);return a}function D(e){let{unknownFlags:t}=(0,m.parseArgs)(e),n=e=>{let n=t.get(e);return typeof n==`string`&&n.trim()?n.trim():void 0},r=n(y.domains);return{majorMode:n(y.majorMode),domains:r?.split(`,`).map(e=>e.trim()).filter(Boolean),profile:n(y.profile),mute:t.get(y.mute)!==void 0,removedLayer:t.get(b)!==void 0}}function O(e){e.registerFlag(y.majorMode,{type:`string`,description:`Named major mode from .doom/modes.yaml`}),e.registerFlag(y.domains,{type:`string`,description:`Comma-separated content domains`}),e.registerFlag(y.profile,{type:`string`,description:`Persona and env from .doom/profiles.yaml`}),e.registerFlag(y.mute,{type:`boolean`,description:`Disable the notification extension`})}async function k(e,n,r,i){if(e.removedLayer&&i.push(`--${b} was replaced by --${y.majorMode}`),e.majorMode)try{c.applyMajorMode(n,e.majorMode,t.getHarnessState())}catch(t){i.push(`--${y.majorMode} ${e.majorMode}: ${x(t)}`)}if(e.domains&&e.domains.length>0)try{await c.applyDomains(e.domains,t.getHarnessState())}catch(t){i.push(`--domains ${e.domains.join(`,`)}: ${x(t)}`)}if(e.profile)try{await c.applyProfile((0,u.resolveProfile)(r,e.profile),t.getHarnessState())}catch(t){i.push(`--profile ${e.profile}: ${x(t)}`)}}async function A(e,n=process.env){let r=s.runDirectory(e);return await f.default.promises.mkdir(r,{mode:448,recursive:!0}),t.updateHarnessState({temporaryDirectory:r},n),r}async function j(e){await f.default.promises.rm(s.runDirectory(e),{recursive:!0,force:!0})}async function M(e,n=t.getHarnessState(),a=process.env){let c=n.root??e.root,u=s.createMapResolvers(e.resolved,e.compiled),f=n.mcp?u.optionalPackageEntry(r.CONTEXT_PACKAGES.mcp):void 0,p={agents:n.agents,autoStop:!1,preset:e.selection.preset,mcpEntry:f,personaEntry:u.ownEntry(r.PERSONA_ENTRY),workflowExtension:i.selectWorkflowExtensionForProcess(u.optionalPackageEntry(r.CONTEXT_PACKAGES.workflow),u.optionalPackageEntry(r.CONTEXT_PACKAGES.dispatcherWorkflow),a),layers:[...n.layers],majorModesConfig:(0,d.loadMajorModesConfig)(c),resolvers:u};t.updateHarnessState({childExtensions:r.assembleChildExtensions({...p,agents:!1,mute:!0})},a),n.agents&&(a.PI_SUBAGENT_EXTRA_AGENT_DIRS=n.agentDirectories.join(l.default.delimiter),a.PI_SUBAGENT_EXTRA_SKILL_DIRS=n.skillDirectories.join(l.default.delimiter));let m=[...r.assembleExtensions({...p,mute:a[_]===`1`})].reverse(),h=e.bundles?.[o.extensionSetKey(m)];return h?[h]:m}async function N(e,t,n){let r=[];for(let i of t)try{let t=await import((0,p.pathToFileURL)(i).href);if(typeof t.default!=`function`){n.push(`${i}: does not export an extension factory`);continue}await t.default(e),r.push(i)}catch(e){n.push(`${i}: ${x(e)}`)}return r}async function P(e,t={}){let n=t.environment??process.env,r=[],i=T(t.cwd??process.cwd());if(!i)return{problems:[v],stale:!1,loaded:[]};let o;try{o=s.readSyncState(i)}catch(e){return{problems:[x(e)],stale:!1,loaded:[]}}if(!o)return{problems:[v],stale:!1,loaded:[]};let c=t.argv??process.argv.slice(2);if(n.DOOMPI_EXTENSIONS_PROVIDED===`1`||w(c,{...o.resolved,...o.bundles}))return{problems:[],stale:!1,loaded:[]};if(a.configurePreset({preset:o.selection.preset,piArgs:[]},n),!S(n)){n[g]=`1`,await E(o,i,n);let e=D(c);e.mute&&(n[_]=`1`),await k(e,(0,d.loadMajorModesConfig)(i),i,r)}let l;try{l=await M(o,(0,h.loadHarnessState)(n).state,n)}catch(e){return{problems:[...r,x(e)],stale:!1,loaded:[]}}let u=await N(e,l,r);return{problems:r,stale:s.computeInputsHash(i,o.selection)!==o.inputsHash,loaded:u}}exports.COMPOSED_ENV=g,exports.DOOM_FLAGS=y,exports.EXTERNAL_EXTENSIONS_ENV=`DOOMPI_EXTENSIONS_PROVIDED`,exports.MUTE_ENV=_,exports.REMOVED_LAYER_FLAG=b,exports.alreadyComposed=S,exports.applyStartupFlags=k,exports.cleanupRunDirectory=j,exports.composeDoomSession=P,exports.composeLoadOrder=M,exports.extensionsProvidedExternally=w,exports.findSyncedRoot=T,exports.loadComposedExtensions=N,exports.prepareRunDirectory=A,exports.readStartupFlags=D,exports.registerDoomFlags=O,exports.startSyncedSession=E;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../_virtual/_rolldown/runtime.cjs`),t=require(`../config/harnessState.cjs`),n=require(`../utils/repository.cjs`);require(`../utils/index.cjs`);const r=require(`./extensionAssembler.cjs`),i=require(`./resourceCollector.cjs`),a=require(`./harnessContext.cjs`),o=require(`./extensionCompiler.cjs`),s=require(`./syncState.cjs`),c=require(`./matrixSwitcher.cjs`);let l=require(`node:path`);l=e.__toESM(l,1);let u=require(`@agimon-ai/doompi-config/profiles`),d=require(`@agimon-ai/doompi-config/majorModes`),f=require(`node:fs`);f=e.__toESM(f,1);let p=require(`node:url`),m=require(`@agimon-ai/doompi-ui/extensionName`),h=require(`@earendil-works/pi-coding-agent`),g=require(`@agimon-ai/doompi-config`);const _=`DOOMPI_COMPOSED`,v=`DOOMPI_MUTE`,y=`doompi is configured for Pi but this repository was never synced. Run doompi sync.`,b={majorMode:`major-mode`,domains:`domains`,profile:`profile`,mute:`mute`},x=`layer`;function S(e){return e instanceof Error?e.message:String(e)}function C(e=process.env){return e[_]===`1`}function w(e){try{return f.default.realpathSync(e)}catch{return l.default.resolve(e)}}function T(e,t){let{extensions:n}=(0,h.parseArgs)(e);if(!n||n.length===0)return!1;let r=new Set(Object.values(t).map(w));return n.some(e=>r.has(w(e)))}function E(e){if(f.default.existsSync(s.syncStatePath(e)))return e;try{let t=n.findRepositoryRoot(e);return f.default.existsSync(s.syncStatePath(t))?t:void 0}catch{return}}async function D(e,n,r=process.env){let i=await j(n),a={...t.readHarnessState({...e.env,...r}),...e.fileState,temporaryDirectory:i};(0,g.createHarnessSession)(a,{directory:i,environment:r});for(let[e,t]of Object.entries(a.profileEnvironment))r[e]===void 0&&(r[e]=t);return a}function O(e){let{unknownFlags:t}=(0,h.parseArgs)(e),n=e=>{let n=t.get(e);return typeof n==`string`&&n.trim()?n.trim():void 0},r=n(b.domains);return{majorMode:n(b.majorMode),domains:r?.split(`,`).map(e=>e.trim()).filter(Boolean),profile:n(b.profile),mute:t.get(b.mute)!==void 0,removedLayer:t.get(x)!==void 0}}function k(e){e.registerFlag(b.majorMode,{type:`string`,description:`Named major mode from .doom/modes.yaml`}),e.registerFlag(b.domains,{type:`string`,description:`Comma-separated content domains`}),e.registerFlag(b.profile,{type:`string`,description:`Persona and env from .doom/profiles.yaml`}),e.registerFlag(b.mute,{type:`boolean`,description:`Disable the notification extension`})}async function A(e,n,r,i){if(e.removedLayer&&i.push(`--${x} was replaced by --${b.majorMode}`),e.majorMode)try{c.applyMajorMode(n,e.majorMode,t.getHarnessState())}catch(t){i.push(`--${b.majorMode} ${e.majorMode}: ${S(t)}`)}if(e.domains&&e.domains.length>0)try{await c.applyDomains(e.domains,t.getHarnessState())}catch(t){i.push(`--domains ${e.domains.join(`,`)}: ${S(t)}`)}if(e.profile)try{await c.applyProfile((0,u.resolveProfile)(r,e.profile),t.getHarnessState())}catch(t){i.push(`--profile ${e.profile}: ${S(t)}`)}}async function j(e,n=process.env){let r=s.runDirectory(e);return await f.default.promises.mkdir(r,{mode:448,recursive:!0}),t.updateHarnessState({temporaryDirectory:r},n),r}async function M(e){await f.default.promises.rm(s.runDirectory(e),{recursive:!0,force:!0})}async function N(e,n=t.getHarnessState(),a=process.env){let c=n.root??e.root,u=s.createMapResolvers(e.resolved,e.compiled),f=n.mcp?u.optionalPackageEntry(r.CONTEXT_PACKAGES.mcp):void 0,p={agents:n.agents,autoStop:!1,preset:e.selection.preset,mcpEntry:f,personaEntry:u.ownEntry(r.PERSONA_ENTRY),workflowExtension:i.selectWorkflowExtensionForProcess(u.optionalPackageEntry(r.CONTEXT_PACKAGES.workflow),u.optionalPackageEntry(r.CONTEXT_PACKAGES.dispatcherWorkflow),a),layers:[...n.layers],majorModesConfig:(0,d.loadMajorModesConfig)(c),resolvers:u};t.updateHarnessState({childExtensions:r.assembleChildExtensions({...p,agents:!1,mute:!0})},a),n.agents&&(a.PI_SUBAGENT_EXTRA_AGENT_DIRS=n.agentDirectories.join(l.default.delimiter),a.PI_SUBAGENT_EXTRA_SKILL_DIRS=n.skillDirectories.join(l.default.delimiter));let m=[...r.assembleExtensions({...p,mute:a[v]===`1`})].reverse(),h=e.bundles?.[o.extensionSetKey(m)];return h?[h]:m}async function P(e,t,n){let r=[];for(let i of t)try{let t=await import((0,p.pathToFileURL)(i).href);if(typeof t.default!=`function`){n.push(`${i}: does not export an extension factory`);continue}await t.default((0,m.withExtensionSource)(e,i)),r.push(i)}catch(e){n.push(`${i}: ${S(e)}`)}return r}async function F(e,t={}){let n=t.environment??process.env,r=[],i=E(t.cwd??process.cwd());if(!i)return{problems:[y],stale:!1,loaded:[]};let o;try{o=s.readSyncState(i)}catch(e){return{problems:[S(e)],stale:!1,loaded:[]}}if(!o)return{problems:[y],stale:!1,loaded:[]};let c=t.argv??process.argv.slice(2);if(n.DOOMPI_EXTENSIONS_PROVIDED===`1`||T(c,{...o.resolved,...o.bundles}))return{problems:[],stale:!1,loaded:[]};if(a.configurePreset({preset:o.selection.preset,piArgs:[]},n),!C(n)){n[_]=`1`,await D(o,i,n);let e=O(c);e.mute&&(n[v]=`1`),await A(e,(0,d.loadMajorModesConfig)(i),i,r)}let l;try{l=await N(o,(0,g.loadHarnessState)(n).state,n)}catch(e){return{problems:[...r,S(e)],stale:!1,loaded:[]}}let u=await P(e,l,r);return{problems:r,stale:s.computeInputsHash(i,o.selection)!==o.inputsHash,loaded:u}}exports.COMPOSED_ENV=_,exports.DOOM_FLAGS=b,exports.EXTERNAL_EXTENSIONS_ENV=`DOOMPI_EXTENSIONS_PROVIDED`,exports.MUTE_ENV=v,exports.REMOVED_LAYER_FLAG=x,exports.alreadyComposed=C,exports.applyStartupFlags=A,exports.cleanupRunDirectory=M,exports.composeDoomSession=F,exports.composeLoadOrder=N,exports.extensionsProvidedExternally=T,exports.findSyncedRoot=E,exports.loadComposedExtensions=P,exports.prepareRunDirectory=j,exports.readStartupFlags=O,exports.registerDoomFlags=k,exports.startSyncedSession=D;
2
2
  //# sourceMappingURL=composer.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"composer.cjs","names":["fs","path","syncStatePath","findRepositoryRoot","readHarnessState","getHarnessState","applyDomains","applyProfile","runDirectory","createMapResolvers","CONTEXT_PACKAGES","PERSONA_ENTRY","selectWorkflowExtensionForProcess","assembleChildExtensions","assembleExtensions","extensionSetKey","readSyncState","computeInputsHash"],"sources":["../../src/services/composer.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { loadMajorModesConfig, type MajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { resolveProfile } from '@agimon-ai/doompi-config/profiles';\nimport { parseArgs, type ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport {\n createHarnessSession,\n getHarnessState,\n loadHarnessState,\n readHarnessState,\n updateHarnessState,\n type HarnessState,\n} from '../config/harnessState.ts';\nimport type { HarnessPreset } from '../interfaces/index.ts';\nimport { findRepositoryRoot } from '../utils/index.ts';\nimport { CONTEXT_PACKAGES, PERSONA_ENTRY, assembleChildExtensions, assembleExtensions } from './extensionAssembler.ts';\nimport { extensionSetKey } from './extensionCompiler.ts';\nimport { configurePreset } from './harnessContext.ts';\nimport { applyDomains, applyMajorMode, applyProfile } from './matrixSwitcher.ts';\nimport { selectWorkflowExtensionForProcess } from './resourceCollector.ts';\nimport {\n computeInputsHash,\n createMapResolvers,\n readSyncState,\n runDirectory,\n syncStatePath,\n type SyncState,\n} from './syncState.ts';\n\n/**\n * The runtime half of `doom-pi sync`.\n *\n * Pi's own config carries a single entry, the doom-pi extension, and this is\n * what that entry runs: hydrate the harness environment, apply the startup\n * flags, then load the extension set the matrix selects. It runs again on every\n * `/reload`, which is what lets `/major-mode` swap the set without a restart,\n * since Pi rebuilds its extension runner from whatever a reload produced.\n */\n\n/** Marks a process whose extensions this module composed. */\nexport const COMPOSED_ENV = 'DOOMPI_COMPOSED';\n/** Set by the launcher when it passes one precomposed aggregate to Pi. */\nexport const EXTERNAL_EXTENSIONS_ENV = 'DOOMPI_EXTENSIONS_PROVIDED';\n/** Survives reload, where argv is re-read but the startup flags must not be. */\nexport const MUTE_ENV = 'DOOMPI_MUTE';\nconst ENABLED_FLAG = '1';\nconst SUBAGENT_AGENT_DIRS_ENV = 'PI_SUBAGENT_EXTRA_AGENT_DIRS';\nconst SUBAGENT_SKILL_DIRS_ENV = 'PI_SUBAGENT_EXTRA_SKILL_DIRS';\nconst DOMAIN_SEPARATOR = ',';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst NOT_SYNCED = 'doompi is configured for Pi but this repository was never synced. Run doompi sync.';\n\nexport const DOOM_FLAGS = {\n majorMode: 'major-mode',\n domains: 'domains',\n profile: 'profile',\n mute: 'mute',\n} as const;\n\n/** Removed flag, reported rather than ignored when a synced session passes it. */\nexport const REMOVED_LAYER_FLAG = 'layer';\n\nexport interface StartupFlags {\n majorMode?: string;\n domains?: string[];\n profile?: string;\n mute: boolean;\n /** True when the caller passed the removed `--layer` flag. */\n removedLayer: boolean;\n}\n\nexport interface ComposeOutcome {\n /** Everything that went wrong, reported once the session can show messages. */\n problems: string[];\n /** Config changed since the last sync, which is a prompt to re-sync. */\n stale: boolean;\n loaded: string[];\n}\n\nfunction message(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** True when this process already applied the command line, so a reload does not. */\nexport function alreadyComposed(environment: NodeJS.ProcessEnv = process.env): boolean {\n return environment[COMPOSED_ENV] === ENABLED_FLAG;\n}\n\nfunction realPath(target: string): string {\n try {\n return fs.realpathSync(target);\n } catch {\n return path.resolve(target);\n }\n}\n\n/**\n * True when this process was handed the doom extension set on the command line.\n *\n * The launcher and detached Doom Team children both pass the composed list as\n * `--extension` arguments, and Pi merges those with whatever project settings\n * declare. Composing again here would load every extension twice, which Pi\n * reports as each tool and flag conflicting with itself.\n *\n * Matching against the synced paths rather than the mere presence of\n * `--extension` keeps an unrelated one-off, `pi -e ./debug.ts`, from silently\n * costing the user their whole setup.\n */\nexport function extensionsProvidedExternally(argv: string[], resolved: Record<string, string>): boolean {\n const { extensions } = parseArgs(argv);\n if (!extensions || extensions.length === 0) return false;\n const composed = new Set(Object.values(resolved).map(realPath));\n return extensions.some((entry) => composed.has(realPath(entry)));\n}\n\n/**\n * Finds the repository whose synced state applies to this session.\n *\n * Pi only reads `.pi/settings.json` from its own working directory, so that is\n * the first place to look; walking up covers a session started in a\n * subdirectory with the extension configured globally.\n */\nexport function findSyncedRoot(cwd: string): string | undefined {\n if (fs.existsSync(syncStatePath(cwd))) return cwd;\n try {\n const root = findRepositoryRoot(cwd);\n return fs.existsSync(syncStatePath(root)) ? root : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Opens this process's session from what sync pinned.\n *\n * The synced record is decoded underneath the live environment, so an inherited\n * value still wins: a launcher run, a nested session, or a CI override is more\n * specific than anything sync wrote. The result becomes the session's state\n * file, which is the authority from here on; the environment it publishes is\n * for the bash hooks and the packages that can only read one.\n */\nexport async function startSyncedSession(\n state: SyncState,\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<HarnessState> {\n const directory = await prepareRunDirectory(repoRoot);\n const harness: HarnessState = {\n ...readHarnessState({ ...state.env, ...environment }),\n // Recorded separately because no variable carries them.\n ...state.fileState,\n temporaryDirectory: directory,\n };\n createHarnessSession(harness, { directory, environment });\n // The profile's own defaults, which the launcher applies at spawn. They are\n // recorded as a map rather than as variables so /profile can tell which ones\n // it owns when it swaps them out later.\n for (const [key, value] of Object.entries(harness.profileEnvironment)) {\n if (environment[key] === undefined) environment[key] = value;\n }\n return harness;\n}\n\n/**\n * Reads the doom flags off the command line.\n *\n * Pi collects unrecognized flags for extensions but hands their values back\n * only after every extension has loaded, which is too late to decide what to\n * load. Parsing argv with Pi's own parser keeps the value rules identical.\n */\nexport function readStartupFlags(argv: string[]): StartupFlags {\n const { unknownFlags } = parseArgs(argv);\n const text = (name: string): string | undefined => {\n const value = unknownFlags.get(name);\n return typeof value === 'string' && value.trim() ? value.trim() : undefined;\n };\n const domains = text(DOOM_FLAGS.domains);\n return {\n majorMode: text(DOOM_FLAGS.majorMode),\n domains: domains\n ?.split(DOMAIN_SEPARATOR)\n .map((name) => name.trim())\n .filter(Boolean),\n profile: text(DOOM_FLAGS.profile),\n mute: unknownFlags.get(DOOM_FLAGS.mute) !== undefined,\n removedLayer: unknownFlags.get(REMOVED_LAYER_FLAG) !== undefined,\n };\n}\n\n/** Registers the flags so Pi accepts them and lists them in `pi --help`. */\nexport function registerDoomFlags(pi: Pick<ExtensionAPI, 'registerFlag'>): void {\n pi.registerFlag(DOOM_FLAGS.majorMode, {\n type: 'string',\n description: 'Named major mode from .doom/modes.yaml',\n });\n pi.registerFlag(DOOM_FLAGS.domains, { type: 'string', description: 'Comma-separated content domains' });\n pi.registerFlag(DOOM_FLAGS.profile, { type: 'string', description: 'Persona and env from .doom/profiles.yaml' });\n pi.registerFlag(DOOM_FLAGS.mute, { type: 'boolean', description: 'Disable the notification extension' });\n}\n\n/**\n * Applies the startup flags through the same switchers the slash commands use.\n *\n * A bad value is collected rather than thrown: a factory that throws takes the\n * whole doom setup down with it, which is far worse than one unapplied flag.\n */\nexport async function applyStartupFlags(\n flags: StartupFlags,\n majorModesConfig: MajorModesConfig,\n repoRoot: string,\n problems: string[],\n): Promise<void> {\n // Reported rather than ignored: Pi hands unrecognized flags to extensions, so\n // a stale `--layer dev` would otherwise start a session on the wrong mode\n // without saying anything.\n if (flags.removedLayer) problems.push(`--${REMOVED_LAYER_FLAG} was replaced by --${DOOM_FLAGS.majorMode}`);\n if (flags.majorMode) {\n try {\n applyMajorMode(majorModesConfig, flags.majorMode, getHarnessState());\n } catch (error) {\n problems.push(`--${DOOM_FLAGS.majorMode} ${flags.majorMode}: ${message(error)}`);\n }\n }\n if (flags.domains && flags.domains.length > 0) {\n try {\n await applyDomains(flags.domains, getHarnessState());\n } catch (error) {\n problems.push(`--domains ${flags.domains.join(DOMAIN_SEPARATOR)}: ${message(error)}`);\n }\n }\n if (flags.profile) {\n try {\n await applyProfile(resolveProfile(repoRoot, flags.profile), getHarnessState());\n } catch (error) {\n problems.push(`--profile ${flags.profile}: ${message(error)}`);\n }\n }\n}\n\n/**\n * Points harness state at a per-process directory before anything writes there.\n *\n * The synced directory is a baseline that every session reads; live switches\n * materialize into this one instead, so a second session in the same repository\n * cannot overwrite what the first is using.\n */\nexport async function prepareRunDirectory(\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<string> {\n const directory = runDirectory(repoRoot);\n await fs.promises.mkdir(directory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n // Through the store rather than the environment: the state file is the\n // authority, and a switcher reading a stale directory would write a live\n // session's resources into the wrong place.\n updateHarnessState({ temporaryDirectory: directory }, environment);\n return directory;\n}\n\n/** Removes this session's scratch directory. */\nexport async function cleanupRunDirectory(repoRoot: string): Promise<void> {\n await fs.promises.rm(runDirectory(repoRoot), { recursive: true, force: true });\n}\n\n/**\n * Builds the ordered list of extensions to load for the current state.\n *\n * Returns them in Pi load order. `assembleExtensions` produces the order the\n * launcher passes to Pi, where the last element becomes the first `--extension`\n * argument, so reversing here keeps that rule stated in exactly one place.\n */\nexport async function composeLoadOrder(\n state: SyncState,\n harness: HarnessState = getHarnessState(),\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<string[]> {\n const repoRoot = harness.root ?? state.root;\n const resolvers = createMapResolvers(state.resolved, state.compiled);\n // Resolved from the sync map rather than generated: doom-mcp reads the\n // repository and plugin layers itself, and the environment the launcher wrote\n // already tells it which plugins and allowlist this session selected.\n const mcpEntry = harness.mcp ? resolvers.optionalPackageEntry(CONTEXT_PACKAGES.mcp) : undefined;\n const context = {\n agents: harness.agents,\n // Exiting when the agent settles is a launcher decision with no synced\n // equivalent: a session started by hand is meant to stay open.\n autoStop: false,\n preset: state.selection.preset,\n mcpEntry,\n personaEntry: resolvers.ownEntry(PERSONA_ENTRY),\n workflowExtension: selectWorkflowExtensionForProcess(\n resolvers.optionalPackageEntry(CONTEXT_PACKAGES.workflow),\n resolvers.optionalPackageEntry(CONTEXT_PACKAGES.dispatcherWorkflow),\n environment,\n ),\n layers: [...harness.layers],\n majorModesConfig: loadMajorModesConfig(repoRoot),\n resolvers,\n };\n\n // A state field, so it goes through the store: Doom Team reads it from the\n // environment the store publishes when it builds a child's spawn snapshot.\n updateHarnessState(\n { childExtensions: assembleChildExtensions({ ...context, agents: false, mute: true }) },\n environment,\n );\n if (harness.agents) {\n environment[SUBAGENT_AGENT_DIRS_ENV] = harness.agentDirectories.join(path.delimiter);\n environment[SUBAGENT_SKILL_DIRS_ENV] = harness.skillDirectories.join(path.delimiter);\n }\n const entries = [...assembleExtensions({ ...context, mute: environment[MUTE_ENV] === ENABLED_FLAG })].reverse();\n const bundle = state.bundles?.[extensionSetKey(entries)];\n return bundle ? [bundle] : entries;\n}\n\n/**\n * Imports each extension and hands it the same Pi API this module received.\n *\n * Every Pi extension entry is a factory of the same shape, so one extension can\n * activate the rest. Failures are per entry: one unloadable module must not\n * cost the session everything after it in the order.\n */\nexport async function loadComposedExtensions(\n pi: ExtensionAPI,\n entries: string[],\n problems: string[],\n): Promise<string[]> {\n const loaded: string[] = [];\n for (const entry of entries) {\n try {\n const module = (await import(pathToFileURL(entry).href)) as { default?: unknown };\n if (typeof module.default !== 'function') {\n problems.push(`${entry}: does not export an extension factory`);\n continue;\n }\n await (module.default as (api: ExtensionAPI) => void | Promise<void>)(pi);\n loaded.push(entry);\n } catch (error) {\n problems.push(`${entry}: ${message(error)}`);\n }\n }\n return loaded;\n}\n\nexport interface ComposeOptions {\n cwd?: string;\n argv?: string[];\n environment?: NodeJS.ProcessEnv;\n}\n\n/**\n * Runs the whole synced startup for one Pi session.\n *\n * Never throws: whatever fails is reported through the outcome so the session\n * still starts and can say what is wrong.\n */\nexport async function composeDoomSession(pi: ExtensionAPI, options: ComposeOptions = {}): Promise<ComposeOutcome> {\n const environment = options.environment ?? process.env;\n const problems: string[] = [];\n const repoRoot = findSyncedRoot(options.cwd ?? process.cwd());\n if (!repoRoot) return { problems: [NOT_SYNCED], stale: false, loaded: [] };\n\n let state: SyncState | undefined;\n try {\n state = readSyncState(repoRoot);\n } catch (error) {\n return { problems: [message(error)], stale: false, loaded: [] };\n }\n if (!state) return { problems: [NOT_SYNCED], stale: false, loaded: [] };\n\n const argv = options.argv ?? process.argv.slice(2);\n // The launcher and Doom Team children compose the set themselves and pass it\n // in, so this entry has nothing left to do but stay out of the way.\n if (\n environment[EXTERNAL_EXTENSIONS_ENV] === ENABLED_FLAG ||\n extensionsProvidedExternally(argv, { ...state.resolved, ...state.bundles })\n ) {\n return { problems: [], stale: false, loaded: [] };\n }\n\n configurePreset({ preset: state.selection.preset as HarnessPreset, piArgs: [] }, environment);\n\n // Only the first load opens a session. A reload finds the pointer this set\n // and reads the file back, which is what carries a live /domains or /profile\n // switch across the reload instead of resetting it to what sync pinned.\n if (!alreadyComposed(environment)) {\n environment[COMPOSED_ENV] = ENABLED_FLAG;\n await startSyncedSession(state, repoRoot, environment);\n const flags = readStartupFlags(argv);\n if (flags.mute) environment[MUTE_ENV] = ENABLED_FLAG;\n await applyStartupFlags(flags, loadMajorModesConfig(repoRoot), repoRoot, problems);\n }\n\n let entries: string[];\n try {\n // Read back through the store rather than from this process: the startup\n // flags may have switched the layer or the domains a moment ago, and the\n // environment being managed is not always this process's own.\n entries = await composeLoadOrder(state, loadHarnessState(environment).state, environment);\n } catch (error) {\n return { problems: [...problems, message(error)], stale: false, loaded: [] };\n }\n\n const loaded = await loadComposedExtensions(pi, entries, problems);\n return { problems, stale: computeInputsHash(repoRoot, state.selection) !== state.inputsHash, loaded };\n}\n"],"mappings":"6tBAyCA,MAAa,EAAe,kBAIf,EAAW,cAMlB,EAAa,qFAEN,EAAa,CACxB,UAAW,aACX,QAAS,UACT,QAAS,UACT,KAAM,OACP,CAGY,EAAqB,QAmBlC,SAAS,EAAQ,EAAwB,CACvC,OAAO,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CAI/D,SAAgB,EAAgB,EAAiC,QAAQ,IAAc,CACrF,OAAO,EAAY,KAAkB,IAGvC,SAAS,EAAS,EAAwB,CACxC,GAAI,CACF,OAAOA,EAAAA,QAAG,aAAa,EAAO,MACxB,CACN,OAAOC,EAAAA,QAAK,QAAQ,EAAO,EAgB/B,SAAgB,EAA6B,EAAgB,EAA2C,CACtG,GAAM,CAAE,eAAA,EAAA,EAAA,WAAyB,EAAK,CACtC,GAAI,CAAC,GAAc,EAAW,SAAW,EAAG,MAAO,GACnD,IAAM,EAAW,IAAI,IAAI,OAAO,OAAO,EAAS,CAAC,IAAI,EAAS,CAAC,CAC/D,OAAO,EAAW,KAAM,GAAU,EAAS,IAAI,EAAS,EAAM,CAAC,CAAC,CAUlE,SAAgB,EAAe,EAAiC,CAC9D,GAAID,EAAAA,QAAG,WAAWE,EAAAA,cAAc,EAAI,CAAC,CAAE,OAAO,EAC9C,GAAI,CACF,IAAM,EAAOC,EAAAA,mBAAmB,EAAI,CACpC,OAAOH,EAAAA,QAAG,WAAWE,EAAAA,cAAc,EAAK,CAAC,CAAG,EAAO,IAAA,QAC7C,CACN,QAaJ,eAAsB,EACpB,EACA,EACA,EAAiC,QAAQ,IAClB,CACvB,IAAM,EAAY,MAAM,EAAoB,EAAS,CAC/C,EAAwB,CAC5B,GAAGE,EAAAA,iBAAiB,CAAE,GAAG,EAAM,IAAK,GAAG,EAAa,CAAC,CAErD,GAAG,EAAM,UACT,mBAAoB,EACrB,EACD,EAAA,EAAA,sBAAqB,EAAS,CAAE,YAAW,cAAa,CAAC,CAIzD,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,EAAQ,mBAAmB,CAC/D,EAAY,KAAS,IAAA,KAAW,EAAY,GAAO,GAEzD,OAAO,EAUT,SAAgB,EAAiB,EAA8B,CAC7D,GAAM,CAAE,iBAAA,EAAA,EAAA,WAA2B,EAAK,CAClC,EAAQ,GAAqC,CACjD,IAAM,EAAQ,EAAa,IAAI,EAAK,CACpC,OAAO,OAAO,GAAU,UAAY,EAAM,MAAM,CAAG,EAAM,MAAM,CAAG,IAAA,IAE9D,EAAU,EAAK,EAAW,QAAQ,CACxC,MAAO,CACL,UAAW,EAAK,EAAW,UAAU,CACrC,QAAS,GACL,MAAM,IAAiB,CACxB,IAAK,GAAS,EAAK,MAAM,CAAC,CAC1B,OAAO,QAAQ,CAClB,QAAS,EAAK,EAAW,QAAQ,CACjC,KAAM,EAAa,IAAI,EAAW,KAAK,GAAK,IAAA,GAC5C,aAAc,EAAa,IAAI,EAAmB,GAAK,IAAA,GACxD,CAIH,SAAgB,EAAkB,EAA8C,CAC9E,EAAG,aAAa,EAAW,UAAW,CACpC,KAAM,SACN,YAAa,yCACd,CAAC,CACF,EAAG,aAAa,EAAW,QAAS,CAAE,KAAM,SAAU,YAAa,kCAAmC,CAAC,CACvG,EAAG,aAAa,EAAW,QAAS,CAAE,KAAM,SAAU,YAAa,2CAA4C,CAAC,CAChH,EAAG,aAAa,EAAW,KAAM,CAAE,KAAM,UAAW,YAAa,qCAAsC,CAAC,CAS1G,eAAsB,EACpB,EACA,EACA,EACA,EACe,CAKf,GADI,EAAM,cAAc,EAAS,KAAK,KAAK,EAAmB,qBAAqB,EAAW,YAAY,CACtG,EAAM,UACR,GAAI,CACF,EAAA,eAAe,EAAkB,EAAM,UAAWC,EAAAA,iBAAiB,CAAC,OAC7D,EAAO,CACd,EAAS,KAAK,KAAK,EAAW,UAAU,GAAG,EAAM,UAAU,IAAI,EAAQ,EAAM,GAAG,CAGpF,GAAI,EAAM,SAAW,EAAM,QAAQ,OAAS,EAC1C,GAAI,CACF,MAAMC,EAAAA,aAAa,EAAM,QAASD,EAAAA,iBAAiB,CAAC,OAC7C,EAAO,CACd,EAAS,KAAK,aAAa,EAAM,QAAQ,KAAK,IAAiB,CAAC,IAAI,EAAQ,EAAM,GAAG,CAGzF,GAAI,EAAM,QACR,GAAI,CACF,MAAME,EAAAA,cAAAA,EAAAA,EAAAA,gBAA4B,EAAU,EAAM,QAAQ,CAAEF,EAAAA,iBAAiB,CAAC,OACvE,EAAO,CACd,EAAS,KAAK,aAAa,EAAM,QAAQ,IAAI,EAAQ,EAAM,GAAG,EAYpE,eAAsB,EACpB,EACA,EAAiC,QAAQ,IACxB,CACjB,IAAM,EAAYG,EAAAA,aAAa,EAAS,CAMxC,OALA,MAAMR,EAAAA,QAAG,SAAS,MAAM,EAAW,CAAE,KAAM,IAAwB,UAAW,GAAM,CAAC,CAIrF,EAAA,mBAAmB,CAAE,mBAAoB,EAAW,CAAE,EAAY,CAC3D,EAIT,eAAsB,EAAoB,EAAiC,CACzE,MAAMA,EAAAA,QAAG,SAAS,GAAGQ,EAAAA,aAAa,EAAS,CAAE,CAAE,UAAW,GAAM,MAAO,GAAM,CAAC,CAUhF,eAAsB,EACpB,EACA,EAAwBH,EAAAA,iBAAiB,CACzC,EAAiC,QAAQ,IACtB,CACnB,IAAM,EAAW,EAAQ,MAAQ,EAAM,KACjC,EAAYI,EAAAA,mBAAmB,EAAM,SAAU,EAAM,SAAS,CAI9D,EAAW,EAAQ,IAAM,EAAU,qBAAqBC,EAAAA,iBAAiB,IAAI,CAAG,IAAA,GAChF,EAAU,CACd,OAAQ,EAAQ,OAGhB,SAAU,GACV,OAAQ,EAAM,UAAU,OACxB,WACA,aAAc,EAAU,SAASC,EAAAA,cAAc,CAC/C,kBAAmBC,EAAAA,kCACjB,EAAU,qBAAqBF,EAAAA,iBAAiB,SAAS,CACzD,EAAU,qBAAqBA,EAAAA,iBAAiB,mBAAmB,CACnE,EACD,CACD,OAAQ,CAAC,GAAG,EAAQ,OAAO,CAC3B,kBAAA,EAAA,EAAA,sBAAuC,EAAS,CAChD,YACD,CAID,EAAA,mBACE,CAAE,gBAAiBG,EAAAA,wBAAwB,CAAE,GAAG,EAAS,OAAQ,GAAO,KAAM,GAAM,CAAC,CAAE,CACvF,EACD,CACG,EAAQ,SACV,EAAY,6BAA2B,EAAQ,iBAAiB,KAAKZ,EAAAA,QAAK,UAAU,CACpF,EAAY,6BAA2B,EAAQ,iBAAiB,KAAKA,EAAAA,QAAK,UAAU,EAEtF,IAAM,EAAU,CAAC,GAAGa,EAAAA,mBAAmB,CAAE,GAAG,EAAS,KAAM,EAAY,KAAc,IAAc,CAAC,CAAC,CAAC,SAAS,CACzG,EAAS,EAAM,UAAUC,EAAAA,gBAAgB,EAAQ,EACvD,OAAO,EAAS,CAAC,EAAO,CAAG,EAU7B,eAAsB,EACpB,EACA,EACA,EACmB,CACnB,IAAM,EAAmB,EAAE,CAC3B,IAAK,IAAM,KAAS,EAClB,GAAI,CACF,IAAM,EAAU,MAAM,QAAA,EAAA,EAAA,eAAqB,EAAM,CAAC,MAClD,GAAI,OAAO,EAAO,SAAY,WAAY,CACxC,EAAS,KAAK,GAAG,EAAM,wCAAwC,CAC/D,SAEF,MAAO,EAAO,QAAwD,EAAG,CACzE,EAAO,KAAK,EAAM,OACX,EAAO,CACd,EAAS,KAAK,GAAG,EAAM,IAAI,EAAQ,EAAM,GAAG,CAGhD,OAAO,EAeT,eAAsB,EAAmB,EAAkB,EAA0B,EAAE,CAA2B,CAChH,IAAM,EAAc,EAAQ,aAAe,QAAQ,IAC7C,EAAqB,EAAE,CACvB,EAAW,EAAe,EAAQ,KAAO,QAAQ,KAAK,CAAC,CAC7D,GAAI,CAAC,EAAU,MAAO,CAAE,SAAU,CAAC,EAAW,CAAE,MAAO,GAAO,OAAQ,EAAE,CAAE,CAE1E,IAAI,EACJ,GAAI,CACF,EAAQC,EAAAA,cAAc,EAAS,OACxB,EAAO,CACd,MAAO,CAAE,SAAU,CAAC,EAAQ,EAAM,CAAC,CAAE,MAAO,GAAO,OAAQ,EAAE,CAAE,CAEjE,GAAI,CAAC,EAAO,MAAO,CAAE,SAAU,CAAC,EAAW,CAAE,MAAO,GAAO,OAAQ,EAAE,CAAE,CAEvE,IAAM,EAAO,EAAQ,MAAQ,QAAQ,KAAK,MAAM,EAAE,CAGlD,GACE,EAAA,6BAAyC,KACzC,EAA6B,EAAM,CAAE,GAAG,EAAM,SAAU,GAAG,EAAM,QAAS,CAAC,CAE3E,MAAO,CAAE,SAAU,EAAE,CAAE,MAAO,GAAO,OAAQ,EAAE,CAAE,CAQnD,GALA,EAAA,gBAAgB,CAAE,OAAQ,EAAM,UAAU,OAAyB,OAAQ,EAAE,CAAE,CAAE,EAAY,CAKzF,CAAC,EAAgB,EAAY,CAAE,CACjC,EAAY,GAAgB,IAC5B,MAAM,EAAmB,EAAO,EAAU,EAAY,CACtD,IAAM,EAAQ,EAAiB,EAAK,CAChC,EAAM,OAAM,EAAY,GAAY,KACxC,MAAM,EAAkB,GAAA,EAAA,EAAA,sBAA4B,EAAS,CAAE,EAAU,EAAS,CAGpF,IAAI,EACJ,GAAI,CAIF,EAAU,MAAM,EAAiB,GAAA,EAAA,EAAA,kBAAwB,EAAY,CAAC,MAAO,EAAY,OAClF,EAAO,CACd,MAAO,CAAE,SAAU,CAAC,GAAG,EAAU,EAAQ,EAAM,CAAC,CAAE,MAAO,GAAO,OAAQ,EAAE,CAAE,CAG9E,IAAM,EAAS,MAAM,EAAuB,EAAI,EAAS,EAAS,CAClE,MAAO,CAAE,WAAU,MAAOC,EAAAA,kBAAkB,EAAU,EAAM,UAAU,GAAK,EAAM,WAAY,SAAQ"}
1
+ {"version":3,"file":"composer.cjs","names":["fs","path","syncStatePath","findRepositoryRoot","readHarnessState","getHarnessState","applyDomains","applyProfile","runDirectory","createMapResolvers","CONTEXT_PACKAGES","PERSONA_ENTRY","selectWorkflowExtensionForProcess","assembleChildExtensions","assembleExtensions","extensionSetKey","readSyncState","computeInputsHash"],"sources":["../../src/services/composer.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { loadMajorModesConfig, type MajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { resolveProfile } from '@agimon-ai/doompi-config/profiles';\nimport { withExtensionSource } from '@agimon-ai/doompi-ui/extensionName';\nimport { type ExtensionAPI, parseArgs } from '@earendil-works/pi-coding-agent';\nimport {\n createHarnessSession,\n getHarnessState,\n type HarnessState,\n loadHarnessState,\n readHarnessState,\n updateHarnessState,\n} from '../config/harnessState.ts';\nimport type { HarnessPreset } from '../interfaces/index.ts';\nimport { findRepositoryRoot } from '../utils/index.ts';\nimport { assembleChildExtensions, assembleExtensions, CONTEXT_PACKAGES, PERSONA_ENTRY } from './extensionAssembler.ts';\nimport { extensionSetKey } from './extensionCompiler.ts';\nimport { configurePreset } from './harnessContext.ts';\nimport { applyDomains, applyMajorMode, applyProfile } from './matrixSwitcher.ts';\nimport { selectWorkflowExtensionForProcess } from './resourceCollector.ts';\nimport {\n computeInputsHash,\n createMapResolvers,\n readSyncState,\n runDirectory,\n type SyncState,\n syncStatePath,\n} from './syncState.ts';\n\n/**\n * The runtime half of `doom-pi sync`.\n *\n * Pi's own config carries a single entry, the doom-pi extension, and this is\n * what that entry runs: hydrate the harness environment, apply the startup\n * flags, then load the extension set the matrix selects. It runs again on every\n * `/reload`, which is what lets `/major-mode` swap the set without a restart,\n * since Pi rebuilds its extension runner from whatever a reload produced.\n */\n\n/** Marks a process whose extensions this module composed. */\nexport const COMPOSED_ENV = 'DOOMPI_COMPOSED';\n/** Set by the launcher when it passes one precomposed aggregate to Pi. */\nexport const EXTERNAL_EXTENSIONS_ENV = 'DOOMPI_EXTENSIONS_PROVIDED';\n/** Survives reload, where argv is re-read but the startup flags must not be. */\nexport const MUTE_ENV = 'DOOMPI_MUTE';\nconst ENABLED_FLAG = '1';\nconst SUBAGENT_AGENT_DIRS_ENV = 'PI_SUBAGENT_EXTRA_AGENT_DIRS';\nconst SUBAGENT_SKILL_DIRS_ENV = 'PI_SUBAGENT_EXTRA_SKILL_DIRS';\nconst DOMAIN_SEPARATOR = ',';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst NOT_SYNCED = 'doompi is configured for Pi but this repository was never synced. Run doompi sync.';\n\nexport const DOOM_FLAGS = {\n majorMode: 'major-mode',\n domains: 'domains',\n profile: 'profile',\n mute: 'mute',\n} as const;\n\n/** Removed flag, reported rather than ignored when a synced session passes it. */\nexport const REMOVED_LAYER_FLAG = 'layer';\n\nexport interface StartupFlags {\n majorMode?: string;\n domains?: string[];\n profile?: string;\n mute: boolean;\n /** True when the caller passed the removed `--layer` flag. */\n removedLayer: boolean;\n}\n\nexport interface ComposeOutcome {\n /** Everything that went wrong, reported once the session can show messages. */\n problems: string[];\n /** Config changed since the last sync, which is a prompt to re-sync. */\n stale: boolean;\n loaded: string[];\n}\n\nfunction message(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** True when this process already applied the command line, so a reload does not. */\nexport function alreadyComposed(environment: NodeJS.ProcessEnv = process.env): boolean {\n return environment[COMPOSED_ENV] === ENABLED_FLAG;\n}\n\nfunction realPath(target: string): string {\n try {\n return fs.realpathSync(target);\n } catch {\n return path.resolve(target);\n }\n}\n\n/**\n * True when this process was handed the doom extension set on the command line.\n *\n * The launcher and detached Doom Team children both pass the composed list as\n * `--extension` arguments, and Pi merges those with whatever project settings\n * declare. Composing again here would load every extension twice, which Pi\n * reports as each tool and flag conflicting with itself.\n *\n * Matching against the synced paths rather than the mere presence of\n * `--extension` keeps an unrelated one-off, `pi -e ./debug.ts`, from silently\n * costing the user their whole setup.\n */\nexport function extensionsProvidedExternally(argv: string[], resolved: Record<string, string>): boolean {\n const { extensions } = parseArgs(argv);\n if (!extensions || extensions.length === 0) return false;\n const composed = new Set(Object.values(resolved).map(realPath));\n return extensions.some((entry) => composed.has(realPath(entry)));\n}\n\n/**\n * Finds the repository whose synced state applies to this session.\n *\n * Pi only reads `.pi/settings.json` from its own working directory, so that is\n * the first place to look; walking up covers a session started in a\n * subdirectory with the extension configured globally.\n */\nexport function findSyncedRoot(cwd: string): string | undefined {\n if (fs.existsSync(syncStatePath(cwd))) return cwd;\n try {\n const root = findRepositoryRoot(cwd);\n return fs.existsSync(syncStatePath(root)) ? root : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Opens this process's session from what sync pinned.\n *\n * The synced record is decoded underneath the live environment, so an inherited\n * value still wins: a launcher run, a nested session, or a CI override is more\n * specific than anything sync wrote. The result becomes the session's state\n * file, which is the authority from here on; the environment it publishes is\n * for the bash hooks and the packages that can only read one.\n */\nexport async function startSyncedSession(\n state: SyncState,\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<HarnessState> {\n const directory = await prepareRunDirectory(repoRoot);\n const harness: HarnessState = {\n ...readHarnessState({ ...state.env, ...environment }),\n // Recorded separately because no variable carries them.\n ...state.fileState,\n temporaryDirectory: directory,\n };\n createHarnessSession(harness, { directory, environment });\n // The profile's own defaults, which the launcher applies at spawn. They are\n // recorded as a map rather than as variables so /profile can tell which ones\n // it owns when it swaps them out later.\n for (const [key, value] of Object.entries(harness.profileEnvironment)) {\n if (environment[key] === undefined) environment[key] = value;\n }\n return harness;\n}\n\n/**\n * Reads the doom flags off the command line.\n *\n * Pi collects unrecognized flags for extensions but hands their values back\n * only after every extension has loaded, which is too late to decide what to\n * load. Parsing argv with Pi's own parser keeps the value rules identical.\n */\nexport function readStartupFlags(argv: string[]): StartupFlags {\n const { unknownFlags } = parseArgs(argv);\n const text = (name: string): string | undefined => {\n const value = unknownFlags.get(name);\n return typeof value === 'string' && value.trim() ? value.trim() : undefined;\n };\n const domains = text(DOOM_FLAGS.domains);\n return {\n majorMode: text(DOOM_FLAGS.majorMode),\n domains: domains\n ?.split(DOMAIN_SEPARATOR)\n .map((name) => name.trim())\n .filter(Boolean),\n profile: text(DOOM_FLAGS.profile),\n mute: unknownFlags.get(DOOM_FLAGS.mute) !== undefined,\n removedLayer: unknownFlags.get(REMOVED_LAYER_FLAG) !== undefined,\n };\n}\n\n/** Registers the flags so Pi accepts them and lists them in `pi --help`. */\nexport function registerDoomFlags(pi: Pick<ExtensionAPI, 'registerFlag'>): void {\n pi.registerFlag(DOOM_FLAGS.majorMode, {\n type: 'string',\n description: 'Named major mode from .doom/modes.yaml',\n });\n pi.registerFlag(DOOM_FLAGS.domains, { type: 'string', description: 'Comma-separated content domains' });\n pi.registerFlag(DOOM_FLAGS.profile, { type: 'string', description: 'Persona and env from .doom/profiles.yaml' });\n pi.registerFlag(DOOM_FLAGS.mute, { type: 'boolean', description: 'Disable the notification extension' });\n}\n\n/**\n * Applies the startup flags through the same switchers the slash commands use.\n *\n * A bad value is collected rather than thrown: a factory that throws takes the\n * whole doom setup down with it, which is far worse than one unapplied flag.\n */\nexport async function applyStartupFlags(\n flags: StartupFlags,\n majorModesConfig: MajorModesConfig,\n repoRoot: string,\n problems: string[],\n): Promise<void> {\n // Reported rather than ignored: Pi hands unrecognized flags to extensions, so\n // a stale `--layer dev` would otherwise start a session on the wrong mode\n // without saying anything.\n if (flags.removedLayer) problems.push(`--${REMOVED_LAYER_FLAG} was replaced by --${DOOM_FLAGS.majorMode}`);\n if (flags.majorMode) {\n try {\n applyMajorMode(majorModesConfig, flags.majorMode, getHarnessState());\n } catch (error) {\n problems.push(`--${DOOM_FLAGS.majorMode} ${flags.majorMode}: ${message(error)}`);\n }\n }\n if (flags.domains && flags.domains.length > 0) {\n try {\n await applyDomains(flags.domains, getHarnessState());\n } catch (error) {\n problems.push(`--domains ${flags.domains.join(DOMAIN_SEPARATOR)}: ${message(error)}`);\n }\n }\n if (flags.profile) {\n try {\n await applyProfile(resolveProfile(repoRoot, flags.profile), getHarnessState());\n } catch (error) {\n problems.push(`--profile ${flags.profile}: ${message(error)}`);\n }\n }\n}\n\n/**\n * Points harness state at a per-process directory before anything writes there.\n *\n * The synced directory is a baseline that every session reads; live switches\n * materialize into this one instead, so a second session in the same repository\n * cannot overwrite what the first is using.\n */\nexport async function prepareRunDirectory(\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<string> {\n const directory = runDirectory(repoRoot);\n await fs.promises.mkdir(directory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n // Through the store rather than the environment: the state file is the\n // authority, and a switcher reading a stale directory would write a live\n // session's resources into the wrong place.\n updateHarnessState({ temporaryDirectory: directory }, environment);\n return directory;\n}\n\n/** Removes this session's scratch directory. */\nexport async function cleanupRunDirectory(repoRoot: string): Promise<void> {\n await fs.promises.rm(runDirectory(repoRoot), { recursive: true, force: true });\n}\n\n/**\n * Builds the ordered list of extensions to load for the current state.\n *\n * Returns them in Pi load order. `assembleExtensions` produces the order the\n * launcher passes to Pi, where the last element becomes the first `--extension`\n * argument, so reversing here keeps that rule stated in exactly one place.\n */\nexport async function composeLoadOrder(\n state: SyncState,\n harness: HarnessState = getHarnessState(),\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<string[]> {\n const repoRoot = harness.root ?? state.root;\n const resolvers = createMapResolvers(state.resolved, state.compiled);\n // Resolved from the sync map rather than generated: doom-mcp reads the\n // repository and plugin layers itself, and the environment the launcher wrote\n // already tells it which plugins and allowlist this session selected.\n const mcpEntry = harness.mcp ? resolvers.optionalPackageEntry(CONTEXT_PACKAGES.mcp) : undefined;\n const context = {\n agents: harness.agents,\n // Exiting when the agent settles is a launcher decision with no synced\n // equivalent: a session started by hand is meant to stay open.\n autoStop: false,\n preset: state.selection.preset,\n mcpEntry,\n personaEntry: resolvers.ownEntry(PERSONA_ENTRY),\n workflowExtension: selectWorkflowExtensionForProcess(\n resolvers.optionalPackageEntry(CONTEXT_PACKAGES.workflow),\n resolvers.optionalPackageEntry(CONTEXT_PACKAGES.dispatcherWorkflow),\n environment,\n ),\n layers: [...harness.layers],\n majorModesConfig: loadMajorModesConfig(repoRoot),\n resolvers,\n };\n\n // A state field, so it goes through the store: Doom Team reads it from the\n // environment the store publishes when it builds a child's spawn snapshot.\n updateHarnessState(\n { childExtensions: assembleChildExtensions({ ...context, agents: false, mute: true }) },\n environment,\n );\n if (harness.agents) {\n environment[SUBAGENT_AGENT_DIRS_ENV] = harness.agentDirectories.join(path.delimiter);\n environment[SUBAGENT_SKILL_DIRS_ENV] = harness.skillDirectories.join(path.delimiter);\n }\n const entries = [...assembleExtensions({ ...context, mute: environment[MUTE_ENV] === ENABLED_FLAG })].reverse();\n const bundle = state.bundles?.[extensionSetKey(entries)];\n return bundle ? [bundle] : entries;\n}\n\n/**\n * Imports each extension and hands it a source-aware view of the Pi API.\n *\n * Every Pi extension entry is a factory of the same shape, so one extension can\n * activate the rest. The view delegates to the same host API while retaining the\n * entry path for tools registered through a composed loader. Failures are per\n * entry: one unloadable module must not cost the session everything after it.\n */\nexport async function loadComposedExtensions(\n pi: ExtensionAPI,\n entries: string[],\n problems: string[],\n): Promise<string[]> {\n const loaded: string[] = [];\n for (const entry of entries) {\n try {\n const module = (await import(pathToFileURL(entry).href)) as { default?: unknown };\n if (typeof module.default !== 'function') {\n problems.push(`${entry}: does not export an extension factory`);\n continue;\n }\n await (module.default as (api: ExtensionAPI) => void | Promise<void>)(withExtensionSource(pi, entry));\n loaded.push(entry);\n } catch (error) {\n problems.push(`${entry}: ${message(error)}`);\n }\n }\n return loaded;\n}\n\nexport interface ComposeOptions {\n cwd?: string;\n argv?: string[];\n environment?: NodeJS.ProcessEnv;\n}\n\n/**\n * Runs the whole synced startup for one Pi session.\n *\n * Never throws: whatever fails is reported through the outcome so the session\n * still starts and can say what is wrong.\n */\nexport async function composeDoomSession(pi: ExtensionAPI, options: ComposeOptions = {}): Promise<ComposeOutcome> {\n const environment = options.environment ?? process.env;\n const problems: string[] = [];\n const repoRoot = findSyncedRoot(options.cwd ?? process.cwd());\n if (!repoRoot) return { problems: [NOT_SYNCED], stale: false, loaded: [] };\n\n let state: SyncState | undefined;\n try {\n state = readSyncState(repoRoot);\n } catch (error) {\n return { problems: [message(error)], stale: false, loaded: [] };\n }\n if (!state) return { problems: [NOT_SYNCED], stale: false, loaded: [] };\n\n const argv = options.argv ?? process.argv.slice(2);\n // The launcher and Doom Team children compose the set themselves and pass it\n // in, so this entry has nothing left to do but stay out of the way.\n if (\n environment[EXTERNAL_EXTENSIONS_ENV] === ENABLED_FLAG ||\n extensionsProvidedExternally(argv, { ...state.resolved, ...state.bundles })\n ) {\n return { problems: [], stale: false, loaded: [] };\n }\n\n configurePreset({ preset: state.selection.preset as HarnessPreset, piArgs: [] }, environment);\n\n // Only the first load opens a session. A reload finds the pointer this set\n // and reads the file back, which is what carries a live /domains or /profile\n // switch across the reload instead of resetting it to what sync pinned.\n if (!alreadyComposed(environment)) {\n environment[COMPOSED_ENV] = ENABLED_FLAG;\n await startSyncedSession(state, repoRoot, environment);\n const flags = readStartupFlags(argv);\n if (flags.mute) environment[MUTE_ENV] = ENABLED_FLAG;\n await applyStartupFlags(flags, loadMajorModesConfig(repoRoot), repoRoot, problems);\n }\n\n let entries: string[];\n try {\n // Read back through the store rather than from this process: the startup\n // flags may have switched the layer or the domains a moment ago, and the\n // environment being managed is not always this process's own.\n entries = await composeLoadOrder(state, loadHarnessState(environment).state, environment);\n } catch (error) {\n return { problems: [...problems, message(error)], stale: false, loaded: [] };\n }\n\n const loaded = await loadComposedExtensions(pi, entries, problems);\n return { problems, stale: computeInputsHash(repoRoot, state.selection) !== state.inputsHash, loaded };\n}\n"],"mappings":"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"}
@@ -111,11 +111,12 @@ declare function cleanupRunDirectory(repoRoot: string): Promise<void>;
111
111
  */
112
112
  declare function composeLoadOrder(state: SyncState, harness?: HarnessState, environment?: NodeJS.ProcessEnv): Promise<string[]>;
113
113
  /**
114
- * Imports each extension and hands it the same Pi API this module received.
114
+ * Imports each extension and hands it a source-aware view of the Pi API.
115
115
  *
116
116
  * Every Pi extension entry is a factory of the same shape, so one extension can
117
- * activate the rest. Failures are per entry: one unloadable module must not
118
- * cost the session everything after it in the order.
117
+ * activate the rest. The view delegates to the same host API while retaining the
118
+ * entry path for tools registered through a composed loader. Failures are per
119
+ * entry: one unloadable module must not cost the session everything after it.
119
120
  */
120
121
  declare function loadComposedExtensions(pi: ExtensionAPI, entries: string[], problems: string[]): Promise<string[]>;
121
122
  interface ComposeOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"composer.d.cts","names":[],"sources":["../../src/services/composer.ts"],"mappings":";;;;;;;;AAyCA;;;;;AAEA;;;cAFa,YAAA;;cAEA,uBAAA;;cAEA,QAAA;AAAA,cAQA,UAAA;EAAA;;;;;;cAQA,kBAAA;AAAA,UAEI,YAAA;EACf,SAAA;EACA,OAAA;EACA,OAAA;EACA,IAAA;EAN6B;EAQ7B,YAAA;AAAA;AAAA,UAGe,cAAA;EATA;EAWf,QAAA;;EAEA,KAAA;EACA,MAAA;AAAA;;iBAQc,eAAA,CAAgB,WAAA,GAAa,MAAA,CAAO,UAAA;;;;AAbpD;;;;;;;;;iBAqCgB,4BAAA,CAA6B,IAAA,YAAgB,QAAA,EAAU,MAAA;;;;;;;;iBAcvD,cAAA,CAAe,GAAA;AAd/B;;;;;;;;;AAAA,iBAiCsB,kBAAA,CACpB,KAAA,EAAO,SAAA,EACP,QAAA,UACA,WAAA,GAAa,MAAA,CAAO,UAAA,GACnB,OAAA,CAAQ,YAAA;;;;;AAJX;;;iBA6BgB,gBAAA,CAAiB,IAAA,aAAiB,YAAA;;iBAoBlC,iBAAA,CAAkB,EAAA,EAAI,IAAA,CAAK,YAAA;;;;;;;iBAgBrB,iBAAA,CACpB,KAAA,EAAO,YAAA,EACP,gBAAA,EAAkB,gBAAA,EAClB,QAAA,UACA,QAAA,aACC,OAAA;;;;;;;AAzCH;iBA4EsB,mBAAA,CACpB,QAAA,UACA,WAAA,GAAa,MAAA,CAAO,UAAA,GACnB,OAAA;;iBAWmB,mBAAA,CAAoB,QAAA,WAAmB,OAAA;;AAtE7D;;;;;;iBAiFsB,gBAAA,CACpB,KAAA,EAAO,SAAA,EACP,OAAA,GAAS,YAAA,EACT,WAAA,GAAa,MAAA,CAAO,UAAA,GACnB,OAAA;;;AArEH;;;;;iBAoHsB,sBAAA,CACpB,EAAA,EAAI,YAAA,EACJ,OAAA,YACA,QAAA,aACC,OAAA;AAAA,UAkBc,cAAA;EACf,GAAA;EACA,IAAA;EACA,WAAA,GAAc,MAAA,CAAO,UAAA;AAAA;;;;;;;iBASD,kBAAA,CAAmB,EAAA,EAAI,YAAA,EAAc,OAAA,GAAS,cAAA,GAAsB,OAAA,CAAQ,cAAA"}
1
+ {"version":3,"file":"composer.d.cts","names":[],"sources":["../../src/services/composer.ts"],"mappings":";;;;;;;;AA0CA;;;;;AAEA;;;cAFa,YAAA;;cAEA,uBAAA;;cAEA,QAAA;AAAA,cAQA,UAAA;EAAA;;;;;;cAQA,kBAAA;AAAA,UAEI,YAAA;EACf,SAAA;EACA,OAAA;EACA,OAAA;EACA,IAAA;EAN6B;EAQ7B,YAAA;AAAA;AAAA,UAGe,cAAA;EATA;EAWf,QAAA;;EAEA,KAAA;EACA,MAAA;AAAA;;iBAQc,eAAA,CAAgB,WAAA,GAAa,MAAA,CAAO,UAAA;;;;AAbpD;;;;;;;;;iBAqCgB,4BAAA,CAA6B,IAAA,YAAgB,QAAA,EAAU,MAAA;;;;;;;;iBAcvD,cAAA,CAAe,GAAA;AAd/B;;;;;;;;;AAAA,iBAiCsB,kBAAA,CACpB,KAAA,EAAO,SAAA,EACP,QAAA,UACA,WAAA,GAAa,MAAA,CAAO,UAAA,GACnB,OAAA,CAAQ,YAAA;;;;;AAJX;;;iBA6BgB,gBAAA,CAAiB,IAAA,aAAiB,YAAA;;iBAoBlC,iBAAA,CAAkB,EAAA,EAAI,IAAA,CAAK,YAAA;;;;;;;iBAgBrB,iBAAA,CACpB,KAAA,EAAO,YAAA,EACP,gBAAA,EAAkB,gBAAA,EAClB,QAAA,UACA,QAAA,aACC,OAAA;;;;;;;AAzCH;iBA4EsB,mBAAA,CACpB,QAAA,UACA,WAAA,GAAa,MAAA,CAAO,UAAA,GACnB,OAAA;;iBAWmB,mBAAA,CAAoB,QAAA,WAAmB,OAAA;;AAtE7D;;;;;;iBAiFsB,gBAAA,CACpB,KAAA,EAAO,SAAA,EACP,OAAA,GAAS,YAAA,EACT,WAAA,GAAa,MAAA,CAAO,UAAA,GACnB,OAAA;;;AArEH;;;;;;iBAqHsB,sBAAA,CACpB,EAAA,EAAI,YAAA,EACJ,OAAA,YACA,QAAA,aACC,OAAA;AAAA,UAkBc,cAAA;EACf,GAAA;EACA,IAAA;EACA,WAAA,GAAc,MAAA,CAAO,UAAA;AAAA;;;;;;AAtGvB;iBA+GsB,kBAAA,CAAmB,EAAA,EAAI,YAAA,EAAc,OAAA,GAAS,cAAA,GAAsB,OAAA,CAAQ,cAAA"}
@@ -111,11 +111,12 @@ declare function cleanupRunDirectory(repoRoot: string): Promise<void>;
111
111
  */
112
112
  declare function composeLoadOrder(state: SyncState, harness?: HarnessState, environment?: NodeJS.ProcessEnv): Promise<string[]>;
113
113
  /**
114
- * Imports each extension and hands it the same Pi API this module received.
114
+ * Imports each extension and hands it a source-aware view of the Pi API.
115
115
  *
116
116
  * Every Pi extension entry is a factory of the same shape, so one extension can
117
- * activate the rest. Failures are per entry: one unloadable module must not
118
- * cost the session everything after it in the order.
117
+ * activate the rest. The view delegates to the same host API while retaining the
118
+ * entry path for tools registered through a composed loader. Failures are per
119
+ * entry: one unloadable module must not cost the session everything after it.
119
120
  */
120
121
  declare function loadComposedExtensions(pi: ExtensionAPI, entries: string[], problems: string[]): Promise<string[]>;
121
122
  interface ComposeOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"composer.d.mts","names":[],"sources":["../../src/services/composer.ts"],"mappings":";;;;;;;;AAyCA;;;;;AAEA;;;cAFa,YAAA;;cAEA,uBAAA;;cAEA,QAAA;AAAA,cAQA,UAAA;EAAA;;;;;;cAQA,kBAAA;AAAA,UAEI,YAAA;EACf,SAAA;EACA,OAAA;EACA,OAAA;EACA,IAAA;EAN6B;EAQ7B,YAAA;AAAA;AAAA,UAGe,cAAA;EATA;EAWf,QAAA;;EAEA,KAAA;EACA,MAAA;AAAA;;iBAQc,eAAA,CAAgB,WAAA,GAAa,MAAA,CAAO,UAAA;;;;AAbpD;;;;;;;;;iBAqCgB,4BAAA,CAA6B,IAAA,YAAgB,QAAA,EAAU,MAAA;;;;;;;;iBAcvD,cAAA,CAAe,GAAA;AAd/B;;;;;;;;;AAAA,iBAiCsB,kBAAA,CACpB,KAAA,EAAO,SAAA,EACP,QAAA,UACA,WAAA,GAAa,MAAA,CAAO,UAAA,GACnB,OAAA,CAAQ,YAAA;;;;;AAJX;;;iBA6BgB,gBAAA,CAAiB,IAAA,aAAiB,YAAA;;iBAoBlC,iBAAA,CAAkB,EAAA,EAAI,IAAA,CAAK,YAAA;;;;;;;iBAgBrB,iBAAA,CACpB,KAAA,EAAO,YAAA,EACP,gBAAA,EAAkB,gBAAA,EAClB,QAAA,UACA,QAAA,aACC,OAAA;;;;;;;AAzCH;iBA4EsB,mBAAA,CACpB,QAAA,UACA,WAAA,GAAa,MAAA,CAAO,UAAA,GACnB,OAAA;;iBAWmB,mBAAA,CAAoB,QAAA,WAAmB,OAAA;;AAtE7D;;;;;;iBAiFsB,gBAAA,CACpB,KAAA,EAAO,SAAA,EACP,OAAA,GAAS,YAAA,EACT,WAAA,GAAa,MAAA,CAAO,UAAA,GACnB,OAAA;;;AArEH;;;;;iBAoHsB,sBAAA,CACpB,EAAA,EAAI,YAAA,EACJ,OAAA,YACA,QAAA,aACC,OAAA;AAAA,UAkBc,cAAA;EACf,GAAA;EACA,IAAA;EACA,WAAA,GAAc,MAAA,CAAO,UAAA;AAAA;;;;;;;iBASD,kBAAA,CAAmB,EAAA,EAAI,YAAA,EAAc,OAAA,GAAS,cAAA,GAAsB,OAAA,CAAQ,cAAA"}
1
+ {"version":3,"file":"composer.d.mts","names":[],"sources":["../../src/services/composer.ts"],"mappings":";;;;;;;;AA0CA;;;;;AAEA;;;cAFa,YAAA;;cAEA,uBAAA;;cAEA,QAAA;AAAA,cAQA,UAAA;EAAA;;;;;;cAQA,kBAAA;AAAA,UAEI,YAAA;EACf,SAAA;EACA,OAAA;EACA,OAAA;EACA,IAAA;EAN6B;EAQ7B,YAAA;AAAA;AAAA,UAGe,cAAA;EATA;EAWf,QAAA;;EAEA,KAAA;EACA,MAAA;AAAA;;iBAQc,eAAA,CAAgB,WAAA,GAAa,MAAA,CAAO,UAAA;;;;AAbpD;;;;;;;;;iBAqCgB,4BAAA,CAA6B,IAAA,YAAgB,QAAA,EAAU,MAAA;;;;;;;;iBAcvD,cAAA,CAAe,GAAA;AAd/B;;;;;;;;;AAAA,iBAiCsB,kBAAA,CACpB,KAAA,EAAO,SAAA,EACP,QAAA,UACA,WAAA,GAAa,MAAA,CAAO,UAAA,GACnB,OAAA,CAAQ,YAAA;;;;;AAJX;;;iBA6BgB,gBAAA,CAAiB,IAAA,aAAiB,YAAA;;iBAoBlC,iBAAA,CAAkB,EAAA,EAAI,IAAA,CAAK,YAAA;;;;;;;iBAgBrB,iBAAA,CACpB,KAAA,EAAO,YAAA,EACP,gBAAA,EAAkB,gBAAA,EAClB,QAAA,UACA,QAAA,aACC,OAAA;;;;;;;AAzCH;iBA4EsB,mBAAA,CACpB,QAAA,UACA,WAAA,GAAa,MAAA,CAAO,UAAA,GACnB,OAAA;;iBAWmB,mBAAA,CAAoB,QAAA,WAAmB,OAAA;;AAtE7D;;;;;;iBAiFsB,gBAAA,CACpB,KAAA,EAAO,SAAA,EACP,OAAA,GAAS,YAAA,EACT,WAAA,GAAa,MAAA,CAAO,UAAA,GACnB,OAAA;;;AArEH;;;;;;iBAqHsB,sBAAA,CACpB,EAAA,EAAI,YAAA,EACJ,OAAA,YACA,QAAA,aACC,OAAA;AAAA,UAkBc,cAAA;EACf,GAAA;EACA,IAAA;EACA,WAAA,GAAc,MAAA,CAAO,UAAA;AAAA;;;;;;AAtGvB;iBA+GsB,kBAAA,CAAmB,EAAA,EAAI,YAAA,EAAc,OAAA,GAAS,cAAA,GAAsB,OAAA,CAAQ,cAAA"}
@@ -1,2 +1,2 @@
1
- import{createHarnessSession as e,getHarnessState as t,loadHarnessState as n,readHarnessState as r,updateHarnessState as i}from"../config/harnessState.mjs";import{findRepositoryRoot as a}from"../utils/repository.mjs";import"../utils/index.mjs";import{CONTEXT_PACKAGES as o,PERSONA_ENTRY as s,assembleChildExtensions as c,assembleExtensions as l}from"./extensionAssembler.mjs";import{selectWorkflowExtensionForProcess as u}from"./resourceCollector.mjs";import{configurePreset as d}from"./harnessContext.mjs";import{extensionSetKey as f}from"./extensionCompiler.mjs";import{computeInputsHash as p,createMapResolvers as m,readSyncState as h,runDirectory as g,syncStatePath as _}from"./syncState.mjs";import{applyDomains as v,applyMajorMode as y,applyProfile as b}from"./matrixSwitcher.mjs";import x from"node:path";import{resolveProfile as S}from"@agimon-ai/doompi-config/profiles";import{loadMajorModesConfig as C}from"@agimon-ai/doompi-config/majorModes";import w from"node:fs";import{pathToFileURL as T}from"node:url";import{parseArgs as E}from"@earendil-works/pi-coding-agent";const D=`DOOMPI_COMPOSED`,O=`DOOMPI_EXTENSIONS_PROVIDED`,k=`DOOMPI_MUTE`,A=`doompi is configured for Pi but this repository was never synced. Run doompi sync.`,j={majorMode:`major-mode`,domains:`domains`,profile:`profile`,mute:`mute`},M=`layer`;function N(e){return e instanceof Error?e.message:String(e)}function P(e=process.env){return e[D]===`1`}function F(e){try{return w.realpathSync(e)}catch{return x.resolve(e)}}function I(e,t){let{extensions:n}=E(e);if(!n||n.length===0)return!1;let r=new Set(Object.values(t).map(F));return n.some(e=>r.has(F(e)))}function L(e){if(w.existsSync(_(e)))return e;try{let t=a(e);return w.existsSync(_(t))?t:void 0}catch{return}}async function R(t,n,i=process.env){let a=await H(n),o={...r({...t.env,...i}),...t.fileState,temporaryDirectory:a};e(o,{directory:a,environment:i});for(let[e,t]of Object.entries(o.profileEnvironment))i[e]===void 0&&(i[e]=t);return o}function z(e){let{unknownFlags:t}=E(e),n=e=>{let n=t.get(e);return typeof n==`string`&&n.trim()?n.trim():void 0},r=n(j.domains);return{majorMode:n(j.majorMode),domains:r?.split(`,`).map(e=>e.trim()).filter(Boolean),profile:n(j.profile),mute:t.get(j.mute)!==void 0,removedLayer:t.get(M)!==void 0}}function B(e){e.registerFlag(j.majorMode,{type:`string`,description:`Named major mode from .doom/modes.yaml`}),e.registerFlag(j.domains,{type:`string`,description:`Comma-separated content domains`}),e.registerFlag(j.profile,{type:`string`,description:`Persona and env from .doom/profiles.yaml`}),e.registerFlag(j.mute,{type:`boolean`,description:`Disable the notification extension`})}async function V(e,n,r,i){if(e.removedLayer&&i.push(`--${M} was replaced by --${j.majorMode}`),e.majorMode)try{y(n,e.majorMode,t())}catch(t){i.push(`--${j.majorMode} ${e.majorMode}: ${N(t)}`)}if(e.domains&&e.domains.length>0)try{await v(e.domains,t())}catch(t){i.push(`--domains ${e.domains.join(`,`)}: ${N(t)}`)}if(e.profile)try{await b(S(r,e.profile),t())}catch(t){i.push(`--profile ${e.profile}: ${N(t)}`)}}async function H(e,t=process.env){let n=g(e);return await w.promises.mkdir(n,{mode:448,recursive:!0}),i({temporaryDirectory:n},t),n}async function U(e){await w.promises.rm(g(e),{recursive:!0,force:!0})}async function W(e,n=t(),r=process.env){let a=n.root??e.root,d=m(e.resolved,e.compiled),p=n.mcp?d.optionalPackageEntry(o.mcp):void 0,h={agents:n.agents,autoStop:!1,preset:e.selection.preset,mcpEntry:p,personaEntry:d.ownEntry(s),workflowExtension:u(d.optionalPackageEntry(o.workflow),d.optionalPackageEntry(o.dispatcherWorkflow),r),layers:[...n.layers],majorModesConfig:C(a),resolvers:d};i({childExtensions:c({...h,agents:!1,mute:!0})},r),n.agents&&(r.PI_SUBAGENT_EXTRA_AGENT_DIRS=n.agentDirectories.join(x.delimiter),r.PI_SUBAGENT_EXTRA_SKILL_DIRS=n.skillDirectories.join(x.delimiter));let g=[...l({...h,mute:r[k]===`1`})].reverse(),_=e.bundles?.[f(g)];return _?[_]:g}async function G(e,t,n){let r=[];for(let i of t)try{let t=await import(T(i).href);if(typeof t.default!=`function`){n.push(`${i}: does not export an extension factory`);continue}await t.default(e),r.push(i)}catch(e){n.push(`${i}: ${N(e)}`)}return r}async function K(e,t={}){let r=t.environment??process.env,i=[],a=L(t.cwd??process.cwd());if(!a)return{problems:[A],stale:!1,loaded:[]};let o;try{o=h(a)}catch(e){return{problems:[N(e)],stale:!1,loaded:[]}}if(!o)return{problems:[A],stale:!1,loaded:[]};let s=t.argv??process.argv.slice(2);if(r.DOOMPI_EXTENSIONS_PROVIDED===`1`||I(s,{...o.resolved,...o.bundles}))return{problems:[],stale:!1,loaded:[]};if(d({preset:o.selection.preset,piArgs:[]},r),!P(r)){r[D]=`1`,await R(o,a,r);let e=z(s);e.mute&&(r[k]=`1`),await V(e,C(a),a,i)}let c;try{c=await W(o,n(r).state,r)}catch(e){return{problems:[...i,N(e)],stale:!1,loaded:[]}}let l=await G(e,c,i);return{problems:i,stale:p(a,o.selection)!==o.inputsHash,loaded:l}}export{D as COMPOSED_ENV,j as DOOM_FLAGS,O as EXTERNAL_EXTENSIONS_ENV,k as MUTE_ENV,M as REMOVED_LAYER_FLAG,P as alreadyComposed,V as applyStartupFlags,U as cleanupRunDirectory,K as composeDoomSession,W as composeLoadOrder,I as extensionsProvidedExternally,L as findSyncedRoot,G as loadComposedExtensions,H as prepareRunDirectory,z as readStartupFlags,B as registerDoomFlags,R as startSyncedSession};
1
+ import{createHarnessSession as e,getHarnessState as t,loadHarnessState as n,readHarnessState as r,updateHarnessState as i}from"../config/harnessState.mjs";import{findRepositoryRoot as a}from"../utils/repository.mjs";import"../utils/index.mjs";import{CONTEXT_PACKAGES as o,PERSONA_ENTRY as s,assembleChildExtensions as c,assembleExtensions as l}from"./extensionAssembler.mjs";import{selectWorkflowExtensionForProcess as u}from"./resourceCollector.mjs";import{configurePreset as d}from"./harnessContext.mjs";import{extensionSetKey as f}from"./extensionCompiler.mjs";import{computeInputsHash as p,createMapResolvers as m,readSyncState as h,runDirectory as g,syncStatePath as _}from"./syncState.mjs";import{applyDomains as v,applyMajorMode as y,applyProfile as b}from"./matrixSwitcher.mjs";import x from"node:path";import{resolveProfile as S}from"@agimon-ai/doompi-config/profiles";import{loadMajorModesConfig as C}from"@agimon-ai/doompi-config/majorModes";import w from"node:fs";import{pathToFileURL as T}from"node:url";import{withExtensionSource as E}from"@agimon-ai/doompi-ui/extensionName";import{parseArgs as D}from"@earendil-works/pi-coding-agent";const O=`DOOMPI_COMPOSED`,k=`DOOMPI_EXTENSIONS_PROVIDED`,A=`DOOMPI_MUTE`,j=`doompi is configured for Pi but this repository was never synced. Run doompi sync.`,M={majorMode:`major-mode`,domains:`domains`,profile:`profile`,mute:`mute`},N=`layer`;function P(e){return e instanceof Error?e.message:String(e)}function F(e=process.env){return e[O]===`1`}function I(e){try{return w.realpathSync(e)}catch{return x.resolve(e)}}function L(e,t){let{extensions:n}=D(e);if(!n||n.length===0)return!1;let r=new Set(Object.values(t).map(I));return n.some(e=>r.has(I(e)))}function R(e){if(w.existsSync(_(e)))return e;try{let t=a(e);return w.existsSync(_(t))?t:void 0}catch{return}}async function z(t,n,i=process.env){let a=await U(n),o={...r({...t.env,...i}),...t.fileState,temporaryDirectory:a};e(o,{directory:a,environment:i});for(let[e,t]of Object.entries(o.profileEnvironment))i[e]===void 0&&(i[e]=t);return o}function B(e){let{unknownFlags:t}=D(e),n=e=>{let n=t.get(e);return typeof n==`string`&&n.trim()?n.trim():void 0},r=n(M.domains);return{majorMode:n(M.majorMode),domains:r?.split(`,`).map(e=>e.trim()).filter(Boolean),profile:n(M.profile),mute:t.get(M.mute)!==void 0,removedLayer:t.get(N)!==void 0}}function V(e){e.registerFlag(M.majorMode,{type:`string`,description:`Named major mode from .doom/modes.yaml`}),e.registerFlag(M.domains,{type:`string`,description:`Comma-separated content domains`}),e.registerFlag(M.profile,{type:`string`,description:`Persona and env from .doom/profiles.yaml`}),e.registerFlag(M.mute,{type:`boolean`,description:`Disable the notification extension`})}async function H(e,n,r,i){if(e.removedLayer&&i.push(`--${N} was replaced by --${M.majorMode}`),e.majorMode)try{y(n,e.majorMode,t())}catch(t){i.push(`--${M.majorMode} ${e.majorMode}: ${P(t)}`)}if(e.domains&&e.domains.length>0)try{await v(e.domains,t())}catch(t){i.push(`--domains ${e.domains.join(`,`)}: ${P(t)}`)}if(e.profile)try{await b(S(r,e.profile),t())}catch(t){i.push(`--profile ${e.profile}: ${P(t)}`)}}async function U(e,t=process.env){let n=g(e);return await w.promises.mkdir(n,{mode:448,recursive:!0}),i({temporaryDirectory:n},t),n}async function W(e){await w.promises.rm(g(e),{recursive:!0,force:!0})}async function G(e,n=t(),r=process.env){let a=n.root??e.root,d=m(e.resolved,e.compiled),p=n.mcp?d.optionalPackageEntry(o.mcp):void 0,h={agents:n.agents,autoStop:!1,preset:e.selection.preset,mcpEntry:p,personaEntry:d.ownEntry(s),workflowExtension:u(d.optionalPackageEntry(o.workflow),d.optionalPackageEntry(o.dispatcherWorkflow),r),layers:[...n.layers],majorModesConfig:C(a),resolvers:d};i({childExtensions:c({...h,agents:!1,mute:!0})},r),n.agents&&(r.PI_SUBAGENT_EXTRA_AGENT_DIRS=n.agentDirectories.join(x.delimiter),r.PI_SUBAGENT_EXTRA_SKILL_DIRS=n.skillDirectories.join(x.delimiter));let g=[...l({...h,mute:r[A]===`1`})].reverse(),_=e.bundles?.[f(g)];return _?[_]:g}async function K(e,t,n){let r=[];for(let i of t)try{let t=await import(T(i).href);if(typeof t.default!=`function`){n.push(`${i}: does not export an extension factory`);continue}await t.default(E(e,i)),r.push(i)}catch(e){n.push(`${i}: ${P(e)}`)}return r}async function q(e,t={}){let r=t.environment??process.env,i=[],a=R(t.cwd??process.cwd());if(!a)return{problems:[j],stale:!1,loaded:[]};let o;try{o=h(a)}catch(e){return{problems:[P(e)],stale:!1,loaded:[]}}if(!o)return{problems:[j],stale:!1,loaded:[]};let s=t.argv??process.argv.slice(2);if(r.DOOMPI_EXTENSIONS_PROVIDED===`1`||L(s,{...o.resolved,...o.bundles}))return{problems:[],stale:!1,loaded:[]};if(d({preset:o.selection.preset,piArgs:[]},r),!F(r)){r[O]=`1`,await z(o,a,r);let e=B(s);e.mute&&(r[A]=`1`),await H(e,C(a),a,i)}let c;try{c=await G(o,n(r).state,r)}catch(e){return{problems:[...i,P(e)],stale:!1,loaded:[]}}let l=await K(e,c,i);return{problems:i,stale:p(a,o.selection)!==o.inputsHash,loaded:l}}export{O as COMPOSED_ENV,M as DOOM_FLAGS,k as EXTERNAL_EXTENSIONS_ENV,A as MUTE_ENV,N as REMOVED_LAYER_FLAG,F as alreadyComposed,H as applyStartupFlags,W as cleanupRunDirectory,q as composeDoomSession,G as composeLoadOrder,L as extensionsProvidedExternally,R as findSyncedRoot,K as loadComposedExtensions,U as prepareRunDirectory,B as readStartupFlags,V as registerDoomFlags,z as startSyncedSession};
2
2
  //# sourceMappingURL=composer.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"composer.mjs","names":[],"sources":["../../src/services/composer.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { loadMajorModesConfig, type MajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { resolveProfile } from '@agimon-ai/doompi-config/profiles';\nimport { parseArgs, type ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport {\n createHarnessSession,\n getHarnessState,\n loadHarnessState,\n readHarnessState,\n updateHarnessState,\n type HarnessState,\n} from '../config/harnessState.ts';\nimport type { HarnessPreset } from '../interfaces/index.ts';\nimport { findRepositoryRoot } from '../utils/index.ts';\nimport { CONTEXT_PACKAGES, PERSONA_ENTRY, assembleChildExtensions, assembleExtensions } from './extensionAssembler.ts';\nimport { extensionSetKey } from './extensionCompiler.ts';\nimport { configurePreset } from './harnessContext.ts';\nimport { applyDomains, applyMajorMode, applyProfile } from './matrixSwitcher.ts';\nimport { selectWorkflowExtensionForProcess } from './resourceCollector.ts';\nimport {\n computeInputsHash,\n createMapResolvers,\n readSyncState,\n runDirectory,\n syncStatePath,\n type SyncState,\n} from './syncState.ts';\n\n/**\n * The runtime half of `doom-pi sync`.\n *\n * Pi's own config carries a single entry, the doom-pi extension, and this is\n * what that entry runs: hydrate the harness environment, apply the startup\n * flags, then load the extension set the matrix selects. It runs again on every\n * `/reload`, which is what lets `/major-mode` swap the set without a restart,\n * since Pi rebuilds its extension runner from whatever a reload produced.\n */\n\n/** Marks a process whose extensions this module composed. */\nexport const COMPOSED_ENV = 'DOOMPI_COMPOSED';\n/** Set by the launcher when it passes one precomposed aggregate to Pi. */\nexport const EXTERNAL_EXTENSIONS_ENV = 'DOOMPI_EXTENSIONS_PROVIDED';\n/** Survives reload, where argv is re-read but the startup flags must not be. */\nexport const MUTE_ENV = 'DOOMPI_MUTE';\nconst ENABLED_FLAG = '1';\nconst SUBAGENT_AGENT_DIRS_ENV = 'PI_SUBAGENT_EXTRA_AGENT_DIRS';\nconst SUBAGENT_SKILL_DIRS_ENV = 'PI_SUBAGENT_EXTRA_SKILL_DIRS';\nconst DOMAIN_SEPARATOR = ',';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst NOT_SYNCED = 'doompi is configured for Pi but this repository was never synced. Run doompi sync.';\n\nexport const DOOM_FLAGS = {\n majorMode: 'major-mode',\n domains: 'domains',\n profile: 'profile',\n mute: 'mute',\n} as const;\n\n/** Removed flag, reported rather than ignored when a synced session passes it. */\nexport const REMOVED_LAYER_FLAG = 'layer';\n\nexport interface StartupFlags {\n majorMode?: string;\n domains?: string[];\n profile?: string;\n mute: boolean;\n /** True when the caller passed the removed `--layer` flag. */\n removedLayer: boolean;\n}\n\nexport interface ComposeOutcome {\n /** Everything that went wrong, reported once the session can show messages. */\n problems: string[];\n /** Config changed since the last sync, which is a prompt to re-sync. */\n stale: boolean;\n loaded: string[];\n}\n\nfunction message(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** True when this process already applied the command line, so a reload does not. */\nexport function alreadyComposed(environment: NodeJS.ProcessEnv = process.env): boolean {\n return environment[COMPOSED_ENV] === ENABLED_FLAG;\n}\n\nfunction realPath(target: string): string {\n try {\n return fs.realpathSync(target);\n } catch {\n return path.resolve(target);\n }\n}\n\n/**\n * True when this process was handed the doom extension set on the command line.\n *\n * The launcher and detached Doom Team children both pass the composed list as\n * `--extension` arguments, and Pi merges those with whatever project settings\n * declare. Composing again here would load every extension twice, which Pi\n * reports as each tool and flag conflicting with itself.\n *\n * Matching against the synced paths rather than the mere presence of\n * `--extension` keeps an unrelated one-off, `pi -e ./debug.ts`, from silently\n * costing the user their whole setup.\n */\nexport function extensionsProvidedExternally(argv: string[], resolved: Record<string, string>): boolean {\n const { extensions } = parseArgs(argv);\n if (!extensions || extensions.length === 0) return false;\n const composed = new Set(Object.values(resolved).map(realPath));\n return extensions.some((entry) => composed.has(realPath(entry)));\n}\n\n/**\n * Finds the repository whose synced state applies to this session.\n *\n * Pi only reads `.pi/settings.json` from its own working directory, so that is\n * the first place to look; walking up covers a session started in a\n * subdirectory with the extension configured globally.\n */\nexport function findSyncedRoot(cwd: string): string | undefined {\n if (fs.existsSync(syncStatePath(cwd))) return cwd;\n try {\n const root = findRepositoryRoot(cwd);\n return fs.existsSync(syncStatePath(root)) ? root : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Opens this process's session from what sync pinned.\n *\n * The synced record is decoded underneath the live environment, so an inherited\n * value still wins: a launcher run, a nested session, or a CI override is more\n * specific than anything sync wrote. The result becomes the session's state\n * file, which is the authority from here on; the environment it publishes is\n * for the bash hooks and the packages that can only read one.\n */\nexport async function startSyncedSession(\n state: SyncState,\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<HarnessState> {\n const directory = await prepareRunDirectory(repoRoot);\n const harness: HarnessState = {\n ...readHarnessState({ ...state.env, ...environment }),\n // Recorded separately because no variable carries them.\n ...state.fileState,\n temporaryDirectory: directory,\n };\n createHarnessSession(harness, { directory, environment });\n // The profile's own defaults, which the launcher applies at spawn. They are\n // recorded as a map rather than as variables so /profile can tell which ones\n // it owns when it swaps them out later.\n for (const [key, value] of Object.entries(harness.profileEnvironment)) {\n if (environment[key] === undefined) environment[key] = value;\n }\n return harness;\n}\n\n/**\n * Reads the doom flags off the command line.\n *\n * Pi collects unrecognized flags for extensions but hands their values back\n * only after every extension has loaded, which is too late to decide what to\n * load. Parsing argv with Pi's own parser keeps the value rules identical.\n */\nexport function readStartupFlags(argv: string[]): StartupFlags {\n const { unknownFlags } = parseArgs(argv);\n const text = (name: string): string | undefined => {\n const value = unknownFlags.get(name);\n return typeof value === 'string' && value.trim() ? value.trim() : undefined;\n };\n const domains = text(DOOM_FLAGS.domains);\n return {\n majorMode: text(DOOM_FLAGS.majorMode),\n domains: domains\n ?.split(DOMAIN_SEPARATOR)\n .map((name) => name.trim())\n .filter(Boolean),\n profile: text(DOOM_FLAGS.profile),\n mute: unknownFlags.get(DOOM_FLAGS.mute) !== undefined,\n removedLayer: unknownFlags.get(REMOVED_LAYER_FLAG) !== undefined,\n };\n}\n\n/** Registers the flags so Pi accepts them and lists them in `pi --help`. */\nexport function registerDoomFlags(pi: Pick<ExtensionAPI, 'registerFlag'>): void {\n pi.registerFlag(DOOM_FLAGS.majorMode, {\n type: 'string',\n description: 'Named major mode from .doom/modes.yaml',\n });\n pi.registerFlag(DOOM_FLAGS.domains, { type: 'string', description: 'Comma-separated content domains' });\n pi.registerFlag(DOOM_FLAGS.profile, { type: 'string', description: 'Persona and env from .doom/profiles.yaml' });\n pi.registerFlag(DOOM_FLAGS.mute, { type: 'boolean', description: 'Disable the notification extension' });\n}\n\n/**\n * Applies the startup flags through the same switchers the slash commands use.\n *\n * A bad value is collected rather than thrown: a factory that throws takes the\n * whole doom setup down with it, which is far worse than one unapplied flag.\n */\nexport async function applyStartupFlags(\n flags: StartupFlags,\n majorModesConfig: MajorModesConfig,\n repoRoot: string,\n problems: string[],\n): Promise<void> {\n // Reported rather than ignored: Pi hands unrecognized flags to extensions, so\n // a stale `--layer dev` would otherwise start a session on the wrong mode\n // without saying anything.\n if (flags.removedLayer) problems.push(`--${REMOVED_LAYER_FLAG} was replaced by --${DOOM_FLAGS.majorMode}`);\n if (flags.majorMode) {\n try {\n applyMajorMode(majorModesConfig, flags.majorMode, getHarnessState());\n } catch (error) {\n problems.push(`--${DOOM_FLAGS.majorMode} ${flags.majorMode}: ${message(error)}`);\n }\n }\n if (flags.domains && flags.domains.length > 0) {\n try {\n await applyDomains(flags.domains, getHarnessState());\n } catch (error) {\n problems.push(`--domains ${flags.domains.join(DOMAIN_SEPARATOR)}: ${message(error)}`);\n }\n }\n if (flags.profile) {\n try {\n await applyProfile(resolveProfile(repoRoot, flags.profile), getHarnessState());\n } catch (error) {\n problems.push(`--profile ${flags.profile}: ${message(error)}`);\n }\n }\n}\n\n/**\n * Points harness state at a per-process directory before anything writes there.\n *\n * The synced directory is a baseline that every session reads; live switches\n * materialize into this one instead, so a second session in the same repository\n * cannot overwrite what the first is using.\n */\nexport async function prepareRunDirectory(\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<string> {\n const directory = runDirectory(repoRoot);\n await fs.promises.mkdir(directory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n // Through the store rather than the environment: the state file is the\n // authority, and a switcher reading a stale directory would write a live\n // session's resources into the wrong place.\n updateHarnessState({ temporaryDirectory: directory }, environment);\n return directory;\n}\n\n/** Removes this session's scratch directory. */\nexport async function cleanupRunDirectory(repoRoot: string): Promise<void> {\n await fs.promises.rm(runDirectory(repoRoot), { recursive: true, force: true });\n}\n\n/**\n * Builds the ordered list of extensions to load for the current state.\n *\n * Returns them in Pi load order. `assembleExtensions` produces the order the\n * launcher passes to Pi, where the last element becomes the first `--extension`\n * argument, so reversing here keeps that rule stated in exactly one place.\n */\nexport async function composeLoadOrder(\n state: SyncState,\n harness: HarnessState = getHarnessState(),\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<string[]> {\n const repoRoot = harness.root ?? state.root;\n const resolvers = createMapResolvers(state.resolved, state.compiled);\n // Resolved from the sync map rather than generated: doom-mcp reads the\n // repository and plugin layers itself, and the environment the launcher wrote\n // already tells it which plugins and allowlist this session selected.\n const mcpEntry = harness.mcp ? resolvers.optionalPackageEntry(CONTEXT_PACKAGES.mcp) : undefined;\n const context = {\n agents: harness.agents,\n // Exiting when the agent settles is a launcher decision with no synced\n // equivalent: a session started by hand is meant to stay open.\n autoStop: false,\n preset: state.selection.preset,\n mcpEntry,\n personaEntry: resolvers.ownEntry(PERSONA_ENTRY),\n workflowExtension: selectWorkflowExtensionForProcess(\n resolvers.optionalPackageEntry(CONTEXT_PACKAGES.workflow),\n resolvers.optionalPackageEntry(CONTEXT_PACKAGES.dispatcherWorkflow),\n environment,\n ),\n layers: [...harness.layers],\n majorModesConfig: loadMajorModesConfig(repoRoot),\n resolvers,\n };\n\n // A state field, so it goes through the store: Doom Team reads it from the\n // environment the store publishes when it builds a child's spawn snapshot.\n updateHarnessState(\n { childExtensions: assembleChildExtensions({ ...context, agents: false, mute: true }) },\n environment,\n );\n if (harness.agents) {\n environment[SUBAGENT_AGENT_DIRS_ENV] = harness.agentDirectories.join(path.delimiter);\n environment[SUBAGENT_SKILL_DIRS_ENV] = harness.skillDirectories.join(path.delimiter);\n }\n const entries = [...assembleExtensions({ ...context, mute: environment[MUTE_ENV] === ENABLED_FLAG })].reverse();\n const bundle = state.bundles?.[extensionSetKey(entries)];\n return bundle ? [bundle] : entries;\n}\n\n/**\n * Imports each extension and hands it the same Pi API this module received.\n *\n * Every Pi extension entry is a factory of the same shape, so one extension can\n * activate the rest. Failures are per entry: one unloadable module must not\n * cost the session everything after it in the order.\n */\nexport async function loadComposedExtensions(\n pi: ExtensionAPI,\n entries: string[],\n problems: string[],\n): Promise<string[]> {\n const loaded: string[] = [];\n for (const entry of entries) {\n try {\n const module = (await import(pathToFileURL(entry).href)) as { default?: unknown };\n if (typeof module.default !== 'function') {\n problems.push(`${entry}: does not export an extension factory`);\n continue;\n }\n await (module.default as (api: ExtensionAPI) => void | Promise<void>)(pi);\n loaded.push(entry);\n } catch (error) {\n problems.push(`${entry}: ${message(error)}`);\n }\n }\n return loaded;\n}\n\nexport interface ComposeOptions {\n cwd?: string;\n argv?: string[];\n environment?: NodeJS.ProcessEnv;\n}\n\n/**\n * Runs the whole synced startup for one Pi session.\n *\n * Never throws: whatever fails is reported through the outcome so the session\n * still starts and can say what is wrong.\n */\nexport async function composeDoomSession(pi: ExtensionAPI, options: ComposeOptions = {}): Promise<ComposeOutcome> {\n const environment = options.environment ?? process.env;\n const problems: string[] = [];\n const repoRoot = findSyncedRoot(options.cwd ?? process.cwd());\n if (!repoRoot) return { problems: [NOT_SYNCED], stale: false, loaded: [] };\n\n let state: SyncState | undefined;\n try {\n state = readSyncState(repoRoot);\n } catch (error) {\n return { problems: [message(error)], stale: false, loaded: [] };\n }\n if (!state) return { problems: [NOT_SYNCED], stale: false, loaded: [] };\n\n const argv = options.argv ?? process.argv.slice(2);\n // The launcher and Doom Team children compose the set themselves and pass it\n // in, so this entry has nothing left to do but stay out of the way.\n if (\n environment[EXTERNAL_EXTENSIONS_ENV] === ENABLED_FLAG ||\n extensionsProvidedExternally(argv, { ...state.resolved, ...state.bundles })\n ) {\n return { problems: [], stale: false, loaded: [] };\n }\n\n configurePreset({ preset: state.selection.preset as HarnessPreset, piArgs: [] }, environment);\n\n // Only the first load opens a session. A reload finds the pointer this set\n // and reads the file back, which is what carries a live /domains or /profile\n // switch across the reload instead of resetting it to what sync pinned.\n if (!alreadyComposed(environment)) {\n environment[COMPOSED_ENV] = ENABLED_FLAG;\n await startSyncedSession(state, repoRoot, environment);\n const flags = readStartupFlags(argv);\n if (flags.mute) environment[MUTE_ENV] = ENABLED_FLAG;\n await applyStartupFlags(flags, loadMajorModesConfig(repoRoot), repoRoot, problems);\n }\n\n let entries: string[];\n try {\n // Read back through the store rather than from this process: the startup\n // flags may have switched the layer or the domains a moment ago, and the\n // environment being managed is not always this process's own.\n entries = await composeLoadOrder(state, loadHarnessState(environment).state, environment);\n } catch (error) {\n return { problems: [...problems, message(error)], stale: false, loaded: [] };\n }\n\n const loaded = await loadComposedExtensions(pi, entries, problems);\n return { problems, stale: computeInputsHash(repoRoot, state.selection) !== state.inputsHash, loaded };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"composer.mjs","names":[],"sources":["../../src/services/composer.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { loadMajorModesConfig, type MajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { resolveProfile } from '@agimon-ai/doompi-config/profiles';\nimport { withExtensionSource } from '@agimon-ai/doompi-ui/extensionName';\nimport { type ExtensionAPI, parseArgs } from '@earendil-works/pi-coding-agent';\nimport {\n createHarnessSession,\n getHarnessState,\n type HarnessState,\n loadHarnessState,\n readHarnessState,\n updateHarnessState,\n} from '../config/harnessState.ts';\nimport type { HarnessPreset } from '../interfaces/index.ts';\nimport { findRepositoryRoot } from '../utils/index.ts';\nimport { assembleChildExtensions, assembleExtensions, CONTEXT_PACKAGES, PERSONA_ENTRY } from './extensionAssembler.ts';\nimport { extensionSetKey } from './extensionCompiler.ts';\nimport { configurePreset } from './harnessContext.ts';\nimport { applyDomains, applyMajorMode, applyProfile } from './matrixSwitcher.ts';\nimport { selectWorkflowExtensionForProcess } from './resourceCollector.ts';\nimport {\n computeInputsHash,\n createMapResolvers,\n readSyncState,\n runDirectory,\n type SyncState,\n syncStatePath,\n} from './syncState.ts';\n\n/**\n * The runtime half of `doom-pi sync`.\n *\n * Pi's own config carries a single entry, the doom-pi extension, and this is\n * what that entry runs: hydrate the harness environment, apply the startup\n * flags, then load the extension set the matrix selects. It runs again on every\n * `/reload`, which is what lets `/major-mode` swap the set without a restart,\n * since Pi rebuilds its extension runner from whatever a reload produced.\n */\n\n/** Marks a process whose extensions this module composed. */\nexport const COMPOSED_ENV = 'DOOMPI_COMPOSED';\n/** Set by the launcher when it passes one precomposed aggregate to Pi. */\nexport const EXTERNAL_EXTENSIONS_ENV = 'DOOMPI_EXTENSIONS_PROVIDED';\n/** Survives reload, where argv is re-read but the startup flags must not be. */\nexport const MUTE_ENV = 'DOOMPI_MUTE';\nconst ENABLED_FLAG = '1';\nconst SUBAGENT_AGENT_DIRS_ENV = 'PI_SUBAGENT_EXTRA_AGENT_DIRS';\nconst SUBAGENT_SKILL_DIRS_ENV = 'PI_SUBAGENT_EXTRA_SKILL_DIRS';\nconst DOMAIN_SEPARATOR = ',';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst NOT_SYNCED = 'doompi is configured for Pi but this repository was never synced. Run doompi sync.';\n\nexport const DOOM_FLAGS = {\n majorMode: 'major-mode',\n domains: 'domains',\n profile: 'profile',\n mute: 'mute',\n} as const;\n\n/** Removed flag, reported rather than ignored when a synced session passes it. */\nexport const REMOVED_LAYER_FLAG = 'layer';\n\nexport interface StartupFlags {\n majorMode?: string;\n domains?: string[];\n profile?: string;\n mute: boolean;\n /** True when the caller passed the removed `--layer` flag. */\n removedLayer: boolean;\n}\n\nexport interface ComposeOutcome {\n /** Everything that went wrong, reported once the session can show messages. */\n problems: string[];\n /** Config changed since the last sync, which is a prompt to re-sync. */\n stale: boolean;\n loaded: string[];\n}\n\nfunction message(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** True when this process already applied the command line, so a reload does not. */\nexport function alreadyComposed(environment: NodeJS.ProcessEnv = process.env): boolean {\n return environment[COMPOSED_ENV] === ENABLED_FLAG;\n}\n\nfunction realPath(target: string): string {\n try {\n return fs.realpathSync(target);\n } catch {\n return path.resolve(target);\n }\n}\n\n/**\n * True when this process was handed the doom extension set on the command line.\n *\n * The launcher and detached Doom Team children both pass the composed list as\n * `--extension` arguments, and Pi merges those with whatever project settings\n * declare. Composing again here would load every extension twice, which Pi\n * reports as each tool and flag conflicting with itself.\n *\n * Matching against the synced paths rather than the mere presence of\n * `--extension` keeps an unrelated one-off, `pi -e ./debug.ts`, from silently\n * costing the user their whole setup.\n */\nexport function extensionsProvidedExternally(argv: string[], resolved: Record<string, string>): boolean {\n const { extensions } = parseArgs(argv);\n if (!extensions || extensions.length === 0) return false;\n const composed = new Set(Object.values(resolved).map(realPath));\n return extensions.some((entry) => composed.has(realPath(entry)));\n}\n\n/**\n * Finds the repository whose synced state applies to this session.\n *\n * Pi only reads `.pi/settings.json` from its own working directory, so that is\n * the first place to look; walking up covers a session started in a\n * subdirectory with the extension configured globally.\n */\nexport function findSyncedRoot(cwd: string): string | undefined {\n if (fs.existsSync(syncStatePath(cwd))) return cwd;\n try {\n const root = findRepositoryRoot(cwd);\n return fs.existsSync(syncStatePath(root)) ? root : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Opens this process's session from what sync pinned.\n *\n * The synced record is decoded underneath the live environment, so an inherited\n * value still wins: a launcher run, a nested session, or a CI override is more\n * specific than anything sync wrote. The result becomes the session's state\n * file, which is the authority from here on; the environment it publishes is\n * for the bash hooks and the packages that can only read one.\n */\nexport async function startSyncedSession(\n state: SyncState,\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<HarnessState> {\n const directory = await prepareRunDirectory(repoRoot);\n const harness: HarnessState = {\n ...readHarnessState({ ...state.env, ...environment }),\n // Recorded separately because no variable carries them.\n ...state.fileState,\n temporaryDirectory: directory,\n };\n createHarnessSession(harness, { directory, environment });\n // The profile's own defaults, which the launcher applies at spawn. They are\n // recorded as a map rather than as variables so /profile can tell which ones\n // it owns when it swaps them out later.\n for (const [key, value] of Object.entries(harness.profileEnvironment)) {\n if (environment[key] === undefined) environment[key] = value;\n }\n return harness;\n}\n\n/**\n * Reads the doom flags off the command line.\n *\n * Pi collects unrecognized flags for extensions but hands their values back\n * only after every extension has loaded, which is too late to decide what to\n * load. Parsing argv with Pi's own parser keeps the value rules identical.\n */\nexport function readStartupFlags(argv: string[]): StartupFlags {\n const { unknownFlags } = parseArgs(argv);\n const text = (name: string): string | undefined => {\n const value = unknownFlags.get(name);\n return typeof value === 'string' && value.trim() ? value.trim() : undefined;\n };\n const domains = text(DOOM_FLAGS.domains);\n return {\n majorMode: text(DOOM_FLAGS.majorMode),\n domains: domains\n ?.split(DOMAIN_SEPARATOR)\n .map((name) => name.trim())\n .filter(Boolean),\n profile: text(DOOM_FLAGS.profile),\n mute: unknownFlags.get(DOOM_FLAGS.mute) !== undefined,\n removedLayer: unknownFlags.get(REMOVED_LAYER_FLAG) !== undefined,\n };\n}\n\n/** Registers the flags so Pi accepts them and lists them in `pi --help`. */\nexport function registerDoomFlags(pi: Pick<ExtensionAPI, 'registerFlag'>): void {\n pi.registerFlag(DOOM_FLAGS.majorMode, {\n type: 'string',\n description: 'Named major mode from .doom/modes.yaml',\n });\n pi.registerFlag(DOOM_FLAGS.domains, { type: 'string', description: 'Comma-separated content domains' });\n pi.registerFlag(DOOM_FLAGS.profile, { type: 'string', description: 'Persona and env from .doom/profiles.yaml' });\n pi.registerFlag(DOOM_FLAGS.mute, { type: 'boolean', description: 'Disable the notification extension' });\n}\n\n/**\n * Applies the startup flags through the same switchers the slash commands use.\n *\n * A bad value is collected rather than thrown: a factory that throws takes the\n * whole doom setup down with it, which is far worse than one unapplied flag.\n */\nexport async function applyStartupFlags(\n flags: StartupFlags,\n majorModesConfig: MajorModesConfig,\n repoRoot: string,\n problems: string[],\n): Promise<void> {\n // Reported rather than ignored: Pi hands unrecognized flags to extensions, so\n // a stale `--layer dev` would otherwise start a session on the wrong mode\n // without saying anything.\n if (flags.removedLayer) problems.push(`--${REMOVED_LAYER_FLAG} was replaced by --${DOOM_FLAGS.majorMode}`);\n if (flags.majorMode) {\n try {\n applyMajorMode(majorModesConfig, flags.majorMode, getHarnessState());\n } catch (error) {\n problems.push(`--${DOOM_FLAGS.majorMode} ${flags.majorMode}: ${message(error)}`);\n }\n }\n if (flags.domains && flags.domains.length > 0) {\n try {\n await applyDomains(flags.domains, getHarnessState());\n } catch (error) {\n problems.push(`--domains ${flags.domains.join(DOMAIN_SEPARATOR)}: ${message(error)}`);\n }\n }\n if (flags.profile) {\n try {\n await applyProfile(resolveProfile(repoRoot, flags.profile), getHarnessState());\n } catch (error) {\n problems.push(`--profile ${flags.profile}: ${message(error)}`);\n }\n }\n}\n\n/**\n * Points harness state at a per-process directory before anything writes there.\n *\n * The synced directory is a baseline that every session reads; live switches\n * materialize into this one instead, so a second session in the same repository\n * cannot overwrite what the first is using.\n */\nexport async function prepareRunDirectory(\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<string> {\n const directory = runDirectory(repoRoot);\n await fs.promises.mkdir(directory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n // Through the store rather than the environment: the state file is the\n // authority, and a switcher reading a stale directory would write a live\n // session's resources into the wrong place.\n updateHarnessState({ temporaryDirectory: directory }, environment);\n return directory;\n}\n\n/** Removes this session's scratch directory. */\nexport async function cleanupRunDirectory(repoRoot: string): Promise<void> {\n await fs.promises.rm(runDirectory(repoRoot), { recursive: true, force: true });\n}\n\n/**\n * Builds the ordered list of extensions to load for the current state.\n *\n * Returns them in Pi load order. `assembleExtensions` produces the order the\n * launcher passes to Pi, where the last element becomes the first `--extension`\n * argument, so reversing here keeps that rule stated in exactly one place.\n */\nexport async function composeLoadOrder(\n state: SyncState,\n harness: HarnessState = getHarnessState(),\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<string[]> {\n const repoRoot = harness.root ?? state.root;\n const resolvers = createMapResolvers(state.resolved, state.compiled);\n // Resolved from the sync map rather than generated: doom-mcp reads the\n // repository and plugin layers itself, and the environment the launcher wrote\n // already tells it which plugins and allowlist this session selected.\n const mcpEntry = harness.mcp ? resolvers.optionalPackageEntry(CONTEXT_PACKAGES.mcp) : undefined;\n const context = {\n agents: harness.agents,\n // Exiting when the agent settles is a launcher decision with no synced\n // equivalent: a session started by hand is meant to stay open.\n autoStop: false,\n preset: state.selection.preset,\n mcpEntry,\n personaEntry: resolvers.ownEntry(PERSONA_ENTRY),\n workflowExtension: selectWorkflowExtensionForProcess(\n resolvers.optionalPackageEntry(CONTEXT_PACKAGES.workflow),\n resolvers.optionalPackageEntry(CONTEXT_PACKAGES.dispatcherWorkflow),\n environment,\n ),\n layers: [...harness.layers],\n majorModesConfig: loadMajorModesConfig(repoRoot),\n resolvers,\n };\n\n // A state field, so it goes through the store: Doom Team reads it from the\n // environment the store publishes when it builds a child's spawn snapshot.\n updateHarnessState(\n { childExtensions: assembleChildExtensions({ ...context, agents: false, mute: true }) },\n environment,\n );\n if (harness.agents) {\n environment[SUBAGENT_AGENT_DIRS_ENV] = harness.agentDirectories.join(path.delimiter);\n environment[SUBAGENT_SKILL_DIRS_ENV] = harness.skillDirectories.join(path.delimiter);\n }\n const entries = [...assembleExtensions({ ...context, mute: environment[MUTE_ENV] === ENABLED_FLAG })].reverse();\n const bundle = state.bundles?.[extensionSetKey(entries)];\n return bundle ? [bundle] : entries;\n}\n\n/**\n * Imports each extension and hands it a source-aware view of the Pi API.\n *\n * Every Pi extension entry is a factory of the same shape, so one extension can\n * activate the rest. The view delegates to the same host API while retaining the\n * entry path for tools registered through a composed loader. Failures are per\n * entry: one unloadable module must not cost the session everything after it.\n */\nexport async function loadComposedExtensions(\n pi: ExtensionAPI,\n entries: string[],\n problems: string[],\n): Promise<string[]> {\n const loaded: string[] = [];\n for (const entry of entries) {\n try {\n const module = (await import(pathToFileURL(entry).href)) as { default?: unknown };\n if (typeof module.default !== 'function') {\n problems.push(`${entry}: does not export an extension factory`);\n continue;\n }\n await (module.default as (api: ExtensionAPI) => void | Promise<void>)(withExtensionSource(pi, entry));\n loaded.push(entry);\n } catch (error) {\n problems.push(`${entry}: ${message(error)}`);\n }\n }\n return loaded;\n}\n\nexport interface ComposeOptions {\n cwd?: string;\n argv?: string[];\n environment?: NodeJS.ProcessEnv;\n}\n\n/**\n * Runs the whole synced startup for one Pi session.\n *\n * Never throws: whatever fails is reported through the outcome so the session\n * still starts and can say what is wrong.\n */\nexport async function composeDoomSession(pi: ExtensionAPI, options: ComposeOptions = {}): Promise<ComposeOutcome> {\n const environment = options.environment ?? process.env;\n const problems: string[] = [];\n const repoRoot = findSyncedRoot(options.cwd ?? process.cwd());\n if (!repoRoot) return { problems: [NOT_SYNCED], stale: false, loaded: [] };\n\n let state: SyncState | undefined;\n try {\n state = readSyncState(repoRoot);\n } catch (error) {\n return { problems: [message(error)], stale: false, loaded: [] };\n }\n if (!state) return { problems: [NOT_SYNCED], stale: false, loaded: [] };\n\n const argv = options.argv ?? process.argv.slice(2);\n // The launcher and Doom Team children compose the set themselves and pass it\n // in, so this entry has nothing left to do but stay out of the way.\n if (\n environment[EXTERNAL_EXTENSIONS_ENV] === ENABLED_FLAG ||\n extensionsProvidedExternally(argv, { ...state.resolved, ...state.bundles })\n ) {\n return { problems: [], stale: false, loaded: [] };\n }\n\n configurePreset({ preset: state.selection.preset as HarnessPreset, piArgs: [] }, environment);\n\n // Only the first load opens a session. A reload finds the pointer this set\n // and reads the file back, which is what carries a live /domains or /profile\n // switch across the reload instead of resetting it to what sync pinned.\n if (!alreadyComposed(environment)) {\n environment[COMPOSED_ENV] = ENABLED_FLAG;\n await startSyncedSession(state, repoRoot, environment);\n const flags = readStartupFlags(argv);\n if (flags.mute) environment[MUTE_ENV] = ENABLED_FLAG;\n await applyStartupFlags(flags, loadMajorModesConfig(repoRoot), repoRoot, problems);\n }\n\n let entries: string[];\n try {\n // Read back through the store rather than from this process: the startup\n // flags may have switched the layer or the domains a moment ago, and the\n // environment being managed is not always this process's own.\n entries = await composeLoadOrder(state, loadHarnessState(environment).state, environment);\n } catch (error) {\n return { problems: [...problems, message(error)], stale: false, loaded: [] };\n }\n\n const loaded = await loadComposedExtensions(pi, entries, problems);\n return { problems, stale: computeInputsHash(repoRoot, state.selection) !== state.inputsHash, loaded };\n}\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- const e=require(`../_virtual/_rolldown/runtime.cjs`),t=require(`../utils/moduleResolution.cjs`);require(`../utils/index.cjs`);let n=require(`node:path`);n=e.__toESM(n,1);let r=require(`node:module`),i=require(`node:fs`);i=e.__toESM(i,1);let a=require(`node:crypto`),o=require(`node:url`);const s=[`.ts`,`.mts`,`.cts`],c=`.mjs`,l=`\0doompi-extension-set`,u={"@mariozechner/pi-coding-agent":`@earendil-works/pi-coding-agent`,"@mariozechner/pi-tui":`@earendil-works/pi-tui`,"@mariozechner/pi-ai":`@earendil-works/pi-ai`,"@sinclair/typebox":`typebox`};function d(e){return s.some(t=>e.endsWith(t))}function f(e){for(let[t,n]of Object.entries(u)){if(e===t)return n;if(e.startsWith(`${t}/`))return`${n}${e.slice(t.length)}`}return e}function p(e){let t=i.default.readFileSync(e);return(0,a.createHash)(`sha256`).update(`v3`).update(e).update(t).digest(`hex`).slice(0,16)}function m(e,t){let r=n.default.basename(e).replace(/\.[cm]?ts$/,``);return`${n.default.basename(n.default.dirname(e))}-${r}.${t}${c}`}const h=new Set([`@earendil-works/pi-coding-agent`,`@earendil-works/pi-tui`,`@earendil-works/pi-ai`]);function g(e){let n=t.optionalPackageEntry(`@earendil-works/pi-coding-agent`);if(n)try{return(0,r.createRequire)(n).resolve(e)}catch{return}}const _=new Set([`@parcel/watcher`,`@napi-rs/keyring`,`better-sqlite3`,`canvas`,`fsevents`,`isolated-vm`,`keytar`,`node-pty`,`onnxruntime-node`,`playwright`,`playwright-core`,`puppeteer`,`sharp`]),v=new Set(r.builtinModules.flatMap(e=>[e,`node:${e}`]));function y(e){return e.trim().replace(/[^a-zA-Z0-9._-]+/g,`-`).replace(/^[-.]+|[-.]+$/g,``)||`doom-set`}function b(e){let t=e.startsWith(`@`)?e.indexOf(`/`,e.indexOf(`/`)+1):e.indexOf(`/`);return t===-1?e:e.slice(0,t)}function x(e){let t=e.split(n.default.sep).join(`/`),r=t.lastIndexOf(`/node_modules/`);if(r===-1)return;let i=t.slice(r+14).split(`/`),a=i.lastIndexOf(`node_modules`),o=a===-1?i:i.slice(a+1);return o[0]?.startsWith(`@`)?o.slice(0,2).join(`/`):o[0]}function S(){return{name:`doom-external-absolute`,async resolveId(e,r,i){if(e.startsWith(`.`)||n.default.isAbsolute(e)||e.startsWith(`node:`))return null;let a=f(e),o=b(a);if(o&&h.has(o)){let e=t.optionalPackageEntry(a);if(e)return{id:e,external:!0}}let s=await this.resolve(a,r,i);return!s||d(s.id)?null:{id:s.id,external:!0}}}}function C(e){try{let t=i.default.statSync(e);return t.isFile()?{path:e,size:t.size,mtimeMs:t.mtimeMs}:void 0}catch{return}}function w(e,t){return e.version!==`v4`||JSON.stringify(e.entries)!==JSON.stringify(t)||!i.default.existsSync(e.output)?!1:e.inputs.every(e=>{let t=C(e.path);return t?.size===e.size&&t.mtimeMs===e.mtimeMs})}function T(e){try{let t=JSON.parse(i.default.readFileSync(e,`utf8`));return t&&Array.isArray(t.entries)&&Array.isArray(t.inputs)?t:void 0}catch{return}}function E(e,t){let n=`${e}.${process.pid}.${Date.now()}.tmp`;i.default.writeFileSync(n,t);try{i.default.renameSync(n,e)}catch(t){if(i.default.rmSync(n,{force:!0}),!i.default.existsSync(e))throw t}}function D(e){return(0,a.createHash)(`sha256`).update(`v4`).update(JSON.stringify(e.map(e=>n.default.resolve(e)))).digest(`hex`).slice(0,16)}function O(e){let t=e.map(e=>`() => import(${JSON.stringify(k(e))})`),n=e.map(e=>JSON.stringify(e));return[`const loaders = [${t.join(`,`)}];`,`const names = [${n.join(`,`)}];`,`function report(index, error) {`,` const detail = error instanceof Error ? error.message : String(error);`," process.stderr.write(`[doompi] ${names[index]}: ${detail}\\n`);",`}`,`export default async function doompiExtensionSet(pi) {`,` const loaded = await Promise.all(loaders.map(async (load, index) => {`,` try {`,` return await load();`,` } catch (error) {`,` report(index, error);`,` return undefined;`,` }`,` }));`,` for (let index = 0; index < loaded.length; index += 1) {`,` const extension = loaded[index];`,` if (!extension) continue;`,` try {`,` if (typeof extension.default !== 'function') throw new Error('does not export an extension factory');`,` await extension.default(pi);`,` } catch (error) {`,` report(index, error);`,` }`,` }`,`}`].join(`
2
- `)}function k(e){return n.default.resolve(e).split(n.default.sep).join(`/`)}function A(e){return{name:`doom-set-external`,async resolveId(r,i,a){if(r===l)return null;if(v.has(r)||r.startsWith(`node:`))return{id:r,external:!0};if(n.default.isAbsolute(r)){let t=x(r);return t&&(h.has(t)||_.has(t))?(e.add(r),{id:r,external:!0}):null}if(r.startsWith(`.`))return null;let o=f(r),s=b(o);if(s&&h.has(s)){let n=t.optionalPackageEntry(o);if(n)return e.add(n),{id:n,external:!0}}if(s===`typebox`){let t=g(o);if(t)return e.add(t),{id:t,external:!1}}if(s&&_.has(s)){let t=await this.resolve(o,i,a);if(t)return e.add(t.id),{id:t.id,external:!0}}return null}}}function j(e){return{name:`doom-set-inputs`,load(t){return n.default.isAbsolute(t)&&i.default.existsSync(t)&&e.add(t),null}}}function M(e,t,n){t.code===`UNRESOLVED_IMPORT`&&n(`error`,t)}function N(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function P(e,t){if(Array.isArray(e)){for(let n of e)P(n,t);return}if(!N(e))return;let n=e.object,r=e.property;if(e.type===`MemberExpression`&&e.computed===!1&&typeof e.start==`number`&&typeof e.end==`number`&&N(n)&&n.type===`MetaProperty`&&N(n.meta)&&n.meta.name===`import`&&N(n.property)&&n.property.name===`meta`&&N(r)&&r.name===`url`){t.push({start:e.start,end:e.end});return}for(let n of Object.values(e))P(n,t)}function F(){return{name:`doom-original-import-meta-url`,transform(e,t){if(!n.default.isAbsolute(t)||!e.includes(`import.meta.url`))return null;let r=n.default.extname(t).toLowerCase(),i=r===`.tsx`?`tsx`:r===`.jsx`?`jsx`:d(t)?`ts`:`js`,a=[];if(P(this.parse(e,{lang:i}),a),a.length===0)return null;let s=JSON.stringify((0,o.pathToFileURL)(t).href),c=e;for(let e of a.sort((e,t)=>t.start-e.start))c=`${c.slice(0,e.start)}${s}${c.slice(e.end)}`;return c}}}async function I(e,t,r={}){if(e.length===0)throw Error(`Cannot compile an empty extension set`);let o=e.map(e=>n.default.resolve(e)),s=n.default.join(t,`sets`);i.default.mkdirSync(s,{recursive:!0});let u=D(o),d=n.default.resolve(r.outputDirectory??s),f=y(r.outputName??`doom-set`);i.default.mkdirSync(d,{recursive:!0});let p=(0,a.createHash)(`sha256`).update(d).update(f).digest(`hex`).slice(0,16),m=n.default.join(s,`${u}.${p}.json`),h=T(m);if(h&&w(h,o))return h.output;let g=new Set(o),_=O(o),{rolldown:v}=await import(`rolldown`),b=await v({input:l,platform:`node`,plugins:[{name:`doom-set-entry`,resolveId(e){return e===l?e:null},load(e){return e===l?_:null}},A(g),F(),j(g)],onLog:M}),x;try{let e=(await b.generate({format:`esm`,sourcemap:!1,minify:!0,codeSplitting:!1})).output.find(e=>e.type===`chunk`);if(!e||e.type!==`chunk`)throw Error(`Rolldown produced no extension bundle`);x=e.code}finally{await b.close()}let S=(0,a.createHash)(`sha256`).update(`v4`).update(x).digest(`hex`).slice(0,16),k=n.default.join(d,`${f}.${S}${c}`);i.default.existsSync(k)||E(k,x);let N=[...g].map(C).filter(e=>e!==void 0).sort((e,t)=>e.path.localeCompare(t.path));return E(m,`${JSON.stringify({version:`v4`,entries:o,output:k,inputs:N},null,2)}\n`),k}async function L(e,t){if(!d(e))return e;let r=n.default.join(t,m(e,p(e)));if(i.default.existsSync(r))return r;let{rolldown:a}=await import(`rolldown`),o=await a({input:e,platform:`node`,plugins:[S(),F()],onLog:M});try{await o.write({file:r,format:`esm`,sourcemap:!1,minify:!0,codeSplitting:!1})}finally{await o.close()}return r}async function R(e,t){i.default.mkdirSync(t,{recursive:!0});let n={};for(let[r,i]of Object.entries(e))try{n[r]=await L(i,t)}catch{n[r]=i}return n}exports.compileExtensionSet=I,exports.compileResolvedEntries=R,exports.extensionSetKey=D;
1
+ const e=require(`../_virtual/_rolldown/runtime.cjs`),t=require(`../utils/moduleResolution.cjs`);require(`../utils/index.cjs`);let n=require(`node:path`);n=e.__toESM(n,1);let r=require(`node:module`),i=require(`node:fs`);i=e.__toESM(i,1);let a=require(`node:crypto`),o=require(`node:url`);const s=[`.ts`,`.mts`,`.cts`],c=`.mjs`,l=`\0doompi-extension-set`,u=`@agimon-ai/doompi-ui/extensionName`,d={"@mariozechner/pi-coding-agent":`@earendil-works/pi-coding-agent`,"@mariozechner/pi-tui":`@earendil-works/pi-tui`,"@mariozechner/pi-ai":`@earendil-works/pi-ai`,"@sinclair/typebox":`typebox`};function f(e){return s.some(t=>e.endsWith(t))}function p(e){for(let[t,n]of Object.entries(d)){if(e===t)return n;if(e.startsWith(`${t}/`))return`${n}${e.slice(t.length)}`}return e}function m(e){let t=i.default.readFileSync(e);return(0,a.createHash)(`sha256`).update(`v3`).update(e).update(t).digest(`hex`).slice(0,16)}function h(e,t){let r=n.default.basename(e).replace(/\.[cm]?ts$/,``);return`${n.default.basename(n.default.dirname(e))}-${r}.${t}${c}`}const g=new Set([`@earendil-works/pi-coding-agent`,`@earendil-works/pi-tui`,`@earendil-works/pi-ai`]);function _(e){let n=t.optionalPackageEntry(`@earendil-works/pi-coding-agent`);if(n)try{return(0,r.createRequire)(n).resolve(e)}catch{return}}const v=new Set([`@parcel/watcher`,`@napi-rs/keyring`,`better-sqlite3`,`canvas`,`fsevents`,`isolated-vm`,`keytar`,`node-pty`,`onnxruntime-node`,`playwright`,`playwright-core`,`puppeteer`,`sharp`]),y=new Set(r.builtinModules.flatMap(e=>[e,`node:${e}`]));function b(e){return e.trim().replace(/[^a-zA-Z0-9._-]+/g,`-`).replace(/^[-.]+|[-.]+$/g,``)||`doom-set`}function x(e){let t=e.startsWith(`@`)?e.indexOf(`/`,e.indexOf(`/`)+1):e.indexOf(`/`);return t===-1?e:e.slice(0,t)}function S(e){let t=e.split(n.default.sep).join(`/`),r=t.lastIndexOf(`/node_modules/`);if(r===-1)return;let i=t.slice(r+14).split(`/`),a=i.lastIndexOf(`node_modules`),o=a===-1?i:i.slice(a+1);return o[0]?.startsWith(`@`)?o.slice(0,2).join(`/`):o[0]}function C(){return{name:`doom-external-absolute`,async resolveId(e,r,i){if(e.startsWith(`.`)||n.default.isAbsolute(e)||e.startsWith(`node:`))return null;let a=p(e),o=x(a);if(o&&g.has(o)){let e=t.optionalPackageEntry(a);if(e)return{id:e,external:!0}}let s=await this.resolve(a,r,i);return!s||f(s.id)?null:{id:s.id,external:!0}}}}function w(e){try{let t=i.default.statSync(e);return t.isFile()?{path:e,size:t.size,mtimeMs:t.mtimeMs}:void 0}catch{return}}function T(e,t){return e.version!==`v5`||JSON.stringify(e.entries)!==JSON.stringify(t)||!i.default.existsSync(e.output)?!1:e.inputs.every(e=>{let t=w(e.path);return t?.size===e.size&&t.mtimeMs===e.mtimeMs})}function E(e){try{let t=JSON.parse(i.default.readFileSync(e,`utf8`));return t&&Array.isArray(t.entries)&&Array.isArray(t.inputs)?t:void 0}catch{return}}function D(e,t){let n=`${e}.${process.pid}.${Date.now()}.tmp`;i.default.writeFileSync(n,t);try{i.default.renameSync(n,e)}catch(t){if(i.default.rmSync(n,{force:!0}),!i.default.existsSync(e))throw t}}function O(e){return(0,a.createHash)(`sha256`).update(`v5`).update(JSON.stringify(e.map(e=>n.default.resolve(e)))).digest(`hex`).slice(0,16)}function k(e){let n=t.optionalPackageEntry(u);if(!n)throw Error(`Cannot resolve ${u}`);let r=e.map(e=>`() => import(${JSON.stringify(A(e))})`),i=e.map(e=>JSON.stringify(e));return[`import { withExtensionSource } from ${JSON.stringify(A(n))};`,`const loaders = [${r.join(`,`)}];`,`const names = [${i.join(`,`)}];`,`function report(index, error) {`,` const detail = error instanceof Error ? error.message : String(error);`,` process.stderr.write("[doompi] " + names[index] + ": " + detail + "\\n");`,`}`,`export default async function doompiExtensionSet(pi) {`,` const loaded = await Promise.all(loaders.map(async (load, index) => {`,` try {`,` return await load();`,` } catch (error) {`,` report(index, error);`,` return undefined;`,` }`,` }));`,` for (let index = 0; index < loaded.length; index += 1) {`,` const extension = loaded[index];`,` if (!extension) continue;`,` try {`,` if (typeof extension.default !== 'function') throw new Error('does not export an extension factory');`,` await extension.default(withExtensionSource(pi, names[index]));`,` } catch (error) {`,` report(index, error);`,` }`,` }`,`}`].join(`
2
+ `)}function A(e){return n.default.resolve(e).split(n.default.sep).join(`/`)}function j(e){return{name:`doom-set-external`,async resolveId(r,i,a){if(r===l)return null;if(y.has(r)||r.startsWith(`node:`))return{id:r,external:!0};if(n.default.isAbsolute(r)){let t=S(r);return t&&(g.has(t)||v.has(t))?(e.add(r),{id:r,external:!0}):null}if(r.startsWith(`.`))return null;let o=p(r),s=x(o);if(s&&g.has(s)){let n=t.optionalPackageEntry(o);if(n)return e.add(n),{id:n,external:!0}}if(s===`typebox`){let t=_(o);if(t)return e.add(t),{id:t,external:!1}}if(s&&v.has(s)){let t=await this.resolve(o,i,a);if(t)return e.add(t.id),{id:t.id,external:!0}}return null}}}function M(e){return{name:`doom-set-inputs`,load(t){return n.default.isAbsolute(t)&&i.default.existsSync(t)&&e.add(t),null}}}function N(e,t,n){t.code===`UNRESOLVED_IMPORT`&&n(`error`,t)}function P(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function F(e,t){if(Array.isArray(e)){for(let n of e)F(n,t);return}if(!P(e))return;let n=e.object,r=e.property;if(e.type===`MemberExpression`&&e.computed===!1&&typeof e.start==`number`&&typeof e.end==`number`&&P(n)&&n.type===`MetaProperty`&&P(n.meta)&&n.meta.name===`import`&&P(n.property)&&n.property.name===`meta`&&P(r)&&r.name===`url`){t.push({start:e.start,end:e.end});return}for(let n of Object.values(e))F(n,t)}function I(){return{name:`doom-original-import-meta-url`,transform(e,t){if(!n.default.isAbsolute(t)||!e.includes(`import.meta.url`))return null;let r=n.default.extname(t).toLowerCase(),i=r===`.tsx`?`tsx`:r===`.jsx`?`jsx`:f(t)?`ts`:`js`,a=[];if(F(this.parse(e,{lang:i}),a),a.length===0)return null;let s=JSON.stringify((0,o.pathToFileURL)(t).href),c=e;for(let e of a.sort((e,t)=>t.start-e.start))c=`${c.slice(0,e.start)}${s}${c.slice(e.end)}`;return c}}}async function L(e,t,r={}){if(e.length===0)throw Error(`Cannot compile an empty extension set`);let o=e.map(e=>n.default.resolve(e)),s=n.default.join(t,`sets`);i.default.mkdirSync(s,{recursive:!0});let u=O(o),d=n.default.resolve(r.outputDirectory??s),f=b(r.outputName??`doom-set`);i.default.mkdirSync(d,{recursive:!0});let p=(0,a.createHash)(`sha256`).update(d).update(f).digest(`hex`).slice(0,16),m=n.default.join(s,`${u}.${p}.json`),h=E(m);if(h&&T(h,o))return h.output;let g=new Set(o),_=k(o),{rolldown:v}=await import(`rolldown`),y=await v({input:l,platform:`node`,plugins:[{name:`doom-set-entry`,resolveId(e){return e===l?e:null},load(e){return e===l?_:null}},j(g),I(),M(g)],onLog:N}),x;try{let e=(await y.generate({format:`esm`,sourcemap:!1,minify:!0,codeSplitting:!1})).output.find(e=>e.type===`chunk`);if(!e||e.type!==`chunk`)throw Error(`Rolldown produced no extension bundle`);x=e.code}finally{await y.close()}let S=(0,a.createHash)(`sha256`).update(`v5`).update(x).digest(`hex`).slice(0,16),C=n.default.join(d,`${f}.${S}${c}`);i.default.existsSync(C)||D(C,x);let A=[...g].map(w).filter(e=>e!==void 0).sort((e,t)=>e.path.localeCompare(t.path));return D(m,`${JSON.stringify({version:`v5`,entries:o,output:C,inputs:A},null,2)}\n`),C}async function R(e,t){if(!f(e))return e;let r=n.default.join(t,h(e,m(e)));if(i.default.existsSync(r))return r;let{rolldown:a}=await import(`rolldown`),o=await a({input:e,platform:`node`,plugins:[C(),I()],onLog:N});try{await o.write({file:r,format:`esm`,sourcemap:!1,minify:!0,codeSplitting:!1})}finally{await o.close()}return r}async function z(e,t){i.default.mkdirSync(t,{recursive:!0});let n={};for(let[r,i]of Object.entries(e))try{n[r]=await R(i,t)}catch{n[r]=i}return n}exports.compileExtensionSet=L,exports.compileResolvedEntries=z,exports.extensionSetKey=O;
3
3
  //# sourceMappingURL=extensionCompiler.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extensionCompiler.cjs","names":["fs","path","optionalPackageEntry","builtinModules"],"sources":["../../src/services/extensionCompiler.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { builtinModules, createRequire } from 'node:module';\nimport { pathToFileURL } from 'node:url';\nimport type { LogLevel, LogOrStringHandler, RolldownLog } from 'rolldown';\nimport { optionalPackageEntry } from '../utils/index.ts';\n\n/**\n * Compiles TypeScript extensions into plain ESM ahead of the session.\n *\n * Pi loads extensions through jiti, which transpiles TypeScript at import time.\n * That works, but it costs a transform per module on a cold cache and makes Node\n * complain about type stripping under node_modules on every launch. Resolution\n * already knows the full extension list, so each TypeScript entry is bundled to\n * a single `.mjs` here and Pi imports it natively.\n *\n * Mode artifacts bundle their JavaScript dependencies. Node built-ins, native\n * packages, and Pi's shared runtime stay external; those imports are rewritten\n * to absolute paths so an artifact under `.pi/doom/dist` never relies on that\n * directory having its own `node_modules` tree.\n */\n\nconst TYPESCRIPT_SUFFIXES = ['.ts', '.mts', '.cts'] as const;\nconst COMPILED_SUFFIX = '.mjs';\nconst CACHE_VERSION = 'v3';\nconst SET_CACHE_VERSION = 'v4';\nconst HASH_LENGTH = 16;\nconst SET_DIRECTORY = 'sets';\nconst MANIFEST_SUFFIX = '.json';\nconst VIRTUAL_SET_ENTRY = '\\0doompi-extension-set';\n\n/** Pi's former npm scope. The packages are the same, published under a new name. */\nconst PI_SCOPE_RENAMES: Readonly<Record<string, string>> = {\n '@mariozechner/pi-coding-agent': '@earendil-works/pi-coding-agent',\n '@mariozechner/pi-tui': '@earendil-works/pi-tui',\n '@mariozechner/pi-ai': '@earendil-works/pi-ai',\n '@sinclair/typebox': 'typebox',\n};\n\nexport function isTypeScriptEntry(entry: string): boolean {\n return TYPESCRIPT_SUFFIXES.some((suffix) => entry.endsWith(suffix));\n}\n\nfunction renameScope(specifier: string): string {\n for (const [before, after] of Object.entries(PI_SCOPE_RENAMES)) {\n if (specifier === before) return after;\n if (specifier.startsWith(`${before}/`)) return `${after}${specifier.slice(before.length)}`;\n }\n return specifier;\n}\n\n/**\n * Cache key for a compiled entry.\n *\n * The entry's own bytes are the only cheap signal that the extension changed; a\n * dependency edit is not visible without walking the graph, so an upgrade that\n * leaves the entry byte-identical is served from cache until the version below\n * is raised.\n */\nfunction cacheKey(entry: string): string {\n const source = fs.readFileSync(entry);\n return createHash('sha256').update(CACHE_VERSION).update(entry).update(source).digest('hex').slice(0, HASH_LENGTH);\n}\n\nfunction compiledName(entry: string, key: string): string {\n const base = path.basename(entry).replace(/\\.[cm]?ts$/, '');\n const parent = path.basename(path.dirname(entry));\n return `${parent}-${base}.${key}${COMPILED_SUFFIX}`;\n}\n\n/**\n * Packages that must resolve to the copy this harness runs, never to a second\n * one bundled into an extension. Pi owns the TUI and the session state, so a\n * duplicate renders into a different screen and mutates a different store.\n */\nconst HOSTED_PACKAGES = new Set(['@earendil-works/pi-coding-agent', '@earendil-works/pi-tui', '@earendil-works/pi-ai']);\n\n/**\n * Resolves packages Pi exposes to extensions as compatibility aliases.\n *\n * Extensions do not need to declare TypeBox themselves because Pi's loader\n * provides it as a virtual module. A dist bundle bypasses that loader, so it\n * resolves the same package against Pi's dependency tree and bundles it.\n */\nfunction piExtensionDependencyEntry(specifier: string): string | undefined {\n const piEntry = optionalPackageEntry('@earendil-works/pi-coding-agent');\n if (!piEntry) return undefined;\n try {\n return createRequire(piEntry).resolve(specifier);\n } catch {\n return undefined;\n }\n}\n\n/** Packages whose native binaries or runtime discovery cannot be rolled up. */\nconst NATIVE_PACKAGES = new Set([\n '@parcel/watcher',\n '@napi-rs/keyring',\n 'better-sqlite3',\n 'canvas',\n 'fsevents',\n 'isolated-vm',\n 'keytar',\n 'node-pty',\n 'onnxruntime-node',\n 'playwright',\n 'playwright-core',\n 'puppeteer',\n 'sharp',\n]);\nconst NODE_BUILTINS = new Set(builtinModules.flatMap((name) => [name, `node:${name}`]));\n\ninterface InputFingerprint {\n path: string;\n size: number;\n mtimeMs: number;\n}\n\ninterface ExtensionSetManifest {\n version: string;\n entries: string[];\n output: string;\n inputs: InputFingerprint[];\n}\n\nexport interface CompileExtensionSetOptions {\n /** Persistent artifact directory. Compiler manifests remain in cacheDirectory. */\n outputDirectory?: string;\n /** Human-readable artifact prefix, normally the major-mode name. */\n outputName?: string;\n}\n\nfunction safeOutputName(value: string): string {\n const normalized = value\n .trim()\n .replace(/[^a-zA-Z0-9._-]+/g, '-')\n .replace(/^[-.]+|[-.]+$/g, '');\n return normalized || 'doom-set';\n}\n\nfunction packageRootOf(specifier: string): string | undefined {\n const slash = specifier.startsWith('@') ? specifier.indexOf('/', specifier.indexOf('/') + 1) : specifier.indexOf('/');\n return slash === -1 ? specifier : specifier.slice(0, slash);\n}\n\nfunction packageRootFromPath(target: string): string | undefined {\n const normalized = target.split(path.sep).join('/');\n const marker = '/node_modules/';\n const offset = normalized.lastIndexOf(marker);\n if (offset === -1) return undefined;\n const parts = normalized.slice(offset + marker.length).split('/');\n // pnpm's first segment is a store key; the second node_modules marker names\n // the package that owns the file.\n const nested = parts.lastIndexOf('node_modules');\n const packageParts = nested === -1 ? parts : parts.slice(nested + 1);\n return packageParts[0]?.startsWith('@') ? packageParts.slice(0, 2).join('/') : packageParts[0];\n}\n\nfunction externalResolver() {\n return {\n name: 'doom-external-absolute',\n async resolveId(\n this: { resolve: (id: string, importer?: string, options?: unknown) => Promise<{ id: string } | null> },\n specifier: string,\n importer: string | undefined,\n options: unknown,\n ): Promise<{ id: string; external: boolean } | null> {\n if (specifier.startsWith('.') || path.isAbsolute(specifier) || specifier.startsWith('node:')) return null;\n const renamed = renameScope(specifier);\n\n // Host packages are resolved from this module, the way the rest of the\n // harness resolves them, because an extension written against Pi's former\n // scope does not have the current one in its own dependency tree.\n const root = packageRootOf(renamed);\n if (root && HOSTED_PACKAGES.has(root)) {\n const hosted = optionalPackageEntry(renamed);\n if (hosted) return { id: hosted, external: true };\n }\n\n // Rolldown's resolver rather than `createRequire`, which cannot see an\n // ESM-only package that publishes no `require` condition and would send\n // Pi's own core into the bundle.\n const resolved = await this.resolve(renamed, importer, options);\n if (!resolved) return null;\n // A dependency that is itself TypeScript cannot be handed to Node either,\n // so it is bundled rather than externalised.\n if (isTypeScriptEntry(resolved.id)) return null;\n return { id: resolved.id, external: true };\n },\n };\n}\n\nfunction fingerprint(target: string): InputFingerprint | undefined {\n try {\n const stat = fs.statSync(target);\n if (!stat.isFile()) return undefined;\n return { path: target, size: stat.size, mtimeMs: stat.mtimeMs };\n } catch {\n return undefined;\n }\n}\n\nfunction manifestIsFresh(manifest: ExtensionSetManifest, entries: string[]): boolean {\n if (\n manifest.version !== SET_CACHE_VERSION ||\n JSON.stringify(manifest.entries) !== JSON.stringify(entries) ||\n !fs.existsSync(manifest.output)\n ) {\n return false;\n }\n return manifest.inputs.every((previous) => {\n const current = fingerprint(previous.path);\n return current?.size === previous.size && current.mtimeMs === previous.mtimeMs;\n });\n}\n\nfunction readSetManifest(manifestPath: string): ExtensionSetManifest | undefined {\n try {\n const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as ExtensionSetManifest;\n return parsed && Array.isArray(parsed.entries) && Array.isArray(parsed.inputs) ? parsed : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction writeAtomic(target: string, contents: string): void {\n const temporary = `${target}.${process.pid}.${Date.now()}.tmp`;\n fs.writeFileSync(temporary, contents);\n try {\n fs.renameSync(temporary, target);\n } catch (error) {\n fs.rmSync(temporary, { force: true });\n if (!fs.existsSync(target)) throw error;\n }\n}\n\n/** Stable lookup key for one exact Pi load order. */\nexport function extensionSetKey(entries: readonly string[]): string {\n return createHash('sha256')\n .update(SET_CACHE_VERSION)\n .update(JSON.stringify(entries.map((entry) => path.resolve(entry))))\n .digest('hex')\n .slice(0, HASH_LENGTH);\n}\n\nfunction extensionSetSource(entries: readonly string[]): string {\n const loaders = entries.map((entry) => `() => import(${JSON.stringify(pathToImportSpecifier(entry))})`);\n const names = entries.map((entry) => JSON.stringify(entry));\n return [\n `const loaders = [${loaders.join(',')}];`,\n `const names = [${names.join(',')}];`,\n 'function report(index, error) {',\n ' const detail = error instanceof Error ? error.message : String(error);',\n ' process.stderr.write(`[doompi] ${names[index]}: ${detail}\\\\n`);',\n '}',\n 'export default async function doompiExtensionSet(pi) {',\n ' const loaded = await Promise.all(loaders.map(async (load, index) => {',\n ' try {',\n ' return await load();',\n ' } catch (error) {',\n ' report(index, error);',\n ' return undefined;',\n ' }',\n ' }));',\n ' for (let index = 0; index < loaded.length; index += 1) {',\n ' const extension = loaded[index];',\n ' if (!extension) continue;',\n ' try {',\n \" if (typeof extension.default !== 'function') throw new Error('does not export an extension factory');\",\n ' await extension.default(pi);',\n ' } catch (error) {',\n ' report(index, error);',\n ' }',\n ' }',\n '}',\n ].join('\\n');\n}\n\nfunction pathToImportSpecifier(target: string): string {\n // Rolldown accepts absolute POSIX paths directly. Windows needs a file URL,\n // but replacing separators is enough for its platform-independent parser.\n return path.resolve(target).split(path.sep).join('/');\n}\n\nfunction setExternalResolver(inputs: Set<string>) {\n return {\n name: 'doom-set-external',\n async resolveId(\n this: { resolve: (id: string, importer?: string, options?: unknown) => Promise<{ id: string } | null> },\n specifier: string,\n importer: string | undefined,\n options: unknown,\n ): Promise<{ id: string; external: boolean } | null> {\n if (specifier === VIRTUAL_SET_ENTRY) return null;\n if (NODE_BUILTINS.has(specifier) || specifier.startsWith('node:')) {\n return { id: specifier, external: true };\n }\n\n if (path.isAbsolute(specifier)) {\n const root = packageRootFromPath(specifier);\n if (root && (HOSTED_PACKAGES.has(root) || NATIVE_PACKAGES.has(root))) {\n inputs.add(specifier);\n return { id: specifier, external: true };\n }\n return null;\n }\n if (specifier.startsWith('.')) return null;\n\n const renamed = renameScope(specifier);\n const root = packageRootOf(renamed);\n if (root && HOSTED_PACKAGES.has(root)) {\n const hosted = optionalPackageEntry(renamed);\n if (hosted) {\n inputs.add(hosted);\n return { id: hosted, external: true };\n }\n }\n if (root === 'typebox') {\n const typebox = piExtensionDependencyEntry(renamed);\n if (typebox) {\n inputs.add(typebox);\n return { id: typebox, external: false };\n }\n }\n if (root && NATIVE_PACKAGES.has(root)) {\n const resolved = await this.resolve(renamed, importer, options);\n if (resolved) {\n inputs.add(resolved.id);\n return { id: resolved.id, external: true };\n }\n }\n return null;\n },\n };\n}\n\nfunction inputCollector(inputs: Set<string>) {\n return {\n name: 'doom-set-inputs',\n load(id: string): null {\n if (path.isAbsolute(id) && fs.existsSync(id)) inputs.add(id);\n return null;\n },\n };\n}\n\n/** An unresolved bare import would otherwise become a broken dist external. */\nfunction failUnresolvedImport(_level: LogLevel, log: RolldownLog, handler: LogOrStringHandler): void {\n if (log.code === 'UNRESOLVED_IMPORT') handler('error', log);\n}\n\ninterface SourceRange {\n start: number;\n end: number;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction collectImportMetaUrlRanges(value: unknown, ranges: SourceRange[]): void {\n if (Array.isArray(value)) {\n for (const item of value) collectImportMetaUrlRanges(item, ranges);\n return;\n }\n if (!isRecord(value)) return;\n\n const object = value.object;\n const property = value.property;\n if (\n value.type === 'MemberExpression' &&\n value.computed === false &&\n typeof value.start === 'number' &&\n typeof value.end === 'number' &&\n isRecord(object) &&\n object.type === 'MetaProperty' &&\n isRecord(object.meta) &&\n object.meta.name === 'import' &&\n isRecord(object.property) &&\n object.property.name === 'meta' &&\n isRecord(property) &&\n property.name === 'url'\n ) {\n ranges.push({ start: value.start, end: value.end });\n return;\n }\n\n for (const child of Object.values(value)) collectImportMetaUrlRanges(child, ranges);\n}\n\n/** Keeps package-owned resource lookup anchored to each original source file. */\nfunction preserveImportMetaUrl() {\n return {\n name: 'doom-original-import-meta-url',\n transform(\n this: {\n parse: (source: string, options?: { lang?: 'js' | 'jsx' | 'ts' | 'tsx' }) => unknown;\n },\n source: string,\n id: string,\n ): string | null {\n if (!path.isAbsolute(id) || !source.includes('import.meta.url')) return null;\n const extension = path.extname(id).toLowerCase();\n const lang = extension === '.tsx' ? 'tsx' : extension === '.jsx' ? 'jsx' : isTypeScriptEntry(id) ? 'ts' : 'js';\n const ranges: SourceRange[] = [];\n collectImportMetaUrlRanges(this.parse(source, { lang }), ranges);\n if (ranges.length === 0) return null;\n const replacement = JSON.stringify(pathToFileURL(id).href);\n let transformed = source;\n for (const range of ranges.sort((left, right) => right.start - left.start)) {\n transformed = `${transformed.slice(0, range.start)}${replacement}${transformed.slice(range.end)}`;\n }\n return transformed;\n },\n };\n}\n\n/**\n * Compiles an ordered extension set into one native ESM entry.\n *\n * The generated entry starts every module load together, then invokes factories\n * sequentially in activation order. Its manifest records every loaded module,\n * so a dependency edit invalidates the cache without running the compiler.\n */\nexport async function compileExtensionSet(\n entries: readonly string[],\n cacheDirectory: string,\n options: CompileExtensionSetOptions = {},\n): Promise<string> {\n if (entries.length === 0) throw new Error('Cannot compile an empty extension set');\n const normalized = entries.map((entry) => path.resolve(entry));\n const setDirectory = path.join(cacheDirectory, SET_DIRECTORY);\n fs.mkdirSync(setDirectory, { recursive: true });\n const key = extensionSetKey(normalized);\n const outputDirectory = path.resolve(options.outputDirectory ?? setDirectory);\n const outputName = safeOutputName(options.outputName ?? 'doom-set');\n fs.mkdirSync(outputDirectory, { recursive: true });\n // One graph can be emitted under several named mode artifacts. Keep their\n // cache records separate without changing extensionSetKey, which is the\n // runtime lookup key for an exact activation order.\n const outputKey = createHash('sha256').update(outputDirectory).update(outputName).digest('hex').slice(0, HASH_LENGTH);\n const manifestPath = path.join(setDirectory, `${key}.${outputKey}${MANIFEST_SUFFIX}`);\n const previous = readSetManifest(manifestPath);\n if (previous && manifestIsFresh(previous, normalized)) return previous.output;\n\n const inputs = new Set(normalized);\n const source = extensionSetSource(normalized);\n const { rolldown } = await import('rolldown');\n const build = await rolldown({\n input: VIRTUAL_SET_ENTRY,\n platform: 'node',\n plugins: [\n {\n name: 'doom-set-entry',\n resolveId(id: string): string | null {\n return id === VIRTUAL_SET_ENTRY ? id : null;\n },\n load(id: string): string | null {\n return id === VIRTUAL_SET_ENTRY ? source : null;\n },\n },\n setExternalResolver(inputs),\n preserveImportMetaUrl(),\n inputCollector(inputs),\n ],\n onLog: failUnresolvedImport,\n });\n let code: string;\n try {\n const generated = await build.generate({ format: 'esm', sourcemap: false, minify: true, codeSplitting: false });\n const chunk = generated.output.find((output) => output.type === 'chunk');\n if (!chunk || chunk.type !== 'chunk') throw new Error('Rolldown produced no extension bundle');\n code = chunk.code;\n } finally {\n await build.close();\n }\n\n const contentKey = createHash('sha256').update(SET_CACHE_VERSION).update(code).digest('hex').slice(0, HASH_LENGTH);\n const output = path.join(outputDirectory, `${outputName}.${contentKey}${COMPILED_SUFFIX}`);\n if (!fs.existsSync(output)) writeAtomic(output, code);\n const inputFingerprints = [...inputs]\n .map(fingerprint)\n .filter((value): value is InputFingerprint => value !== undefined)\n .sort((left, right) => left.path.localeCompare(right.path));\n writeAtomic(\n manifestPath,\n `${JSON.stringify({ version: SET_CACHE_VERSION, entries: normalized, output, inputs: inputFingerprints }, null, 2)}\\n`,\n );\n return output;\n}\n\n/**\n * Bundles `entry` into `cacheDirectory` and returns the compiled path.\n *\n * Non-TypeScript entries are returned untouched. A cache hit skips the build.\n */\nexport async function compileExtension(entry: string, cacheDirectory: string): Promise<string> {\n if (!isTypeScriptEntry(entry)) return entry;\n\n const output = path.join(cacheDirectory, compiledName(entry, cacheKey(entry)));\n if (fs.existsSync(output)) return output;\n\n // Rolldown loads a native module and a sizeable parser graph. Sync is the\n // only command that needs it, and a cache hit does not, so keep it off every\n // other CLI startup path.\n const { rolldown } = await import('rolldown');\n const build = await rolldown({\n input: entry,\n platform: 'node',\n plugins: [externalResolver(), preserveImportMetaUrl()],\n onLog: failUnresolvedImport,\n });\n try {\n // One file per extension. Several of them import lazily, and code splitting\n // would emit sibling chunks that a single output path cannot describe.\n await build.write({ file: output, format: 'esm', sourcemap: false, minify: true, codeSplitting: false });\n } finally {\n await build.close();\n }\n return output;\n}\n\n/**\n * Compiles every TypeScript entry in a resolution map, leaving the rest as-is.\n *\n * A failure is not fatal: the original path is kept so Pi falls back to loading\n * the TypeScript through jiti, which is what it did before this existed.\n */\nexport async function compileResolvedEntries(\n resolved: Record<string, string>,\n cacheDirectory: string,\n): Promise<Record<string, string>> {\n fs.mkdirSync(cacheDirectory, { recursive: true });\n const compiled: Record<string, string> = {};\n for (const [key, target] of Object.entries(resolved)) {\n try {\n compiled[key] = await compileExtension(target, cacheDirectory);\n } catch {\n compiled[key] = target;\n }\n }\n return compiled;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"extensionCompiler.cjs","names":["fs","path","optionalPackageEntry","builtinModules"],"sources":["../../src/services/extensionCompiler.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport { builtinModules, createRequire } from 'node:module';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { LogLevel, LogOrStringHandler, RolldownLog } from 'rolldown';\nimport { optionalPackageEntry } from '../utils/index.ts';\n\n/**\n * Compiles TypeScript extensions into plain ESM ahead of the session.\n *\n * Pi loads extensions through jiti, which transpiles TypeScript at import time.\n * That works, but it costs a transform per module on a cold cache and makes Node\n * complain about type stripping under node_modules on every launch. Resolution\n * already knows the full extension list, so each TypeScript entry is bundled to\n * a single `.mjs` here and Pi imports it natively.\n *\n * Mode artifacts bundle their JavaScript dependencies. Node built-ins, native\n * packages, and Pi's shared runtime stay external; those imports are rewritten\n * to absolute paths so an artifact under `.pi/doom/dist` never relies on that\n * directory having its own `node_modules` tree.\n */\n\nconst TYPESCRIPT_SUFFIXES = ['.ts', '.mts', '.cts'] as const;\nconst COMPILED_SUFFIX = '.mjs';\nconst CACHE_VERSION = 'v3';\nconst SET_CACHE_VERSION = 'v5';\nconst HASH_LENGTH = 16;\nconst SET_DIRECTORY = 'sets';\nconst MANIFEST_SUFFIX = '.json';\nconst VIRTUAL_SET_ENTRY = '\\0doompi-extension-set';\nconst EXTENSION_SOURCE_HELPER = '@agimon-ai/doompi-ui/extensionName';\n\n/** Pi's former npm scope. The packages are the same, published under a new name. */\nconst PI_SCOPE_RENAMES: Readonly<Record<string, string>> = {\n '@mariozechner/pi-coding-agent': '@earendil-works/pi-coding-agent',\n '@mariozechner/pi-tui': '@earendil-works/pi-tui',\n '@mariozechner/pi-ai': '@earendil-works/pi-ai',\n '@sinclair/typebox': 'typebox',\n};\n\nexport function isTypeScriptEntry(entry: string): boolean {\n return TYPESCRIPT_SUFFIXES.some((suffix) => entry.endsWith(suffix));\n}\n\nfunction renameScope(specifier: string): string {\n for (const [before, after] of Object.entries(PI_SCOPE_RENAMES)) {\n if (specifier === before) return after;\n if (specifier.startsWith(`${before}/`)) return `${after}${specifier.slice(before.length)}`;\n }\n return specifier;\n}\n\n/**\n * Cache key for a compiled entry.\n *\n * The entry's own bytes are the only cheap signal that the extension changed; a\n * dependency edit is not visible without walking the graph, so an upgrade that\n * leaves the entry byte-identical is served from cache until the version below\n * is raised.\n */\nfunction cacheKey(entry: string): string {\n const source = fs.readFileSync(entry);\n return createHash('sha256').update(CACHE_VERSION).update(entry).update(source).digest('hex').slice(0, HASH_LENGTH);\n}\n\nfunction compiledName(entry: string, key: string): string {\n const base = path.basename(entry).replace(/\\.[cm]?ts$/, '');\n const parent = path.basename(path.dirname(entry));\n return `${parent}-${base}.${key}${COMPILED_SUFFIX}`;\n}\n\n/**\n * Packages that must resolve to the copy this harness runs, never to a second\n * one bundled into an extension. Pi owns the TUI and the session state, so a\n * duplicate renders into a different screen and mutates a different store.\n */\nconst HOSTED_PACKAGES = new Set(['@earendil-works/pi-coding-agent', '@earendil-works/pi-tui', '@earendil-works/pi-ai']);\n\n/**\n * Resolves packages Pi exposes to extensions as compatibility aliases.\n *\n * Extensions do not need to declare TypeBox themselves because Pi's loader\n * provides it as a virtual module. A dist bundle bypasses that loader, so it\n * resolves the same package against Pi's dependency tree and bundles it.\n */\nfunction piExtensionDependencyEntry(specifier: string): string | undefined {\n const piEntry = optionalPackageEntry('@earendil-works/pi-coding-agent');\n if (!piEntry) return undefined;\n try {\n return createRequire(piEntry).resolve(specifier);\n } catch {\n return undefined;\n }\n}\n\n/** Packages whose native binaries or runtime discovery cannot be rolled up. */\nconst NATIVE_PACKAGES = new Set([\n '@parcel/watcher',\n '@napi-rs/keyring',\n 'better-sqlite3',\n 'canvas',\n 'fsevents',\n 'isolated-vm',\n 'keytar',\n 'node-pty',\n 'onnxruntime-node',\n 'playwright',\n 'playwright-core',\n 'puppeteer',\n 'sharp',\n]);\nconst NODE_BUILTINS = new Set(builtinModules.flatMap((name) => [name, `node:${name}`]));\n\ninterface InputFingerprint {\n path: string;\n size: number;\n mtimeMs: number;\n}\n\ninterface ExtensionSetManifest {\n version: string;\n entries: string[];\n output: string;\n inputs: InputFingerprint[];\n}\n\nexport interface CompileExtensionSetOptions {\n /** Persistent artifact directory. Compiler manifests remain in cacheDirectory. */\n outputDirectory?: string;\n /** Human-readable artifact prefix, normally the major-mode name. */\n outputName?: string;\n}\n\nfunction safeOutputName(value: string): string {\n const normalized = value\n .trim()\n .replace(/[^a-zA-Z0-9._-]+/g, '-')\n .replace(/^[-.]+|[-.]+$/g, '');\n return normalized || 'doom-set';\n}\n\nfunction packageRootOf(specifier: string): string | undefined {\n const slash = specifier.startsWith('@') ? specifier.indexOf('/', specifier.indexOf('/') + 1) : specifier.indexOf('/');\n return slash === -1 ? specifier : specifier.slice(0, slash);\n}\n\nfunction packageRootFromPath(target: string): string | undefined {\n const normalized = target.split(path.sep).join('/');\n const marker = '/node_modules/';\n const offset = normalized.lastIndexOf(marker);\n if (offset === -1) return undefined;\n const parts = normalized.slice(offset + marker.length).split('/');\n // pnpm's first segment is a store key; the second node_modules marker names\n // the package that owns the file.\n const nested = parts.lastIndexOf('node_modules');\n const packageParts = nested === -1 ? parts : parts.slice(nested + 1);\n return packageParts[0]?.startsWith('@') ? packageParts.slice(0, 2).join('/') : packageParts[0];\n}\n\nfunction externalResolver() {\n return {\n name: 'doom-external-absolute',\n async resolveId(\n this: { resolve: (id: string, importer?: string, options?: unknown) => Promise<{ id: string } | null> },\n specifier: string,\n importer: string | undefined,\n options: unknown,\n ): Promise<{ id: string; external: boolean } | null> {\n if (specifier.startsWith('.') || path.isAbsolute(specifier) || specifier.startsWith('node:')) return null;\n const renamed = renameScope(specifier);\n\n // Host packages are resolved from this module, the way the rest of the\n // harness resolves them, because an extension written against Pi's former\n // scope does not have the current one in its own dependency tree.\n const root = packageRootOf(renamed);\n if (root && HOSTED_PACKAGES.has(root)) {\n const hosted = optionalPackageEntry(renamed);\n if (hosted) return { id: hosted, external: true };\n }\n\n // Rolldown's resolver rather than `createRequire`, which cannot see an\n // ESM-only package that publishes no `require` condition and would send\n // Pi's own core into the bundle.\n const resolved = await this.resolve(renamed, importer, options);\n if (!resolved) return null;\n // A dependency that is itself TypeScript cannot be handed to Node either,\n // so it is bundled rather than externalised.\n if (isTypeScriptEntry(resolved.id)) return null;\n return { id: resolved.id, external: true };\n },\n };\n}\n\nfunction fingerprint(target: string): InputFingerprint | undefined {\n try {\n const stat = fs.statSync(target);\n if (!stat.isFile()) return undefined;\n return { path: target, size: stat.size, mtimeMs: stat.mtimeMs };\n } catch {\n return undefined;\n }\n}\n\nfunction manifestIsFresh(manifest: ExtensionSetManifest, entries: string[]): boolean {\n if (\n manifest.version !== SET_CACHE_VERSION ||\n JSON.stringify(manifest.entries) !== JSON.stringify(entries) ||\n !fs.existsSync(manifest.output)\n ) {\n return false;\n }\n return manifest.inputs.every((previous) => {\n const current = fingerprint(previous.path);\n return current?.size === previous.size && current.mtimeMs === previous.mtimeMs;\n });\n}\n\nfunction readSetManifest(manifestPath: string): ExtensionSetManifest | undefined {\n try {\n const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as ExtensionSetManifest;\n return parsed && Array.isArray(parsed.entries) && Array.isArray(parsed.inputs) ? parsed : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction writeAtomic(target: string, contents: string): void {\n const temporary = `${target}.${process.pid}.${Date.now()}.tmp`;\n fs.writeFileSync(temporary, contents);\n try {\n fs.renameSync(temporary, target);\n } catch (error) {\n fs.rmSync(temporary, { force: true });\n if (!fs.existsSync(target)) throw error;\n }\n}\n\n/** Stable lookup key for one exact Pi load order. */\nexport function extensionSetKey(entries: readonly string[]): string {\n return createHash('sha256')\n .update(SET_CACHE_VERSION)\n .update(JSON.stringify(entries.map((entry) => path.resolve(entry))))\n .digest('hex')\n .slice(0, HASH_LENGTH);\n}\n\nfunction extensionSetSource(entries: readonly string[]): string {\n const helper = optionalPackageEntry(EXTENSION_SOURCE_HELPER);\n if (!helper) throw new Error(`Cannot resolve ${EXTENSION_SOURCE_HELPER}`);\n const loaders = entries.map((entry) => `() => import(${JSON.stringify(pathToImportSpecifier(entry))})`);\n const names = entries.map((entry) => JSON.stringify(entry));\n return [\n `import { withExtensionSource } from ${JSON.stringify(pathToImportSpecifier(helper))};`,\n `const loaders = [${loaders.join(',')}];`,\n `const names = [${names.join(',')}];`,\n 'function report(index, error) {',\n ' const detail = error instanceof Error ? error.message : String(error);',\n ' process.stderr.write(\"[doompi] \" + names[index] + \": \" + detail + \"\\\\n\");',\n '}',\n 'export default async function doompiExtensionSet(pi) {',\n ' const loaded = await Promise.all(loaders.map(async (load, index) => {',\n ' try {',\n ' return await load();',\n ' } catch (error) {',\n ' report(index, error);',\n ' return undefined;',\n ' }',\n ' }));',\n ' for (let index = 0; index < loaded.length; index += 1) {',\n ' const extension = loaded[index];',\n ' if (!extension) continue;',\n ' try {',\n \" if (typeof extension.default !== 'function') throw new Error('does not export an extension factory');\",\n ' await extension.default(withExtensionSource(pi, names[index]));',\n ' } catch (error) {',\n ' report(index, error);',\n ' }',\n ' }',\n '}',\n ].join('\\n');\n}\n\nfunction pathToImportSpecifier(target: string): string {\n // Rolldown accepts absolute POSIX paths directly. Windows needs a file URL,\n // but replacing separators is enough for its platform-independent parser.\n return path.resolve(target).split(path.sep).join('/');\n}\n\nfunction setExternalResolver(inputs: Set<string>) {\n return {\n name: 'doom-set-external',\n async resolveId(\n this: { resolve: (id: string, importer?: string, options?: unknown) => Promise<{ id: string } | null> },\n specifier: string,\n importer: string | undefined,\n options: unknown,\n ): Promise<{ id: string; external: boolean } | null> {\n if (specifier === VIRTUAL_SET_ENTRY) return null;\n if (NODE_BUILTINS.has(specifier) || specifier.startsWith('node:')) {\n return { id: specifier, external: true };\n }\n\n if (path.isAbsolute(specifier)) {\n const root = packageRootFromPath(specifier);\n if (root && (HOSTED_PACKAGES.has(root) || NATIVE_PACKAGES.has(root))) {\n inputs.add(specifier);\n return { id: specifier, external: true };\n }\n return null;\n }\n if (specifier.startsWith('.')) return null;\n\n const renamed = renameScope(specifier);\n const root = packageRootOf(renamed);\n if (root && HOSTED_PACKAGES.has(root)) {\n const hosted = optionalPackageEntry(renamed);\n if (hosted) {\n inputs.add(hosted);\n return { id: hosted, external: true };\n }\n }\n if (root === 'typebox') {\n const typebox = piExtensionDependencyEntry(renamed);\n if (typebox) {\n inputs.add(typebox);\n return { id: typebox, external: false };\n }\n }\n if (root && NATIVE_PACKAGES.has(root)) {\n const resolved = await this.resolve(renamed, importer, options);\n if (resolved) {\n inputs.add(resolved.id);\n return { id: resolved.id, external: true };\n }\n }\n return null;\n },\n };\n}\n\nfunction inputCollector(inputs: Set<string>) {\n return {\n name: 'doom-set-inputs',\n load(id: string): null {\n if (path.isAbsolute(id) && fs.existsSync(id)) inputs.add(id);\n return null;\n },\n };\n}\n\n/** An unresolved bare import would otherwise become a broken dist external. */\nfunction failUnresolvedImport(_level: LogLevel, log: RolldownLog, handler: LogOrStringHandler): void {\n if (log.code === 'UNRESOLVED_IMPORT') handler('error', log);\n}\n\ninterface SourceRange {\n start: number;\n end: number;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction collectImportMetaUrlRanges(value: unknown, ranges: SourceRange[]): void {\n if (Array.isArray(value)) {\n for (const item of value) collectImportMetaUrlRanges(item, ranges);\n return;\n }\n if (!isRecord(value)) return;\n\n const object = value.object;\n const property = value.property;\n if (\n value.type === 'MemberExpression' &&\n value.computed === false &&\n typeof value.start === 'number' &&\n typeof value.end === 'number' &&\n isRecord(object) &&\n object.type === 'MetaProperty' &&\n isRecord(object.meta) &&\n object.meta.name === 'import' &&\n isRecord(object.property) &&\n object.property.name === 'meta' &&\n isRecord(property) &&\n property.name === 'url'\n ) {\n ranges.push({ start: value.start, end: value.end });\n return;\n }\n\n for (const child of Object.values(value)) collectImportMetaUrlRanges(child, ranges);\n}\n\n/** Keeps package-owned resource lookup anchored to each original source file. */\nfunction preserveImportMetaUrl() {\n return {\n name: 'doom-original-import-meta-url',\n transform(\n this: {\n parse: (source: string, options?: { lang?: 'js' | 'jsx' | 'ts' | 'tsx' }) => unknown;\n },\n source: string,\n id: string,\n ): string | null {\n if (!path.isAbsolute(id) || !source.includes('import.meta.url')) return null;\n const extension = path.extname(id).toLowerCase();\n const lang = extension === '.tsx' ? 'tsx' : extension === '.jsx' ? 'jsx' : isTypeScriptEntry(id) ? 'ts' : 'js';\n const ranges: SourceRange[] = [];\n collectImportMetaUrlRanges(this.parse(source, { lang }), ranges);\n if (ranges.length === 0) return null;\n const replacement = JSON.stringify(pathToFileURL(id).href);\n let transformed = source;\n for (const range of ranges.sort((left, right) => right.start - left.start)) {\n transformed = `${transformed.slice(0, range.start)}${replacement}${transformed.slice(range.end)}`;\n }\n return transformed;\n },\n };\n}\n\n/**\n * Compiles an ordered extension set into one native ESM entry.\n *\n * The generated entry starts every module load together, then invokes factories\n * sequentially in activation order. Its manifest records every loaded module,\n * so a dependency edit invalidates the cache without running the compiler.\n */\nexport async function compileExtensionSet(\n entries: readonly string[],\n cacheDirectory: string,\n options: CompileExtensionSetOptions = {},\n): Promise<string> {\n if (entries.length === 0) throw new Error('Cannot compile an empty extension set');\n const normalized = entries.map((entry) => path.resolve(entry));\n const setDirectory = path.join(cacheDirectory, SET_DIRECTORY);\n fs.mkdirSync(setDirectory, { recursive: true });\n const key = extensionSetKey(normalized);\n const outputDirectory = path.resolve(options.outputDirectory ?? setDirectory);\n const outputName = safeOutputName(options.outputName ?? 'doom-set');\n fs.mkdirSync(outputDirectory, { recursive: true });\n // One graph can be emitted under several named mode artifacts. Keep their\n // cache records separate without changing extensionSetKey, which is the\n // runtime lookup key for an exact activation order.\n const outputKey = createHash('sha256').update(outputDirectory).update(outputName).digest('hex').slice(0, HASH_LENGTH);\n const manifestPath = path.join(setDirectory, `${key}.${outputKey}${MANIFEST_SUFFIX}`);\n const previous = readSetManifest(manifestPath);\n if (previous && manifestIsFresh(previous, normalized)) return previous.output;\n\n const inputs = new Set(normalized);\n const source = extensionSetSource(normalized);\n const { rolldown } = await import('rolldown');\n const build = await rolldown({\n input: VIRTUAL_SET_ENTRY,\n platform: 'node',\n plugins: [\n {\n name: 'doom-set-entry',\n resolveId(id: string): string | null {\n return id === VIRTUAL_SET_ENTRY ? id : null;\n },\n load(id: string): string | null {\n return id === VIRTUAL_SET_ENTRY ? source : null;\n },\n },\n setExternalResolver(inputs),\n preserveImportMetaUrl(),\n inputCollector(inputs),\n ],\n onLog: failUnresolvedImport,\n });\n let code: string;\n try {\n const generated = await build.generate({ format: 'esm', sourcemap: false, minify: true, codeSplitting: false });\n const chunk = generated.output.find((output) => output.type === 'chunk');\n if (!chunk || chunk.type !== 'chunk') throw new Error('Rolldown produced no extension bundle');\n code = chunk.code;\n } finally {\n await build.close();\n }\n\n const contentKey = createHash('sha256').update(SET_CACHE_VERSION).update(code).digest('hex').slice(0, HASH_LENGTH);\n const output = path.join(outputDirectory, `${outputName}.${contentKey}${COMPILED_SUFFIX}`);\n if (!fs.existsSync(output)) writeAtomic(output, code);\n const inputFingerprints = [...inputs]\n .map(fingerprint)\n .filter((value): value is InputFingerprint => value !== undefined)\n .sort((left, right) => left.path.localeCompare(right.path));\n writeAtomic(\n manifestPath,\n `${JSON.stringify({ version: SET_CACHE_VERSION, entries: normalized, output, inputs: inputFingerprints }, null, 2)}\\n`,\n );\n return output;\n}\n\n/**\n * Bundles `entry` into `cacheDirectory` and returns the compiled path.\n *\n * Non-TypeScript entries are returned untouched. A cache hit skips the build.\n */\nexport async function compileExtension(entry: string, cacheDirectory: string): Promise<string> {\n if (!isTypeScriptEntry(entry)) return entry;\n\n const output = path.join(cacheDirectory, compiledName(entry, cacheKey(entry)));\n if (fs.existsSync(output)) return output;\n\n // Rolldown loads a native module and a sizeable parser graph. Sync is the\n // only command that needs it, and a cache hit does not, so keep it off every\n // other CLI startup path.\n const { rolldown } = await import('rolldown');\n const build = await rolldown({\n input: entry,\n platform: 'node',\n plugins: [externalResolver(), preserveImportMetaUrl()],\n onLog: failUnresolvedImport,\n });\n try {\n // One file per extension. Several of them import lazily, and code splitting\n // would emit sibling chunks that a single output path cannot describe.\n await build.write({ file: output, format: 'esm', sourcemap: false, minify: true, codeSplitting: false });\n } finally {\n await build.close();\n }\n return output;\n}\n\n/**\n * Compiles every TypeScript entry in a resolution map, leaving the rest as-is.\n *\n * A failure is not fatal: the original path is kept so Pi falls back to loading\n * the TypeScript through jiti, which is what it did before this existed.\n */\nexport async function compileResolvedEntries(\n resolved: Record<string, string>,\n cacheDirectory: string,\n): Promise<Record<string, string>> {\n fs.mkdirSync(cacheDirectory, { recursive: true });\n const compiled: Record<string, string> = {};\n for (const [key, target] of Object.entries(resolved)) {\n try {\n compiled[key] = await compileExtension(target, cacheDirectory);\n } catch {\n compiled[key] = target;\n }\n }\n return compiled;\n}\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- import{optionalPackageEntry as e}from"../utils/moduleResolution.mjs";import"../utils/index.mjs";import{builtinModules as t,createRequire as n}from"node:module";import r from"node:path";import i from"node:fs";import{createHash as a}from"node:crypto";import{pathToFileURL as o}from"node:url";const s=[`.ts`,`.mts`,`.cts`],c=`.mjs`,l=`\0doompi-extension-set`,u={"@mariozechner/pi-coding-agent":`@earendil-works/pi-coding-agent`,"@mariozechner/pi-tui":`@earendil-works/pi-tui`,"@mariozechner/pi-ai":`@earendil-works/pi-ai`,"@sinclair/typebox":`typebox`};function d(e){return s.some(t=>e.endsWith(t))}function f(e){for(let[t,n]of Object.entries(u)){if(e===t)return n;if(e.startsWith(`${t}/`))return`${n}${e.slice(t.length)}`}return e}function p(e){let t=i.readFileSync(e);return a(`sha256`).update(`v3`).update(e).update(t).digest(`hex`).slice(0,16)}function m(e,t){let n=r.basename(e).replace(/\.[cm]?ts$/,``);return`${r.basename(r.dirname(e))}-${n}.${t}${c}`}const h=new Set([`@earendil-works/pi-coding-agent`,`@earendil-works/pi-tui`,`@earendil-works/pi-ai`]);function g(t){let r=e(`@earendil-works/pi-coding-agent`);if(r)try{return n(r).resolve(t)}catch{return}}const _=new Set([`@parcel/watcher`,`@napi-rs/keyring`,`better-sqlite3`,`canvas`,`fsevents`,`isolated-vm`,`keytar`,`node-pty`,`onnxruntime-node`,`playwright`,`playwright-core`,`puppeteer`,`sharp`]),v=new Set(t.flatMap(e=>[e,`node:${e}`]));function y(e){return e.trim().replace(/[^a-zA-Z0-9._-]+/g,`-`).replace(/^[-.]+|[-.]+$/g,``)||`doom-set`}function b(e){let t=e.startsWith(`@`)?e.indexOf(`/`,e.indexOf(`/`)+1):e.indexOf(`/`);return t===-1?e:e.slice(0,t)}function x(e){let t=e.split(r.sep).join(`/`),n=t.lastIndexOf(`/node_modules/`);if(n===-1)return;let i=t.slice(n+14).split(`/`),a=i.lastIndexOf(`node_modules`),o=a===-1?i:i.slice(a+1);return o[0]?.startsWith(`@`)?o.slice(0,2).join(`/`):o[0]}function S(){return{name:`doom-external-absolute`,async resolveId(t,n,i){if(t.startsWith(`.`)||r.isAbsolute(t)||t.startsWith(`node:`))return null;let a=f(t),o=b(a);if(o&&h.has(o)){let t=e(a);if(t)return{id:t,external:!0}}let s=await this.resolve(a,n,i);return!s||d(s.id)?null:{id:s.id,external:!0}}}}function C(e){try{let t=i.statSync(e);return t.isFile()?{path:e,size:t.size,mtimeMs:t.mtimeMs}:void 0}catch{return}}function w(e,t){return e.version!==`v4`||JSON.stringify(e.entries)!==JSON.stringify(t)||!i.existsSync(e.output)?!1:e.inputs.every(e=>{let t=C(e.path);return t?.size===e.size&&t.mtimeMs===e.mtimeMs})}function T(e){try{let t=JSON.parse(i.readFileSync(e,`utf8`));return t&&Array.isArray(t.entries)&&Array.isArray(t.inputs)?t:void 0}catch{return}}function E(e,t){let n=`${e}.${process.pid}.${Date.now()}.tmp`;i.writeFileSync(n,t);try{i.renameSync(n,e)}catch(t){if(i.rmSync(n,{force:!0}),!i.existsSync(e))throw t}}function D(e){return a(`sha256`).update(`v4`).update(JSON.stringify(e.map(e=>r.resolve(e)))).digest(`hex`).slice(0,16)}function O(e){let t=e.map(e=>`() => import(${JSON.stringify(k(e))})`),n=e.map(e=>JSON.stringify(e));return[`const loaders = [${t.join(`,`)}];`,`const names = [${n.join(`,`)}];`,`function report(index, error) {`,` const detail = error instanceof Error ? error.message : String(error);`," process.stderr.write(`[doompi] ${names[index]}: ${detail}\\n`);",`}`,`export default async function doompiExtensionSet(pi) {`,` const loaded = await Promise.all(loaders.map(async (load, index) => {`,` try {`,` return await load();`,` } catch (error) {`,` report(index, error);`,` return undefined;`,` }`,` }));`,` for (let index = 0; index < loaded.length; index += 1) {`,` const extension = loaded[index];`,` if (!extension) continue;`,` try {`,` if (typeof extension.default !== 'function') throw new Error('does not export an extension factory');`,` await extension.default(pi);`,` } catch (error) {`,` report(index, error);`,` }`,` }`,`}`].join(`
2
- `)}function k(e){return r.resolve(e).split(r.sep).join(`/`)}function A(t){return{name:`doom-set-external`,async resolveId(n,i,a){if(n===l)return null;if(v.has(n)||n.startsWith(`node:`))return{id:n,external:!0};if(r.isAbsolute(n)){let e=x(n);return e&&(h.has(e)||_.has(e))?(t.add(n),{id:n,external:!0}):null}if(n.startsWith(`.`))return null;let o=f(n),s=b(o);if(s&&h.has(s)){let n=e(o);if(n)return t.add(n),{id:n,external:!0}}if(s===`typebox`){let e=g(o);if(e)return t.add(e),{id:e,external:!1}}if(s&&_.has(s)){let e=await this.resolve(o,i,a);if(e)return t.add(e.id),{id:e.id,external:!0}}return null}}}function j(e){return{name:`doom-set-inputs`,load(t){return r.isAbsolute(t)&&i.existsSync(t)&&e.add(t),null}}}function M(e,t,n){t.code===`UNRESOLVED_IMPORT`&&n(`error`,t)}function N(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function P(e,t){if(Array.isArray(e)){for(let n of e)P(n,t);return}if(!N(e))return;let n=e.object,r=e.property;if(e.type===`MemberExpression`&&e.computed===!1&&typeof e.start==`number`&&typeof e.end==`number`&&N(n)&&n.type===`MetaProperty`&&N(n.meta)&&n.meta.name===`import`&&N(n.property)&&n.property.name===`meta`&&N(r)&&r.name===`url`){t.push({start:e.start,end:e.end});return}for(let n of Object.values(e))P(n,t)}function F(){return{name:`doom-original-import-meta-url`,transform(e,t){if(!r.isAbsolute(t)||!e.includes(`import.meta.url`))return null;let n=r.extname(t).toLowerCase(),i=n===`.tsx`?`tsx`:n===`.jsx`?`jsx`:d(t)?`ts`:`js`,a=[];if(P(this.parse(e,{lang:i}),a),a.length===0)return null;let s=JSON.stringify(o(t).href),c=e;for(let e of a.sort((e,t)=>t.start-e.start))c=`${c.slice(0,e.start)}${s}${c.slice(e.end)}`;return c}}}async function I(e,t,n={}){if(e.length===0)throw Error(`Cannot compile an empty extension set`);let o=e.map(e=>r.resolve(e)),s=r.join(t,`sets`);i.mkdirSync(s,{recursive:!0});let u=D(o),d=r.resolve(n.outputDirectory??s),f=y(n.outputName??`doom-set`);i.mkdirSync(d,{recursive:!0});let p=a(`sha256`).update(d).update(f).digest(`hex`).slice(0,16),m=r.join(s,`${u}.${p}.json`),h=T(m);if(h&&w(h,o))return h.output;let g=new Set(o),_=O(o),{rolldown:v}=await import(`rolldown`),b=await v({input:l,platform:`node`,plugins:[{name:`doom-set-entry`,resolveId(e){return e===l?e:null},load(e){return e===l?_:null}},A(g),F(),j(g)],onLog:M}),x;try{let e=(await b.generate({format:`esm`,sourcemap:!1,minify:!0,codeSplitting:!1})).output.find(e=>e.type===`chunk`);if(!e||e.type!==`chunk`)throw Error(`Rolldown produced no extension bundle`);x=e.code}finally{await b.close()}let S=a(`sha256`).update(`v4`).update(x).digest(`hex`).slice(0,16),k=r.join(d,`${f}.${S}${c}`);i.existsSync(k)||E(k,x);let N=[...g].map(C).filter(e=>e!==void 0).sort((e,t)=>e.path.localeCompare(t.path));return E(m,`${JSON.stringify({version:`v4`,entries:o,output:k,inputs:N},null,2)}\n`),k}async function L(e,t){if(!d(e))return e;let n=r.join(t,m(e,p(e)));if(i.existsSync(n))return n;let{rolldown:a}=await import(`rolldown`),o=await a({input:e,platform:`node`,plugins:[S(),F()],onLog:M});try{await o.write({file:n,format:`esm`,sourcemap:!1,minify:!0,codeSplitting:!1})}finally{await o.close()}return n}async function R(e,t){i.mkdirSync(t,{recursive:!0});let n={};for(let[r,i]of Object.entries(e))try{n[r]=await L(i,t)}catch{n[r]=i}return n}export{I as compileExtensionSet,R as compileResolvedEntries,D as extensionSetKey};
1
+ import{optionalPackageEntry as e}from"../utils/moduleResolution.mjs";import"../utils/index.mjs";import{builtinModules as t,createRequire as n}from"node:module";import r from"node:path";import i from"node:fs";import{createHash as a}from"node:crypto";import{pathToFileURL as o}from"node:url";const s=[`.ts`,`.mts`,`.cts`],c=`.mjs`,l=`\0doompi-extension-set`,u=`@agimon-ai/doompi-ui/extensionName`,d={"@mariozechner/pi-coding-agent":`@earendil-works/pi-coding-agent`,"@mariozechner/pi-tui":`@earendil-works/pi-tui`,"@mariozechner/pi-ai":`@earendil-works/pi-ai`,"@sinclair/typebox":`typebox`};function f(e){return s.some(t=>e.endsWith(t))}function p(e){for(let[t,n]of Object.entries(d)){if(e===t)return n;if(e.startsWith(`${t}/`))return`${n}${e.slice(t.length)}`}return e}function m(e){let t=i.readFileSync(e);return a(`sha256`).update(`v3`).update(e).update(t).digest(`hex`).slice(0,16)}function h(e,t){let n=r.basename(e).replace(/\.[cm]?ts$/,``);return`${r.basename(r.dirname(e))}-${n}.${t}${c}`}const g=new Set([`@earendil-works/pi-coding-agent`,`@earendil-works/pi-tui`,`@earendil-works/pi-ai`]);function _(t){let r=e(`@earendil-works/pi-coding-agent`);if(r)try{return n(r).resolve(t)}catch{return}}const v=new Set([`@parcel/watcher`,`@napi-rs/keyring`,`better-sqlite3`,`canvas`,`fsevents`,`isolated-vm`,`keytar`,`node-pty`,`onnxruntime-node`,`playwright`,`playwright-core`,`puppeteer`,`sharp`]),y=new Set(t.flatMap(e=>[e,`node:${e}`]));function b(e){return e.trim().replace(/[^a-zA-Z0-9._-]+/g,`-`).replace(/^[-.]+|[-.]+$/g,``)||`doom-set`}function x(e){let t=e.startsWith(`@`)?e.indexOf(`/`,e.indexOf(`/`)+1):e.indexOf(`/`);return t===-1?e:e.slice(0,t)}function S(e){let t=e.split(r.sep).join(`/`),n=t.lastIndexOf(`/node_modules/`);if(n===-1)return;let i=t.slice(n+14).split(`/`),a=i.lastIndexOf(`node_modules`),o=a===-1?i:i.slice(a+1);return o[0]?.startsWith(`@`)?o.slice(0,2).join(`/`):o[0]}function C(){return{name:`doom-external-absolute`,async resolveId(t,n,i){if(t.startsWith(`.`)||r.isAbsolute(t)||t.startsWith(`node:`))return null;let a=p(t),o=x(a);if(o&&g.has(o)){let t=e(a);if(t)return{id:t,external:!0}}let s=await this.resolve(a,n,i);return!s||f(s.id)?null:{id:s.id,external:!0}}}}function w(e){try{let t=i.statSync(e);return t.isFile()?{path:e,size:t.size,mtimeMs:t.mtimeMs}:void 0}catch{return}}function T(e,t){return e.version!==`v5`||JSON.stringify(e.entries)!==JSON.stringify(t)||!i.existsSync(e.output)?!1:e.inputs.every(e=>{let t=w(e.path);return t?.size===e.size&&t.mtimeMs===e.mtimeMs})}function E(e){try{let t=JSON.parse(i.readFileSync(e,`utf8`));return t&&Array.isArray(t.entries)&&Array.isArray(t.inputs)?t:void 0}catch{return}}function D(e,t){let n=`${e}.${process.pid}.${Date.now()}.tmp`;i.writeFileSync(n,t);try{i.renameSync(n,e)}catch(t){if(i.rmSync(n,{force:!0}),!i.existsSync(e))throw t}}function O(e){return a(`sha256`).update(`v5`).update(JSON.stringify(e.map(e=>r.resolve(e)))).digest(`hex`).slice(0,16)}function k(t){let n=e(u);if(!n)throw Error(`Cannot resolve ${u}`);let r=t.map(e=>`() => import(${JSON.stringify(A(e))})`),i=t.map(e=>JSON.stringify(e));return[`import { withExtensionSource } from ${JSON.stringify(A(n))};`,`const loaders = [${r.join(`,`)}];`,`const names = [${i.join(`,`)}];`,`function report(index, error) {`,` const detail = error instanceof Error ? error.message : String(error);`,` process.stderr.write("[doompi] " + names[index] + ": " + detail + "\\n");`,`}`,`export default async function doompiExtensionSet(pi) {`,` const loaded = await Promise.all(loaders.map(async (load, index) => {`,` try {`,` return await load();`,` } catch (error) {`,` report(index, error);`,` return undefined;`,` }`,` }));`,` for (let index = 0; index < loaded.length; index += 1) {`,` const extension = loaded[index];`,` if (!extension) continue;`,` try {`,` if (typeof extension.default !== 'function') throw new Error('does not export an extension factory');`,` await extension.default(withExtensionSource(pi, names[index]));`,` } catch (error) {`,` report(index, error);`,` }`,` }`,`}`].join(`
2
+ `)}function A(e){return r.resolve(e).split(r.sep).join(`/`)}function j(t){return{name:`doom-set-external`,async resolveId(n,i,a){if(n===l)return null;if(y.has(n)||n.startsWith(`node:`))return{id:n,external:!0};if(r.isAbsolute(n)){let e=S(n);return e&&(g.has(e)||v.has(e))?(t.add(n),{id:n,external:!0}):null}if(n.startsWith(`.`))return null;let o=p(n),s=x(o);if(s&&g.has(s)){let n=e(o);if(n)return t.add(n),{id:n,external:!0}}if(s===`typebox`){let e=_(o);if(e)return t.add(e),{id:e,external:!1}}if(s&&v.has(s)){let e=await this.resolve(o,i,a);if(e)return t.add(e.id),{id:e.id,external:!0}}return null}}}function M(e){return{name:`doom-set-inputs`,load(t){return r.isAbsolute(t)&&i.existsSync(t)&&e.add(t),null}}}function N(e,t,n){t.code===`UNRESOLVED_IMPORT`&&n(`error`,t)}function P(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function F(e,t){if(Array.isArray(e)){for(let n of e)F(n,t);return}if(!P(e))return;let n=e.object,r=e.property;if(e.type===`MemberExpression`&&e.computed===!1&&typeof e.start==`number`&&typeof e.end==`number`&&P(n)&&n.type===`MetaProperty`&&P(n.meta)&&n.meta.name===`import`&&P(n.property)&&n.property.name===`meta`&&P(r)&&r.name===`url`){t.push({start:e.start,end:e.end});return}for(let n of Object.values(e))F(n,t)}function I(){return{name:`doom-original-import-meta-url`,transform(e,t){if(!r.isAbsolute(t)||!e.includes(`import.meta.url`))return null;let n=r.extname(t).toLowerCase(),i=n===`.tsx`?`tsx`:n===`.jsx`?`jsx`:f(t)?`ts`:`js`,a=[];if(F(this.parse(e,{lang:i}),a),a.length===0)return null;let s=JSON.stringify(o(t).href),c=e;for(let e of a.sort((e,t)=>t.start-e.start))c=`${c.slice(0,e.start)}${s}${c.slice(e.end)}`;return c}}}async function L(e,t,n={}){if(e.length===0)throw Error(`Cannot compile an empty extension set`);let o=e.map(e=>r.resolve(e)),s=r.join(t,`sets`);i.mkdirSync(s,{recursive:!0});let u=O(o),d=r.resolve(n.outputDirectory??s),f=b(n.outputName??`doom-set`);i.mkdirSync(d,{recursive:!0});let p=a(`sha256`).update(d).update(f).digest(`hex`).slice(0,16),m=r.join(s,`${u}.${p}.json`),h=E(m);if(h&&T(h,o))return h.output;let g=new Set(o),_=k(o),{rolldown:v}=await import(`rolldown`),y=await v({input:l,platform:`node`,plugins:[{name:`doom-set-entry`,resolveId(e){return e===l?e:null},load(e){return e===l?_:null}},j(g),I(),M(g)],onLog:N}),x;try{let e=(await y.generate({format:`esm`,sourcemap:!1,minify:!0,codeSplitting:!1})).output.find(e=>e.type===`chunk`);if(!e||e.type!==`chunk`)throw Error(`Rolldown produced no extension bundle`);x=e.code}finally{await y.close()}let S=a(`sha256`).update(`v5`).update(x).digest(`hex`).slice(0,16),C=r.join(d,`${f}.${S}${c}`);i.existsSync(C)||D(C,x);let A=[...g].map(w).filter(e=>e!==void 0).sort((e,t)=>e.path.localeCompare(t.path));return D(m,`${JSON.stringify({version:`v5`,entries:o,output:C,inputs:A},null,2)}\n`),C}async function R(e,t){if(!f(e))return e;let n=r.join(t,h(e,m(e)));if(i.existsSync(n))return n;let{rolldown:a}=await import(`rolldown`),o=await a({input:e,platform:`node`,plugins:[C(),I()],onLog:N});try{await o.write({file:n,format:`esm`,sourcemap:!1,minify:!0,codeSplitting:!1})}finally{await o.close()}return n}async function z(e,t){i.mkdirSync(t,{recursive:!0});let n={};for(let[r,i]of Object.entries(e))try{n[r]=await R(i,t)}catch{n[r]=i}return n}export{L as compileExtensionSet,z as compileResolvedEntries,O as extensionSetKey};
3
3
  //# sourceMappingURL=extensionCompiler.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extensionCompiler.mjs","names":[],"sources":["../../src/services/extensionCompiler.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { builtinModules, createRequire } from 'node:module';\nimport { pathToFileURL } from 'node:url';\nimport type { LogLevel, LogOrStringHandler, RolldownLog } from 'rolldown';\nimport { optionalPackageEntry } from '../utils/index.ts';\n\n/**\n * Compiles TypeScript extensions into plain ESM ahead of the session.\n *\n * Pi loads extensions through jiti, which transpiles TypeScript at import time.\n * That works, but it costs a transform per module on a cold cache and makes Node\n * complain about type stripping under node_modules on every launch. Resolution\n * already knows the full extension list, so each TypeScript entry is bundled to\n * a single `.mjs` here and Pi imports it natively.\n *\n * Mode artifacts bundle their JavaScript dependencies. Node built-ins, native\n * packages, and Pi's shared runtime stay external; those imports are rewritten\n * to absolute paths so an artifact under `.pi/doom/dist` never relies on that\n * directory having its own `node_modules` tree.\n */\n\nconst TYPESCRIPT_SUFFIXES = ['.ts', '.mts', '.cts'] as const;\nconst COMPILED_SUFFIX = '.mjs';\nconst CACHE_VERSION = 'v3';\nconst SET_CACHE_VERSION = 'v4';\nconst HASH_LENGTH = 16;\nconst SET_DIRECTORY = 'sets';\nconst MANIFEST_SUFFIX = '.json';\nconst VIRTUAL_SET_ENTRY = '\\0doompi-extension-set';\n\n/** Pi's former npm scope. The packages are the same, published under a new name. */\nconst PI_SCOPE_RENAMES: Readonly<Record<string, string>> = {\n '@mariozechner/pi-coding-agent': '@earendil-works/pi-coding-agent',\n '@mariozechner/pi-tui': '@earendil-works/pi-tui',\n '@mariozechner/pi-ai': '@earendil-works/pi-ai',\n '@sinclair/typebox': 'typebox',\n};\n\nexport function isTypeScriptEntry(entry: string): boolean {\n return TYPESCRIPT_SUFFIXES.some((suffix) => entry.endsWith(suffix));\n}\n\nfunction renameScope(specifier: string): string {\n for (const [before, after] of Object.entries(PI_SCOPE_RENAMES)) {\n if (specifier === before) return after;\n if (specifier.startsWith(`${before}/`)) return `${after}${specifier.slice(before.length)}`;\n }\n return specifier;\n}\n\n/**\n * Cache key for a compiled entry.\n *\n * The entry's own bytes are the only cheap signal that the extension changed; a\n * dependency edit is not visible without walking the graph, so an upgrade that\n * leaves the entry byte-identical is served from cache until the version below\n * is raised.\n */\nfunction cacheKey(entry: string): string {\n const source = fs.readFileSync(entry);\n return createHash('sha256').update(CACHE_VERSION).update(entry).update(source).digest('hex').slice(0, HASH_LENGTH);\n}\n\nfunction compiledName(entry: string, key: string): string {\n const base = path.basename(entry).replace(/\\.[cm]?ts$/, '');\n const parent = path.basename(path.dirname(entry));\n return `${parent}-${base}.${key}${COMPILED_SUFFIX}`;\n}\n\n/**\n * Packages that must resolve to the copy this harness runs, never to a second\n * one bundled into an extension. Pi owns the TUI and the session state, so a\n * duplicate renders into a different screen and mutates a different store.\n */\nconst HOSTED_PACKAGES = new Set(['@earendil-works/pi-coding-agent', '@earendil-works/pi-tui', '@earendil-works/pi-ai']);\n\n/**\n * Resolves packages Pi exposes to extensions as compatibility aliases.\n *\n * Extensions do not need to declare TypeBox themselves because Pi's loader\n * provides it as a virtual module. A dist bundle bypasses that loader, so it\n * resolves the same package against Pi's dependency tree and bundles it.\n */\nfunction piExtensionDependencyEntry(specifier: string): string | undefined {\n const piEntry = optionalPackageEntry('@earendil-works/pi-coding-agent');\n if (!piEntry) return undefined;\n try {\n return createRequire(piEntry).resolve(specifier);\n } catch {\n return undefined;\n }\n}\n\n/** Packages whose native binaries or runtime discovery cannot be rolled up. */\nconst NATIVE_PACKAGES = new Set([\n '@parcel/watcher',\n '@napi-rs/keyring',\n 'better-sqlite3',\n 'canvas',\n 'fsevents',\n 'isolated-vm',\n 'keytar',\n 'node-pty',\n 'onnxruntime-node',\n 'playwright',\n 'playwright-core',\n 'puppeteer',\n 'sharp',\n]);\nconst NODE_BUILTINS = new Set(builtinModules.flatMap((name) => [name, `node:${name}`]));\n\ninterface InputFingerprint {\n path: string;\n size: number;\n mtimeMs: number;\n}\n\ninterface ExtensionSetManifest {\n version: string;\n entries: string[];\n output: string;\n inputs: InputFingerprint[];\n}\n\nexport interface CompileExtensionSetOptions {\n /** Persistent artifact directory. Compiler manifests remain in cacheDirectory. */\n outputDirectory?: string;\n /** Human-readable artifact prefix, normally the major-mode name. */\n outputName?: string;\n}\n\nfunction safeOutputName(value: string): string {\n const normalized = value\n .trim()\n .replace(/[^a-zA-Z0-9._-]+/g, '-')\n .replace(/^[-.]+|[-.]+$/g, '');\n return normalized || 'doom-set';\n}\n\nfunction packageRootOf(specifier: string): string | undefined {\n const slash = specifier.startsWith('@') ? specifier.indexOf('/', specifier.indexOf('/') + 1) : specifier.indexOf('/');\n return slash === -1 ? specifier : specifier.slice(0, slash);\n}\n\nfunction packageRootFromPath(target: string): string | undefined {\n const normalized = target.split(path.sep).join('/');\n const marker = '/node_modules/';\n const offset = normalized.lastIndexOf(marker);\n if (offset === -1) return undefined;\n const parts = normalized.slice(offset + marker.length).split('/');\n // pnpm's first segment is a store key; the second node_modules marker names\n // the package that owns the file.\n const nested = parts.lastIndexOf('node_modules');\n const packageParts = nested === -1 ? parts : parts.slice(nested + 1);\n return packageParts[0]?.startsWith('@') ? packageParts.slice(0, 2).join('/') : packageParts[0];\n}\n\nfunction externalResolver() {\n return {\n name: 'doom-external-absolute',\n async resolveId(\n this: { resolve: (id: string, importer?: string, options?: unknown) => Promise<{ id: string } | null> },\n specifier: string,\n importer: string | undefined,\n options: unknown,\n ): Promise<{ id: string; external: boolean } | null> {\n if (specifier.startsWith('.') || path.isAbsolute(specifier) || specifier.startsWith('node:')) return null;\n const renamed = renameScope(specifier);\n\n // Host packages are resolved from this module, the way the rest of the\n // harness resolves them, because an extension written against Pi's former\n // scope does not have the current one in its own dependency tree.\n const root = packageRootOf(renamed);\n if (root && HOSTED_PACKAGES.has(root)) {\n const hosted = optionalPackageEntry(renamed);\n if (hosted) return { id: hosted, external: true };\n }\n\n // Rolldown's resolver rather than `createRequire`, which cannot see an\n // ESM-only package that publishes no `require` condition and would send\n // Pi's own core into the bundle.\n const resolved = await this.resolve(renamed, importer, options);\n if (!resolved) return null;\n // A dependency that is itself TypeScript cannot be handed to Node either,\n // so it is bundled rather than externalised.\n if (isTypeScriptEntry(resolved.id)) return null;\n return { id: resolved.id, external: true };\n },\n };\n}\n\nfunction fingerprint(target: string): InputFingerprint | undefined {\n try {\n const stat = fs.statSync(target);\n if (!stat.isFile()) return undefined;\n return { path: target, size: stat.size, mtimeMs: stat.mtimeMs };\n } catch {\n return undefined;\n }\n}\n\nfunction manifestIsFresh(manifest: ExtensionSetManifest, entries: string[]): boolean {\n if (\n manifest.version !== SET_CACHE_VERSION ||\n JSON.stringify(manifest.entries) !== JSON.stringify(entries) ||\n !fs.existsSync(manifest.output)\n ) {\n return false;\n }\n return manifest.inputs.every((previous) => {\n const current = fingerprint(previous.path);\n return current?.size === previous.size && current.mtimeMs === previous.mtimeMs;\n });\n}\n\nfunction readSetManifest(manifestPath: string): ExtensionSetManifest | undefined {\n try {\n const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as ExtensionSetManifest;\n return parsed && Array.isArray(parsed.entries) && Array.isArray(parsed.inputs) ? parsed : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction writeAtomic(target: string, contents: string): void {\n const temporary = `${target}.${process.pid}.${Date.now()}.tmp`;\n fs.writeFileSync(temporary, contents);\n try {\n fs.renameSync(temporary, target);\n } catch (error) {\n fs.rmSync(temporary, { force: true });\n if (!fs.existsSync(target)) throw error;\n }\n}\n\n/** Stable lookup key for one exact Pi load order. */\nexport function extensionSetKey(entries: readonly string[]): string {\n return createHash('sha256')\n .update(SET_CACHE_VERSION)\n .update(JSON.stringify(entries.map((entry) => path.resolve(entry))))\n .digest('hex')\n .slice(0, HASH_LENGTH);\n}\n\nfunction extensionSetSource(entries: readonly string[]): string {\n const loaders = entries.map((entry) => `() => import(${JSON.stringify(pathToImportSpecifier(entry))})`);\n const names = entries.map((entry) => JSON.stringify(entry));\n return [\n `const loaders = [${loaders.join(',')}];`,\n `const names = [${names.join(',')}];`,\n 'function report(index, error) {',\n ' const detail = error instanceof Error ? error.message : String(error);',\n ' process.stderr.write(`[doompi] ${names[index]}: ${detail}\\\\n`);',\n '}',\n 'export default async function doompiExtensionSet(pi) {',\n ' const loaded = await Promise.all(loaders.map(async (load, index) => {',\n ' try {',\n ' return await load();',\n ' } catch (error) {',\n ' report(index, error);',\n ' return undefined;',\n ' }',\n ' }));',\n ' for (let index = 0; index < loaded.length; index += 1) {',\n ' const extension = loaded[index];',\n ' if (!extension) continue;',\n ' try {',\n \" if (typeof extension.default !== 'function') throw new Error('does not export an extension factory');\",\n ' await extension.default(pi);',\n ' } catch (error) {',\n ' report(index, error);',\n ' }',\n ' }',\n '}',\n ].join('\\n');\n}\n\nfunction pathToImportSpecifier(target: string): string {\n // Rolldown accepts absolute POSIX paths directly. Windows needs a file URL,\n // but replacing separators is enough for its platform-independent parser.\n return path.resolve(target).split(path.sep).join('/');\n}\n\nfunction setExternalResolver(inputs: Set<string>) {\n return {\n name: 'doom-set-external',\n async resolveId(\n this: { resolve: (id: string, importer?: string, options?: unknown) => Promise<{ id: string } | null> },\n specifier: string,\n importer: string | undefined,\n options: unknown,\n ): Promise<{ id: string; external: boolean } | null> {\n if (specifier === VIRTUAL_SET_ENTRY) return null;\n if (NODE_BUILTINS.has(specifier) || specifier.startsWith('node:')) {\n return { id: specifier, external: true };\n }\n\n if (path.isAbsolute(specifier)) {\n const root = packageRootFromPath(specifier);\n if (root && (HOSTED_PACKAGES.has(root) || NATIVE_PACKAGES.has(root))) {\n inputs.add(specifier);\n return { id: specifier, external: true };\n }\n return null;\n }\n if (specifier.startsWith('.')) return null;\n\n const renamed = renameScope(specifier);\n const root = packageRootOf(renamed);\n if (root && HOSTED_PACKAGES.has(root)) {\n const hosted = optionalPackageEntry(renamed);\n if (hosted) {\n inputs.add(hosted);\n return { id: hosted, external: true };\n }\n }\n if (root === 'typebox') {\n const typebox = piExtensionDependencyEntry(renamed);\n if (typebox) {\n inputs.add(typebox);\n return { id: typebox, external: false };\n }\n }\n if (root && NATIVE_PACKAGES.has(root)) {\n const resolved = await this.resolve(renamed, importer, options);\n if (resolved) {\n inputs.add(resolved.id);\n return { id: resolved.id, external: true };\n }\n }\n return null;\n },\n };\n}\n\nfunction inputCollector(inputs: Set<string>) {\n return {\n name: 'doom-set-inputs',\n load(id: string): null {\n if (path.isAbsolute(id) && fs.existsSync(id)) inputs.add(id);\n return null;\n },\n };\n}\n\n/** An unresolved bare import would otherwise become a broken dist external. */\nfunction failUnresolvedImport(_level: LogLevel, log: RolldownLog, handler: LogOrStringHandler): void {\n if (log.code === 'UNRESOLVED_IMPORT') handler('error', log);\n}\n\ninterface SourceRange {\n start: number;\n end: number;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction collectImportMetaUrlRanges(value: unknown, ranges: SourceRange[]): void {\n if (Array.isArray(value)) {\n for (const item of value) collectImportMetaUrlRanges(item, ranges);\n return;\n }\n if (!isRecord(value)) return;\n\n const object = value.object;\n const property = value.property;\n if (\n value.type === 'MemberExpression' &&\n value.computed === false &&\n typeof value.start === 'number' &&\n typeof value.end === 'number' &&\n isRecord(object) &&\n object.type === 'MetaProperty' &&\n isRecord(object.meta) &&\n object.meta.name === 'import' &&\n isRecord(object.property) &&\n object.property.name === 'meta' &&\n isRecord(property) &&\n property.name === 'url'\n ) {\n ranges.push({ start: value.start, end: value.end });\n return;\n }\n\n for (const child of Object.values(value)) collectImportMetaUrlRanges(child, ranges);\n}\n\n/** Keeps package-owned resource lookup anchored to each original source file. */\nfunction preserveImportMetaUrl() {\n return {\n name: 'doom-original-import-meta-url',\n transform(\n this: {\n parse: (source: string, options?: { lang?: 'js' | 'jsx' | 'ts' | 'tsx' }) => unknown;\n },\n source: string,\n id: string,\n ): string | null {\n if (!path.isAbsolute(id) || !source.includes('import.meta.url')) return null;\n const extension = path.extname(id).toLowerCase();\n const lang = extension === '.tsx' ? 'tsx' : extension === '.jsx' ? 'jsx' : isTypeScriptEntry(id) ? 'ts' : 'js';\n const ranges: SourceRange[] = [];\n collectImportMetaUrlRanges(this.parse(source, { lang }), ranges);\n if (ranges.length === 0) return null;\n const replacement = JSON.stringify(pathToFileURL(id).href);\n let transformed = source;\n for (const range of ranges.sort((left, right) => right.start - left.start)) {\n transformed = `${transformed.slice(0, range.start)}${replacement}${transformed.slice(range.end)}`;\n }\n return transformed;\n },\n };\n}\n\n/**\n * Compiles an ordered extension set into one native ESM entry.\n *\n * The generated entry starts every module load together, then invokes factories\n * sequentially in activation order. Its manifest records every loaded module,\n * so a dependency edit invalidates the cache without running the compiler.\n */\nexport async function compileExtensionSet(\n entries: readonly string[],\n cacheDirectory: string,\n options: CompileExtensionSetOptions = {},\n): Promise<string> {\n if (entries.length === 0) throw new Error('Cannot compile an empty extension set');\n const normalized = entries.map((entry) => path.resolve(entry));\n const setDirectory = path.join(cacheDirectory, SET_DIRECTORY);\n fs.mkdirSync(setDirectory, { recursive: true });\n const key = extensionSetKey(normalized);\n const outputDirectory = path.resolve(options.outputDirectory ?? setDirectory);\n const outputName = safeOutputName(options.outputName ?? 'doom-set');\n fs.mkdirSync(outputDirectory, { recursive: true });\n // One graph can be emitted under several named mode artifacts. Keep their\n // cache records separate without changing extensionSetKey, which is the\n // runtime lookup key for an exact activation order.\n const outputKey = createHash('sha256').update(outputDirectory).update(outputName).digest('hex').slice(0, HASH_LENGTH);\n const manifestPath = path.join(setDirectory, `${key}.${outputKey}${MANIFEST_SUFFIX}`);\n const previous = readSetManifest(manifestPath);\n if (previous && manifestIsFresh(previous, normalized)) return previous.output;\n\n const inputs = new Set(normalized);\n const source = extensionSetSource(normalized);\n const { rolldown } = await import('rolldown');\n const build = await rolldown({\n input: VIRTUAL_SET_ENTRY,\n platform: 'node',\n plugins: [\n {\n name: 'doom-set-entry',\n resolveId(id: string): string | null {\n return id === VIRTUAL_SET_ENTRY ? id : null;\n },\n load(id: string): string | null {\n return id === VIRTUAL_SET_ENTRY ? source : null;\n },\n },\n setExternalResolver(inputs),\n preserveImportMetaUrl(),\n inputCollector(inputs),\n ],\n onLog: failUnresolvedImport,\n });\n let code: string;\n try {\n const generated = await build.generate({ format: 'esm', sourcemap: false, minify: true, codeSplitting: false });\n const chunk = generated.output.find((output) => output.type === 'chunk');\n if (!chunk || chunk.type !== 'chunk') throw new Error('Rolldown produced no extension bundle');\n code = chunk.code;\n } finally {\n await build.close();\n }\n\n const contentKey = createHash('sha256').update(SET_CACHE_VERSION).update(code).digest('hex').slice(0, HASH_LENGTH);\n const output = path.join(outputDirectory, `${outputName}.${contentKey}${COMPILED_SUFFIX}`);\n if (!fs.existsSync(output)) writeAtomic(output, code);\n const inputFingerprints = [...inputs]\n .map(fingerprint)\n .filter((value): value is InputFingerprint => value !== undefined)\n .sort((left, right) => left.path.localeCompare(right.path));\n writeAtomic(\n manifestPath,\n `${JSON.stringify({ version: SET_CACHE_VERSION, entries: normalized, output, inputs: inputFingerprints }, null, 2)}\\n`,\n );\n return output;\n}\n\n/**\n * Bundles `entry` into `cacheDirectory` and returns the compiled path.\n *\n * Non-TypeScript entries are returned untouched. A cache hit skips the build.\n */\nexport async function compileExtension(entry: string, cacheDirectory: string): Promise<string> {\n if (!isTypeScriptEntry(entry)) return entry;\n\n const output = path.join(cacheDirectory, compiledName(entry, cacheKey(entry)));\n if (fs.existsSync(output)) return output;\n\n // Rolldown loads a native module and a sizeable parser graph. Sync is the\n // only command that needs it, and a cache hit does not, so keep it off every\n // other CLI startup path.\n const { rolldown } = await import('rolldown');\n const build = await rolldown({\n input: entry,\n platform: 'node',\n plugins: [externalResolver(), preserveImportMetaUrl()],\n onLog: failUnresolvedImport,\n });\n try {\n // One file per extension. Several of them import lazily, and code splitting\n // would emit sibling chunks that a single output path cannot describe.\n await build.write({ file: output, format: 'esm', sourcemap: false, minify: true, codeSplitting: false });\n } finally {\n await build.close();\n }\n return output;\n}\n\n/**\n * Compiles every TypeScript entry in a resolution map, leaving the rest as-is.\n *\n * A failure is not fatal: the original path is kept so Pi falls back to loading\n * the TypeScript through jiti, which is what it did before this existed.\n */\nexport async function compileResolvedEntries(\n resolved: Record<string, string>,\n cacheDirectory: string,\n): Promise<Record<string, string>> {\n fs.mkdirSync(cacheDirectory, { recursive: true });\n const compiled: Record<string, string> = {};\n for (const [key, target] of Object.entries(resolved)) {\n try {\n compiled[key] = await compileExtension(target, cacheDirectory);\n } catch {\n compiled[key] = target;\n }\n }\n return compiled;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"extensionCompiler.mjs","names":[],"sources":["../../src/services/extensionCompiler.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport { builtinModules, createRequire } from 'node:module';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { LogLevel, LogOrStringHandler, RolldownLog } from 'rolldown';\nimport { optionalPackageEntry } from '../utils/index.ts';\n\n/**\n * Compiles TypeScript extensions into plain ESM ahead of the session.\n *\n * Pi loads extensions through jiti, which transpiles TypeScript at import time.\n * That works, but it costs a transform per module on a cold cache and makes Node\n * complain about type stripping under node_modules on every launch. Resolution\n * already knows the full extension list, so each TypeScript entry is bundled to\n * a single `.mjs` here and Pi imports it natively.\n *\n * Mode artifacts bundle their JavaScript dependencies. Node built-ins, native\n * packages, and Pi's shared runtime stay external; those imports are rewritten\n * to absolute paths so an artifact under `.pi/doom/dist` never relies on that\n * directory having its own `node_modules` tree.\n */\n\nconst TYPESCRIPT_SUFFIXES = ['.ts', '.mts', '.cts'] as const;\nconst COMPILED_SUFFIX = '.mjs';\nconst CACHE_VERSION = 'v3';\nconst SET_CACHE_VERSION = 'v5';\nconst HASH_LENGTH = 16;\nconst SET_DIRECTORY = 'sets';\nconst MANIFEST_SUFFIX = '.json';\nconst VIRTUAL_SET_ENTRY = '\\0doompi-extension-set';\nconst EXTENSION_SOURCE_HELPER = '@agimon-ai/doompi-ui/extensionName';\n\n/** Pi's former npm scope. The packages are the same, published under a new name. */\nconst PI_SCOPE_RENAMES: Readonly<Record<string, string>> = {\n '@mariozechner/pi-coding-agent': '@earendil-works/pi-coding-agent',\n '@mariozechner/pi-tui': '@earendil-works/pi-tui',\n '@mariozechner/pi-ai': '@earendil-works/pi-ai',\n '@sinclair/typebox': 'typebox',\n};\n\nexport function isTypeScriptEntry(entry: string): boolean {\n return TYPESCRIPT_SUFFIXES.some((suffix) => entry.endsWith(suffix));\n}\n\nfunction renameScope(specifier: string): string {\n for (const [before, after] of Object.entries(PI_SCOPE_RENAMES)) {\n if (specifier === before) return after;\n if (specifier.startsWith(`${before}/`)) return `${after}${specifier.slice(before.length)}`;\n }\n return specifier;\n}\n\n/**\n * Cache key for a compiled entry.\n *\n * The entry's own bytes are the only cheap signal that the extension changed; a\n * dependency edit is not visible without walking the graph, so an upgrade that\n * leaves the entry byte-identical is served from cache until the version below\n * is raised.\n */\nfunction cacheKey(entry: string): string {\n const source = fs.readFileSync(entry);\n return createHash('sha256').update(CACHE_VERSION).update(entry).update(source).digest('hex').slice(0, HASH_LENGTH);\n}\n\nfunction compiledName(entry: string, key: string): string {\n const base = path.basename(entry).replace(/\\.[cm]?ts$/, '');\n const parent = path.basename(path.dirname(entry));\n return `${parent}-${base}.${key}${COMPILED_SUFFIX}`;\n}\n\n/**\n * Packages that must resolve to the copy this harness runs, never to a second\n * one bundled into an extension. Pi owns the TUI and the session state, so a\n * duplicate renders into a different screen and mutates a different store.\n */\nconst HOSTED_PACKAGES = new Set(['@earendil-works/pi-coding-agent', '@earendil-works/pi-tui', '@earendil-works/pi-ai']);\n\n/**\n * Resolves packages Pi exposes to extensions as compatibility aliases.\n *\n * Extensions do not need to declare TypeBox themselves because Pi's loader\n * provides it as a virtual module. A dist bundle bypasses that loader, so it\n * resolves the same package against Pi's dependency tree and bundles it.\n */\nfunction piExtensionDependencyEntry(specifier: string): string | undefined {\n const piEntry = optionalPackageEntry('@earendil-works/pi-coding-agent');\n if (!piEntry) return undefined;\n try {\n return createRequire(piEntry).resolve(specifier);\n } catch {\n return undefined;\n }\n}\n\n/** Packages whose native binaries or runtime discovery cannot be rolled up. */\nconst NATIVE_PACKAGES = new Set([\n '@parcel/watcher',\n '@napi-rs/keyring',\n 'better-sqlite3',\n 'canvas',\n 'fsevents',\n 'isolated-vm',\n 'keytar',\n 'node-pty',\n 'onnxruntime-node',\n 'playwright',\n 'playwright-core',\n 'puppeteer',\n 'sharp',\n]);\nconst NODE_BUILTINS = new Set(builtinModules.flatMap((name) => [name, `node:${name}`]));\n\ninterface InputFingerprint {\n path: string;\n size: number;\n mtimeMs: number;\n}\n\ninterface ExtensionSetManifest {\n version: string;\n entries: string[];\n output: string;\n inputs: InputFingerprint[];\n}\n\nexport interface CompileExtensionSetOptions {\n /** Persistent artifact directory. Compiler manifests remain in cacheDirectory. */\n outputDirectory?: string;\n /** Human-readable artifact prefix, normally the major-mode name. */\n outputName?: string;\n}\n\nfunction safeOutputName(value: string): string {\n const normalized = value\n .trim()\n .replace(/[^a-zA-Z0-9._-]+/g, '-')\n .replace(/^[-.]+|[-.]+$/g, '');\n return normalized || 'doom-set';\n}\n\nfunction packageRootOf(specifier: string): string | undefined {\n const slash = specifier.startsWith('@') ? specifier.indexOf('/', specifier.indexOf('/') + 1) : specifier.indexOf('/');\n return slash === -1 ? specifier : specifier.slice(0, slash);\n}\n\nfunction packageRootFromPath(target: string): string | undefined {\n const normalized = target.split(path.sep).join('/');\n const marker = '/node_modules/';\n const offset = normalized.lastIndexOf(marker);\n if (offset === -1) return undefined;\n const parts = normalized.slice(offset + marker.length).split('/');\n // pnpm's first segment is a store key; the second node_modules marker names\n // the package that owns the file.\n const nested = parts.lastIndexOf('node_modules');\n const packageParts = nested === -1 ? parts : parts.slice(nested + 1);\n return packageParts[0]?.startsWith('@') ? packageParts.slice(0, 2).join('/') : packageParts[0];\n}\n\nfunction externalResolver() {\n return {\n name: 'doom-external-absolute',\n async resolveId(\n this: { resolve: (id: string, importer?: string, options?: unknown) => Promise<{ id: string } | null> },\n specifier: string,\n importer: string | undefined,\n options: unknown,\n ): Promise<{ id: string; external: boolean } | null> {\n if (specifier.startsWith('.') || path.isAbsolute(specifier) || specifier.startsWith('node:')) return null;\n const renamed = renameScope(specifier);\n\n // Host packages are resolved from this module, the way the rest of the\n // harness resolves them, because an extension written against Pi's former\n // scope does not have the current one in its own dependency tree.\n const root = packageRootOf(renamed);\n if (root && HOSTED_PACKAGES.has(root)) {\n const hosted = optionalPackageEntry(renamed);\n if (hosted) return { id: hosted, external: true };\n }\n\n // Rolldown's resolver rather than `createRequire`, which cannot see an\n // ESM-only package that publishes no `require` condition and would send\n // Pi's own core into the bundle.\n const resolved = await this.resolve(renamed, importer, options);\n if (!resolved) return null;\n // A dependency that is itself TypeScript cannot be handed to Node either,\n // so it is bundled rather than externalised.\n if (isTypeScriptEntry(resolved.id)) return null;\n return { id: resolved.id, external: true };\n },\n };\n}\n\nfunction fingerprint(target: string): InputFingerprint | undefined {\n try {\n const stat = fs.statSync(target);\n if (!stat.isFile()) return undefined;\n return { path: target, size: stat.size, mtimeMs: stat.mtimeMs };\n } catch {\n return undefined;\n }\n}\n\nfunction manifestIsFresh(manifest: ExtensionSetManifest, entries: string[]): boolean {\n if (\n manifest.version !== SET_CACHE_VERSION ||\n JSON.stringify(manifest.entries) !== JSON.stringify(entries) ||\n !fs.existsSync(manifest.output)\n ) {\n return false;\n }\n return manifest.inputs.every((previous) => {\n const current = fingerprint(previous.path);\n return current?.size === previous.size && current.mtimeMs === previous.mtimeMs;\n });\n}\n\nfunction readSetManifest(manifestPath: string): ExtensionSetManifest | undefined {\n try {\n const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as ExtensionSetManifest;\n return parsed && Array.isArray(parsed.entries) && Array.isArray(parsed.inputs) ? parsed : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction writeAtomic(target: string, contents: string): void {\n const temporary = `${target}.${process.pid}.${Date.now()}.tmp`;\n fs.writeFileSync(temporary, contents);\n try {\n fs.renameSync(temporary, target);\n } catch (error) {\n fs.rmSync(temporary, { force: true });\n if (!fs.existsSync(target)) throw error;\n }\n}\n\n/** Stable lookup key for one exact Pi load order. */\nexport function extensionSetKey(entries: readonly string[]): string {\n return createHash('sha256')\n .update(SET_CACHE_VERSION)\n .update(JSON.stringify(entries.map((entry) => path.resolve(entry))))\n .digest('hex')\n .slice(0, HASH_LENGTH);\n}\n\nfunction extensionSetSource(entries: readonly string[]): string {\n const helper = optionalPackageEntry(EXTENSION_SOURCE_HELPER);\n if (!helper) throw new Error(`Cannot resolve ${EXTENSION_SOURCE_HELPER}`);\n const loaders = entries.map((entry) => `() => import(${JSON.stringify(pathToImportSpecifier(entry))})`);\n const names = entries.map((entry) => JSON.stringify(entry));\n return [\n `import { withExtensionSource } from ${JSON.stringify(pathToImportSpecifier(helper))};`,\n `const loaders = [${loaders.join(',')}];`,\n `const names = [${names.join(',')}];`,\n 'function report(index, error) {',\n ' const detail = error instanceof Error ? error.message : String(error);',\n ' process.stderr.write(\"[doompi] \" + names[index] + \": \" + detail + \"\\\\n\");',\n '}',\n 'export default async function doompiExtensionSet(pi) {',\n ' const loaded = await Promise.all(loaders.map(async (load, index) => {',\n ' try {',\n ' return await load();',\n ' } catch (error) {',\n ' report(index, error);',\n ' return undefined;',\n ' }',\n ' }));',\n ' for (let index = 0; index < loaded.length; index += 1) {',\n ' const extension = loaded[index];',\n ' if (!extension) continue;',\n ' try {',\n \" if (typeof extension.default !== 'function') throw new Error('does not export an extension factory');\",\n ' await extension.default(withExtensionSource(pi, names[index]));',\n ' } catch (error) {',\n ' report(index, error);',\n ' }',\n ' }',\n '}',\n ].join('\\n');\n}\n\nfunction pathToImportSpecifier(target: string): string {\n // Rolldown accepts absolute POSIX paths directly. Windows needs a file URL,\n // but replacing separators is enough for its platform-independent parser.\n return path.resolve(target).split(path.sep).join('/');\n}\n\nfunction setExternalResolver(inputs: Set<string>) {\n return {\n name: 'doom-set-external',\n async resolveId(\n this: { resolve: (id: string, importer?: string, options?: unknown) => Promise<{ id: string } | null> },\n specifier: string,\n importer: string | undefined,\n options: unknown,\n ): Promise<{ id: string; external: boolean } | null> {\n if (specifier === VIRTUAL_SET_ENTRY) return null;\n if (NODE_BUILTINS.has(specifier) || specifier.startsWith('node:')) {\n return { id: specifier, external: true };\n }\n\n if (path.isAbsolute(specifier)) {\n const root = packageRootFromPath(specifier);\n if (root && (HOSTED_PACKAGES.has(root) || NATIVE_PACKAGES.has(root))) {\n inputs.add(specifier);\n return { id: specifier, external: true };\n }\n return null;\n }\n if (specifier.startsWith('.')) return null;\n\n const renamed = renameScope(specifier);\n const root = packageRootOf(renamed);\n if (root && HOSTED_PACKAGES.has(root)) {\n const hosted = optionalPackageEntry(renamed);\n if (hosted) {\n inputs.add(hosted);\n return { id: hosted, external: true };\n }\n }\n if (root === 'typebox') {\n const typebox = piExtensionDependencyEntry(renamed);\n if (typebox) {\n inputs.add(typebox);\n return { id: typebox, external: false };\n }\n }\n if (root && NATIVE_PACKAGES.has(root)) {\n const resolved = await this.resolve(renamed, importer, options);\n if (resolved) {\n inputs.add(resolved.id);\n return { id: resolved.id, external: true };\n }\n }\n return null;\n },\n };\n}\n\nfunction inputCollector(inputs: Set<string>) {\n return {\n name: 'doom-set-inputs',\n load(id: string): null {\n if (path.isAbsolute(id) && fs.existsSync(id)) inputs.add(id);\n return null;\n },\n };\n}\n\n/** An unresolved bare import would otherwise become a broken dist external. */\nfunction failUnresolvedImport(_level: LogLevel, log: RolldownLog, handler: LogOrStringHandler): void {\n if (log.code === 'UNRESOLVED_IMPORT') handler('error', log);\n}\n\ninterface SourceRange {\n start: number;\n end: number;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction collectImportMetaUrlRanges(value: unknown, ranges: SourceRange[]): void {\n if (Array.isArray(value)) {\n for (const item of value) collectImportMetaUrlRanges(item, ranges);\n return;\n }\n if (!isRecord(value)) return;\n\n const object = value.object;\n const property = value.property;\n if (\n value.type === 'MemberExpression' &&\n value.computed === false &&\n typeof value.start === 'number' &&\n typeof value.end === 'number' &&\n isRecord(object) &&\n object.type === 'MetaProperty' &&\n isRecord(object.meta) &&\n object.meta.name === 'import' &&\n isRecord(object.property) &&\n object.property.name === 'meta' &&\n isRecord(property) &&\n property.name === 'url'\n ) {\n ranges.push({ start: value.start, end: value.end });\n return;\n }\n\n for (const child of Object.values(value)) collectImportMetaUrlRanges(child, ranges);\n}\n\n/** Keeps package-owned resource lookup anchored to each original source file. */\nfunction preserveImportMetaUrl() {\n return {\n name: 'doom-original-import-meta-url',\n transform(\n this: {\n parse: (source: string, options?: { lang?: 'js' | 'jsx' | 'ts' | 'tsx' }) => unknown;\n },\n source: string,\n id: string,\n ): string | null {\n if (!path.isAbsolute(id) || !source.includes('import.meta.url')) return null;\n const extension = path.extname(id).toLowerCase();\n const lang = extension === '.tsx' ? 'tsx' : extension === '.jsx' ? 'jsx' : isTypeScriptEntry(id) ? 'ts' : 'js';\n const ranges: SourceRange[] = [];\n collectImportMetaUrlRanges(this.parse(source, { lang }), ranges);\n if (ranges.length === 0) return null;\n const replacement = JSON.stringify(pathToFileURL(id).href);\n let transformed = source;\n for (const range of ranges.sort((left, right) => right.start - left.start)) {\n transformed = `${transformed.slice(0, range.start)}${replacement}${transformed.slice(range.end)}`;\n }\n return transformed;\n },\n };\n}\n\n/**\n * Compiles an ordered extension set into one native ESM entry.\n *\n * The generated entry starts every module load together, then invokes factories\n * sequentially in activation order. Its manifest records every loaded module,\n * so a dependency edit invalidates the cache without running the compiler.\n */\nexport async function compileExtensionSet(\n entries: readonly string[],\n cacheDirectory: string,\n options: CompileExtensionSetOptions = {},\n): Promise<string> {\n if (entries.length === 0) throw new Error('Cannot compile an empty extension set');\n const normalized = entries.map((entry) => path.resolve(entry));\n const setDirectory = path.join(cacheDirectory, SET_DIRECTORY);\n fs.mkdirSync(setDirectory, { recursive: true });\n const key = extensionSetKey(normalized);\n const outputDirectory = path.resolve(options.outputDirectory ?? setDirectory);\n const outputName = safeOutputName(options.outputName ?? 'doom-set');\n fs.mkdirSync(outputDirectory, { recursive: true });\n // One graph can be emitted under several named mode artifacts. Keep their\n // cache records separate without changing extensionSetKey, which is the\n // runtime lookup key for an exact activation order.\n const outputKey = createHash('sha256').update(outputDirectory).update(outputName).digest('hex').slice(0, HASH_LENGTH);\n const manifestPath = path.join(setDirectory, `${key}.${outputKey}${MANIFEST_SUFFIX}`);\n const previous = readSetManifest(manifestPath);\n if (previous && manifestIsFresh(previous, normalized)) return previous.output;\n\n const inputs = new Set(normalized);\n const source = extensionSetSource(normalized);\n const { rolldown } = await import('rolldown');\n const build = await rolldown({\n input: VIRTUAL_SET_ENTRY,\n platform: 'node',\n plugins: [\n {\n name: 'doom-set-entry',\n resolveId(id: string): string | null {\n return id === VIRTUAL_SET_ENTRY ? id : null;\n },\n load(id: string): string | null {\n return id === VIRTUAL_SET_ENTRY ? source : null;\n },\n },\n setExternalResolver(inputs),\n preserveImportMetaUrl(),\n inputCollector(inputs),\n ],\n onLog: failUnresolvedImport,\n });\n let code: string;\n try {\n const generated = await build.generate({ format: 'esm', sourcemap: false, minify: true, codeSplitting: false });\n const chunk = generated.output.find((output) => output.type === 'chunk');\n if (!chunk || chunk.type !== 'chunk') throw new Error('Rolldown produced no extension bundle');\n code = chunk.code;\n } finally {\n await build.close();\n }\n\n const contentKey = createHash('sha256').update(SET_CACHE_VERSION).update(code).digest('hex').slice(0, HASH_LENGTH);\n const output = path.join(outputDirectory, `${outputName}.${contentKey}${COMPILED_SUFFIX}`);\n if (!fs.existsSync(output)) writeAtomic(output, code);\n const inputFingerprints = [...inputs]\n .map(fingerprint)\n .filter((value): value is InputFingerprint => value !== undefined)\n .sort((left, right) => left.path.localeCompare(right.path));\n writeAtomic(\n manifestPath,\n `${JSON.stringify({ version: SET_CACHE_VERSION, entries: normalized, output, inputs: inputFingerprints }, null, 2)}\\n`,\n );\n return output;\n}\n\n/**\n * Bundles `entry` into `cacheDirectory` and returns the compiled path.\n *\n * Non-TypeScript entries are returned untouched. A cache hit skips the build.\n */\nexport async function compileExtension(entry: string, cacheDirectory: string): Promise<string> {\n if (!isTypeScriptEntry(entry)) return entry;\n\n const output = path.join(cacheDirectory, compiledName(entry, cacheKey(entry)));\n if (fs.existsSync(output)) return output;\n\n // Rolldown loads a native module and a sizeable parser graph. Sync is the\n // only command that needs it, and a cache hit does not, so keep it off every\n // other CLI startup path.\n const { rolldown } = await import('rolldown');\n const build = await rolldown({\n input: entry,\n platform: 'node',\n plugins: [externalResolver(), preserveImportMetaUrl()],\n onLog: failUnresolvedImport,\n });\n try {\n // One file per extension. Several of them import lazily, and code splitting\n // would emit sibling chunks that a single output path cannot describe.\n await build.write({ file: output, format: 'esm', sourcemap: false, minify: true, codeSplitting: false });\n } finally {\n await build.close();\n }\n return output;\n}\n\n/**\n * Compiles every TypeScript entry in a resolution map, leaving the rest as-is.\n *\n * A failure is not fatal: the original path is kept so Pi falls back to loading\n * the TypeScript through jiti, which is what it did before this existed.\n */\nexport async function compileResolvedEntries(\n resolved: Record<string, string>,\n cacheDirectory: string,\n): Promise<Record<string, string>> {\n fs.mkdirSync(cacheDirectory, { recursive: true });\n const compiled: Record<string, string> = {};\n for (const [key, target] of Object.entries(resolved)) {\n try {\n compiled[key] = await compileExtension(target, cacheDirectory);\n } catch {\n compiled[key] = target;\n }\n }\n return compiled;\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- const e=require(`../_virtual/_rolldown/runtime.cjs`),t=require(`../utils/json.cjs`);require(`../utils/index.cjs`);const n=require(`./extensionAssembler.cjs`),r=require(`./extensionCompiler.cjs`);let i=require(`node:path`);i=e.__toESM(i,1);const a=i.default.join(`.pi`,`doom`,`cache`),o=i.default.join(`.pi`,`doom`,`dist`);function s(e){let t={agents:e.options.agents,preset:e.options.preset,mcpEntry:e.mcpEntry,personaEntry:e.personaEntry,layers:e.selectedLayers,majorModesConfig:e.majorModesConfig};return{extensions:n.assembleExtensions({...t,autoStop:e.options.autoStop,mute:e.options.mute,workflowExtension:e.workflowExtension}),childExtensions:n.assembleChildExtensions({...t,agents:!1,autoStop:!1,mute:!0})}}function c(e){return{majorMode:e.majorMode,domains:[...e.domains],profile:e.profile,preset:e.preset,mute:e.mute,autoStop:e.autoStop,agents:e.agents,mcp:e.mcp}}function l(e){return[e.majorMode,e.mute?`mute`:void 0,e.autoStop?`auto-stop`:void 0].filter(e=>!!e).join(`.`)}function u(e){return e.replace(/\.mjs$/,`.manifest.json`)}async function d(e){let n=e.extensionPaths.map(e=>i.default.resolve(e)),s={...e.selection,domains:[...e.selection.domains]},c=await r.compileExtensionSet(n,e.cacheDirectory??i.default.join(e.repoRoot,a),{outputDirectory:e.outputDirectory??i.default.join(e.repoRoot,o),outputName:e.outputName??l(s)}),d=u(c),f={version:1,artifact:c,selection:s,extensionPaths:n,childExtensionPaths:(e.childExtensionPaths??[]).map(e=>i.default.resolve(e)),resources:{skillPaths:(e.skillPaths??[]).map(e=>i.default.resolve(e)),pluginRoots:(e.pluginRoots??[]).map(e=>i.default.resolve(e))}};return t.writeFileAtomic(d,`${JSON.stringify(f,null,2)}\n`),{bundle:c,manifest:d}}async function f(e,t=s(e)){let n=await d({repoRoot:e.options.repoRoot,selection:c(e.options),extensionPaths:[...t.extensions].reverse(),childExtensionPaths:t.childExtensions,skillPaths:e.resources.skillDirectories,pluginRoots:e.plugins.map(e=>e.directory)});return{...t,...n}}exports.buildRuntimeBundle=f,exports.compileModeExtension=d,exports.createRuntimeExtensionPlan=s;
1
+ const e=require(`../_virtual/_rolldown/runtime.cjs`),t=require(`../utils/json.cjs`);require(`../utils/index.cjs`);const n=require(`./extensionAssembler.cjs`),r=require(`./extensionCompiler.cjs`);let i=require(`node:path`);i=e.__toESM(i,1);const a=i.default.join(`.pi`,`doom`,`cache`),o=i.default.join(`.pi`,`doom`,`dist`);function s(e){let t={agents:e.options.agents,preset:e.options.preset,mcpEntry:e.mcpEntry,personaEntry:e.personaEntry,layers:e.selectedLayers,majorModesConfig:e.majorModesConfig,resolvers:n.createLayerResolvers(e.options.repoRoot)};return{extensions:n.assembleExtensions({...t,autoStop:e.options.autoStop,mute:e.options.mute,workflowExtension:e.workflowExtension}),childExtensions:n.assembleChildExtensions({...t,agents:!1,autoStop:!1,mute:!0})}}function c(e){return{majorMode:e.majorMode,domains:[...e.domains],profile:e.profile,preset:e.preset,mute:e.mute,autoStop:e.autoStop,agents:e.agents,mcp:e.mcp}}function l(e){return[e.majorMode,e.mute?`mute`:void 0,e.autoStop?`auto-stop`:void 0].filter(e=>!!e).join(`.`)}function u(e){return e.replace(/\.mjs$/,`.manifest.json`)}async function d(e){let n=e.extensionPaths.map(e=>i.default.resolve(e)),s={...e.selection,domains:[...e.selection.domains]},c=await r.compileExtensionSet(n,e.cacheDirectory??i.default.join(e.repoRoot,a),{outputDirectory:e.outputDirectory??i.default.join(e.repoRoot,o),outputName:e.outputName??l(s)}),d=u(c),f={version:1,artifact:c,selection:s,extensionPaths:n,childExtensionPaths:(e.childExtensionPaths??[]).map(e=>i.default.resolve(e)),resources:{skillPaths:(e.skillPaths??[]).map(e=>i.default.resolve(e)),pluginRoots:(e.pluginRoots??[]).map(e=>i.default.resolve(e))}};return t.writeFileAtomic(d,`${JSON.stringify(f,null,2)}\n`),{bundle:c,manifest:d}}async function f(e,t=s(e)){let n=await d({repoRoot:e.options.repoRoot,selection:c(e.options),extensionPaths:[...t.extensions].reverse(),childExtensionPaths:t.childExtensions,skillPaths:e.resources.skillDirectories,pluginRoots:e.plugins.map(e=>e.directory)});return{...t,...n}}exports.buildRuntimeBundle=f,exports.compileModeExtension=d,exports.createRuntimeExtensionPlan=s;
2
2
  //# sourceMappingURL=runtimeBundle.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtimeBundle.cjs","names":["path","assembleExtensions","assembleChildExtensions","compileExtensionSet"],"sources":["../../src/services/runtimeBundle.ts"],"sourcesContent":["import path from 'node:path';\nimport type { HarnessOptions } from '../interfaces/index.ts';\nimport { writeFileAtomic } from '../utils/index.ts';\nimport type { HarnessContext } from './harnessContext.ts';\nimport { assembleChildExtensions, assembleExtensions } from './extensionAssembler.ts';\nimport { compileExtensionSet } from './extensionCompiler.ts';\n\nconst EXTENSION_CACHE_DIRECTORY = path.join('.pi', 'doom', 'cache');\nconst MODE_DIST_DIRECTORY = path.join('.pi', 'doom', 'dist');\nconst MODE_MANIFEST_VERSION = 1;\n\nexport interface RuntimeExtensionPlan {\n /** Pi CLI order. Pi evaluates explicit extension arguments in reverse. */\n extensions: string[];\n childExtensions: string[];\n}\n\nexport interface RuntimeBundleBuild extends RuntimeExtensionPlan {\n bundle: string;\n manifest: string;\n}\n\nexport interface ModeBuildSelection {\n majorMode: string;\n domains: string[];\n profile?: string;\n preset: string;\n mute: boolean;\n autoStop: boolean;\n agents: boolean;\n mcp: boolean;\n}\n\nexport interface ModeBuildManifest {\n version: number;\n artifact: string;\n selection: ModeBuildSelection;\n /** Original source entries in the order their factories activate. */\n extensionPaths: string[];\n /** Original source entries inherited by detached child agents. */\n childExtensionPaths: string[];\n resources: {\n /** Exact SKILL.md paths; never derived from the bundled artifact path. */\n skillPaths: string[];\n pluginRoots: string[];\n };\n}\n\nexport interface ModeExtensionBuildOptions {\n repoRoot: string;\n selection: ModeBuildSelection;\n extensionPaths: readonly string[];\n childExtensionPaths?: readonly string[];\n skillPaths?: readonly string[];\n pluginRoots?: readonly string[];\n outputName?: string;\n cacheDirectory?: string;\n outputDirectory?: string;\n}\n\n/** Resolves the parent and detached-child extension lists for one launch. */\nexport function createRuntimeExtensionPlan(context: HarnessContext): RuntimeExtensionPlan {\n const common = {\n agents: context.options.agents,\n preset: context.options.preset,\n mcpEntry: context.mcpEntry,\n personaEntry: context.personaEntry,\n layers: context.selectedLayers,\n majorModesConfig: context.majorModesConfig,\n };\n return {\n extensions: assembleExtensions({\n ...common,\n autoStop: context.options.autoStop,\n mute: context.options.mute,\n workflowExtension: context.workflowExtension,\n }),\n childExtensions: assembleChildExtensions({\n ...common,\n agents: false,\n autoStop: false,\n mute: true,\n }),\n };\n}\n\nfunction modeSelection(options: HarnessOptions): ModeBuildSelection {\n return {\n majorMode: options.majorMode,\n domains: [...options.domains],\n profile: options.profile,\n preset: options.preset,\n mute: options.mute,\n autoStop: options.autoStop,\n agents: options.agents,\n mcp: options.mcp,\n };\n}\n\nfunction modeOutputName(selection: ModeBuildSelection): string {\n return [selection.majorMode, selection.mute ? 'mute' : undefined, selection.autoStop ? 'auto-stop' : undefined]\n .filter((value): value is string => Boolean(value))\n .join('.');\n}\n\nfunction manifestPathFor(bundle: string): string {\n return bundle.replace(/\\.mjs$/, '.manifest.json');\n}\n\n/**\n * Emits one named mode extension and its original-path resource manifest.\n *\n * Extension code lives in dist while graph fingerprints remain in cache. Skill\n * loading never follows the bundle's import.meta.url: Pi receives the exact\n * original SKILL.md paths recorded here and in harness state.\n */\nexport async function compileModeExtension(options: ModeExtensionBuildOptions): Promise<{\n bundle: string;\n manifest: string;\n}> {\n const extensionPaths = options.extensionPaths.map((entry) => path.resolve(entry));\n const selection = { ...options.selection, domains: [...options.selection.domains] };\n const bundle = await compileExtensionSet(\n extensionPaths,\n options.cacheDirectory ?? path.join(options.repoRoot, EXTENSION_CACHE_DIRECTORY),\n {\n outputDirectory: options.outputDirectory ?? path.join(options.repoRoot, MODE_DIST_DIRECTORY),\n outputName: options.outputName ?? modeOutputName(selection),\n },\n );\n const manifest = manifestPathFor(bundle);\n const contents: ModeBuildManifest = {\n version: MODE_MANIFEST_VERSION,\n artifact: bundle,\n selection,\n extensionPaths,\n childExtensionPaths: (options.childExtensionPaths ?? []).map((entry) => path.resolve(entry)),\n resources: {\n skillPaths: (options.skillPaths ?? []).map((entry) => path.resolve(entry)),\n pluginRoots: (options.pluginRoots ?? []).map((entry) => path.resolve(entry)),\n },\n };\n writeFileAtomic(manifest, `${JSON.stringify(contents, null, 2)}\\n`);\n return { bundle, manifest };\n}\n\n/** Compiles the exact Pi load order used by the selected launch matrix. */\nexport async function buildRuntimeBundle(\n context: HarnessContext,\n plan: RuntimeExtensionPlan = createRuntimeExtensionPlan(context),\n): Promise<RuntimeBundleBuild> {\n const built = await compileModeExtension({\n repoRoot: context.options.repoRoot,\n selection: modeSelection(context.options),\n extensionPaths: [...plan.extensions].reverse(),\n childExtensionPaths: plan.childExtensions,\n skillPaths: context.resources.skillDirectories,\n pluginRoots: context.plugins.map((plugin) => plugin.directory),\n });\n return { ...plan, ...built };\n}\n"],"mappings":"+OAOA,MAAM,EAA4BA,EAAAA,QAAK,KAAK,MAAO,OAAQ,QAAQ,CAC7D,EAAsBA,EAAAA,QAAK,KAAK,MAAO,OAAQ,OAAO,CAqD5D,SAAgB,EAA2B,EAA+C,CACxF,IAAM,EAAS,CACb,OAAQ,EAAQ,QAAQ,OACxB,OAAQ,EAAQ,QAAQ,OACxB,SAAU,EAAQ,SAClB,aAAc,EAAQ,aACtB,OAAQ,EAAQ,eAChB,iBAAkB,EAAQ,iBAC3B,CACD,MAAO,CACL,WAAYC,EAAAA,mBAAmB,CAC7B,GAAG,EACH,SAAU,EAAQ,QAAQ,SAC1B,KAAM,EAAQ,QAAQ,KACtB,kBAAmB,EAAQ,kBAC5B,CAAC,CACF,gBAAiBC,EAAAA,wBAAwB,CACvC,GAAG,EACH,OAAQ,GACR,SAAU,GACV,KAAM,GACP,CAAC,CACH,CAGH,SAAS,EAAc,EAA6C,CAClE,MAAO,CACL,UAAW,EAAQ,UACnB,QAAS,CAAC,GAAG,EAAQ,QAAQ,CAC7B,QAAS,EAAQ,QACjB,OAAQ,EAAQ,OAChB,KAAM,EAAQ,KACd,SAAU,EAAQ,SAClB,OAAQ,EAAQ,OAChB,IAAK,EAAQ,IACd,CAGH,SAAS,EAAe,EAAuC,CAC7D,MAAO,CAAC,EAAU,UAAW,EAAU,KAAO,OAAS,IAAA,GAAW,EAAU,SAAW,YAAc,IAAA,GAAU,CAC5G,OAAQ,GAA2B,EAAQ,EAAO,CAClD,KAAK,IAAI,CAGd,SAAS,EAAgB,EAAwB,CAC/C,OAAO,EAAO,QAAQ,SAAU,iBAAiB,CAUnD,eAAsB,EAAqB,EAGxC,CACD,IAAM,EAAiB,EAAQ,eAAe,IAAK,GAAUF,EAAAA,QAAK,QAAQ,EAAM,CAAC,CAC3E,EAAY,CAAE,GAAG,EAAQ,UAAW,QAAS,CAAC,GAAG,EAAQ,UAAU,QAAQ,CAAE,CAC7E,EAAS,MAAMG,EAAAA,oBACnB,EACA,EAAQ,gBAAkBH,EAAAA,QAAK,KAAK,EAAQ,SAAU,EAA0B,CAChF,CACE,gBAAiB,EAAQ,iBAAmBA,EAAAA,QAAK,KAAK,EAAQ,SAAU,EAAoB,CAC5F,WAAY,EAAQ,YAAc,EAAe,EAAU,CAC5D,CACF,CACK,EAAW,EAAgB,EAAO,CAClC,EAA8B,CAClC,QAAS,EACT,SAAU,EACV,YACA,iBACA,qBAAsB,EAAQ,qBAAuB,EAAE,EAAE,IAAK,GAAUA,EAAAA,QAAK,QAAQ,EAAM,CAAC,CAC5F,UAAW,CACT,YAAa,EAAQ,YAAc,EAAE,EAAE,IAAK,GAAUA,EAAAA,QAAK,QAAQ,EAAM,CAAC,CAC1E,aAAc,EAAQ,aAAe,EAAE,EAAE,IAAK,GAAUA,EAAAA,QAAK,QAAQ,EAAM,CAAC,CAC7E,CACF,CAED,OADA,EAAA,gBAAgB,EAAU,GAAG,KAAK,UAAU,EAAU,KAAM,EAAE,CAAC,IAAI,CAC5D,CAAE,SAAQ,WAAU,CAI7B,eAAsB,EACpB,EACA,EAA6B,EAA2B,EAAQ,CACnC,CAC7B,IAAM,EAAQ,MAAM,EAAqB,CACvC,SAAU,EAAQ,QAAQ,SAC1B,UAAW,EAAc,EAAQ,QAAQ,CACzC,eAAgB,CAAC,GAAG,EAAK,WAAW,CAAC,SAAS,CAC9C,oBAAqB,EAAK,gBAC1B,WAAY,EAAQ,UAAU,iBAC9B,YAAa,EAAQ,QAAQ,IAAK,GAAW,EAAO,UAAU,CAC/D,CAAC,CACF,MAAO,CAAE,GAAG,EAAM,GAAG,EAAO"}
1
+ {"version":3,"file":"runtimeBundle.cjs","names":["path","createLayerResolvers","assembleExtensions","assembleChildExtensions","compileExtensionSet"],"sources":["../../src/services/runtimeBundle.ts"],"sourcesContent":["import path from 'node:path';\nimport type { HarnessOptions } from '../interfaces/index.ts';\nimport { writeFileAtomic } from '../utils/index.ts';\nimport { assembleChildExtensions, assembleExtensions, createLayerResolvers } from './extensionAssembler.ts';\nimport { compileExtensionSet } from './extensionCompiler.ts';\nimport type { HarnessContext } from './harnessContext.ts';\n\nconst EXTENSION_CACHE_DIRECTORY = path.join('.pi', 'doom', 'cache');\nconst MODE_DIST_DIRECTORY = path.join('.pi', 'doom', 'dist');\nconst MODE_MANIFEST_VERSION = 1;\n\nexport interface RuntimeExtensionPlan {\n /** Pi CLI order. Pi evaluates explicit extension arguments in reverse. */\n extensions: string[];\n childExtensions: string[];\n}\n\nexport interface RuntimeBundleBuild extends RuntimeExtensionPlan {\n bundle: string;\n manifest: string;\n}\n\nexport interface ModeBuildSelection {\n majorMode: string;\n domains: string[];\n profile?: string;\n preset: string;\n mute: boolean;\n autoStop: boolean;\n agents: boolean;\n mcp: boolean;\n}\n\nexport interface ModeBuildManifest {\n version: number;\n artifact: string;\n selection: ModeBuildSelection;\n /** Original source entries in the order their factories activate. */\n extensionPaths: string[];\n /** Original source entries inherited by detached child agents. */\n childExtensionPaths: string[];\n resources: {\n /** Exact SKILL.md paths; never derived from the bundled artifact path. */\n skillPaths: string[];\n pluginRoots: string[];\n };\n}\n\nexport interface ModeExtensionBuildOptions {\n repoRoot: string;\n selection: ModeBuildSelection;\n extensionPaths: readonly string[];\n childExtensionPaths?: readonly string[];\n skillPaths?: readonly string[];\n pluginRoots?: readonly string[];\n outputName?: string;\n cacheDirectory?: string;\n outputDirectory?: string;\n}\n\n/** Resolves the parent and detached-child extension lists for one launch. */\nexport function createRuntimeExtensionPlan(context: HarnessContext): RuntimeExtensionPlan {\n const common = {\n agents: context.options.agents,\n preset: context.options.preset,\n mcpEntry: context.mcpEntry,\n personaEntry: context.personaEntry,\n layers: context.selectedLayers,\n majorModesConfig: context.majorModesConfig,\n resolvers: createLayerResolvers(context.options.repoRoot),\n };\n return {\n extensions: assembleExtensions({\n ...common,\n autoStop: context.options.autoStop,\n mute: context.options.mute,\n workflowExtension: context.workflowExtension,\n }),\n childExtensions: assembleChildExtensions({\n ...common,\n agents: false,\n autoStop: false,\n mute: true,\n }),\n };\n}\n\nfunction modeSelection(options: HarnessOptions): ModeBuildSelection {\n return {\n majorMode: options.majorMode,\n domains: [...options.domains],\n profile: options.profile,\n preset: options.preset,\n mute: options.mute,\n autoStop: options.autoStop,\n agents: options.agents,\n mcp: options.mcp,\n };\n}\n\nfunction modeOutputName(selection: ModeBuildSelection): string {\n return [selection.majorMode, selection.mute ? 'mute' : undefined, selection.autoStop ? 'auto-stop' : undefined]\n .filter((value): value is string => Boolean(value))\n .join('.');\n}\n\nfunction manifestPathFor(bundle: string): string {\n return bundle.replace(/\\.mjs$/, '.manifest.json');\n}\n\n/**\n * Emits one named mode extension and its original-path resource manifest.\n *\n * Extension code lives in dist while graph fingerprints remain in cache. Skill\n * loading never follows the bundle's import.meta.url: Pi receives the exact\n * original SKILL.md paths recorded here and in harness state.\n */\nexport async function compileModeExtension(options: ModeExtensionBuildOptions): Promise<{\n bundle: string;\n manifest: string;\n}> {\n const extensionPaths = options.extensionPaths.map((entry) => path.resolve(entry));\n const selection = { ...options.selection, domains: [...options.selection.domains] };\n const bundle = await compileExtensionSet(\n extensionPaths,\n options.cacheDirectory ?? path.join(options.repoRoot, EXTENSION_CACHE_DIRECTORY),\n {\n outputDirectory: options.outputDirectory ?? path.join(options.repoRoot, MODE_DIST_DIRECTORY),\n outputName: options.outputName ?? modeOutputName(selection),\n },\n );\n const manifest = manifestPathFor(bundle);\n const contents: ModeBuildManifest = {\n version: MODE_MANIFEST_VERSION,\n artifact: bundle,\n selection,\n extensionPaths,\n childExtensionPaths: (options.childExtensionPaths ?? []).map((entry) => path.resolve(entry)),\n resources: {\n skillPaths: (options.skillPaths ?? []).map((entry) => path.resolve(entry)),\n pluginRoots: (options.pluginRoots ?? []).map((entry) => path.resolve(entry)),\n },\n };\n writeFileAtomic(manifest, `${JSON.stringify(contents, null, 2)}\\n`);\n return { bundle, manifest };\n}\n\n/** Compiles the exact Pi load order used by the selected launch matrix. */\nexport async function buildRuntimeBundle(\n context: HarnessContext,\n plan: RuntimeExtensionPlan = createRuntimeExtensionPlan(context),\n): Promise<RuntimeBundleBuild> {\n const built = await compileModeExtension({\n repoRoot: context.options.repoRoot,\n selection: modeSelection(context.options),\n extensionPaths: [...plan.extensions].reverse(),\n childExtensionPaths: plan.childExtensions,\n skillPaths: context.resources.skillDirectories,\n pluginRoots: context.plugins.map((plugin) => plugin.directory),\n });\n return { ...plan, ...built };\n}\n"],"mappings":"+OAOA,MAAM,EAA4BA,EAAAA,QAAK,KAAK,MAAO,OAAQ,QAAQ,CAC7D,EAAsBA,EAAAA,QAAK,KAAK,MAAO,OAAQ,OAAO,CAqD5D,SAAgB,EAA2B,EAA+C,CACxF,IAAM,EAAS,CACb,OAAQ,EAAQ,QAAQ,OACxB,OAAQ,EAAQ,QAAQ,OACxB,SAAU,EAAQ,SAClB,aAAc,EAAQ,aACtB,OAAQ,EAAQ,eAChB,iBAAkB,EAAQ,iBAC1B,UAAWC,EAAAA,qBAAqB,EAAQ,QAAQ,SAAS,CAC1D,CACD,MAAO,CACL,WAAYC,EAAAA,mBAAmB,CAC7B,GAAG,EACH,SAAU,EAAQ,QAAQ,SAC1B,KAAM,EAAQ,QAAQ,KACtB,kBAAmB,EAAQ,kBAC5B,CAAC,CACF,gBAAiBC,EAAAA,wBAAwB,CACvC,GAAG,EACH,OAAQ,GACR,SAAU,GACV,KAAM,GACP,CAAC,CACH,CAGH,SAAS,EAAc,EAA6C,CAClE,MAAO,CACL,UAAW,EAAQ,UACnB,QAAS,CAAC,GAAG,EAAQ,QAAQ,CAC7B,QAAS,EAAQ,QACjB,OAAQ,EAAQ,OAChB,KAAM,EAAQ,KACd,SAAU,EAAQ,SAClB,OAAQ,EAAQ,OAChB,IAAK,EAAQ,IACd,CAGH,SAAS,EAAe,EAAuC,CAC7D,MAAO,CAAC,EAAU,UAAW,EAAU,KAAO,OAAS,IAAA,GAAW,EAAU,SAAW,YAAc,IAAA,GAAU,CAC5G,OAAQ,GAA2B,EAAQ,EAAO,CAClD,KAAK,IAAI,CAGd,SAAS,EAAgB,EAAwB,CAC/C,OAAO,EAAO,QAAQ,SAAU,iBAAiB,CAUnD,eAAsB,EAAqB,EAGxC,CACD,IAAM,EAAiB,EAAQ,eAAe,IAAK,GAAUH,EAAAA,QAAK,QAAQ,EAAM,CAAC,CAC3E,EAAY,CAAE,GAAG,EAAQ,UAAW,QAAS,CAAC,GAAG,EAAQ,UAAU,QAAQ,CAAE,CAC7E,EAAS,MAAMI,EAAAA,oBACnB,EACA,EAAQ,gBAAkBJ,EAAAA,QAAK,KAAK,EAAQ,SAAU,EAA0B,CAChF,CACE,gBAAiB,EAAQ,iBAAmBA,EAAAA,QAAK,KAAK,EAAQ,SAAU,EAAoB,CAC5F,WAAY,EAAQ,YAAc,EAAe,EAAU,CAC5D,CACF,CACK,EAAW,EAAgB,EAAO,CAClC,EAA8B,CAClC,QAAS,EACT,SAAU,EACV,YACA,iBACA,qBAAsB,EAAQ,qBAAuB,EAAE,EAAE,IAAK,GAAUA,EAAAA,QAAK,QAAQ,EAAM,CAAC,CAC5F,UAAW,CACT,YAAa,EAAQ,YAAc,EAAE,EAAE,IAAK,GAAUA,EAAAA,QAAK,QAAQ,EAAM,CAAC,CAC1E,aAAc,EAAQ,aAAe,EAAE,EAAE,IAAK,GAAUA,EAAAA,QAAK,QAAQ,EAAM,CAAC,CAC7E,CACF,CAED,OADA,EAAA,gBAAgB,EAAU,GAAG,KAAK,UAAU,EAAU,KAAM,EAAE,CAAC,IAAI,CAC5D,CAAE,SAAQ,WAAU,CAI7B,eAAsB,EACpB,EACA,EAA6B,EAA2B,EAAQ,CACnC,CAC7B,IAAM,EAAQ,MAAM,EAAqB,CACvC,SAAU,EAAQ,QAAQ,SAC1B,UAAW,EAAc,EAAQ,QAAQ,CACzC,eAAgB,CAAC,GAAG,EAAK,WAAW,CAAC,SAAS,CAC9C,oBAAqB,EAAK,gBAC1B,WAAY,EAAQ,UAAU,iBAC9B,YAAa,EAAQ,QAAQ,IAAK,GAAW,EAAO,UAAU,CAC/D,CAAC,CACF,MAAO,CAAE,GAAG,EAAM,GAAG,EAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"runtimeBundle.d.cts","names":[],"sources":["../../src/services/runtimeBundle.ts"],"mappings":";;;UAWiB,oBAAA;;EAEf,UAAA;EACA,eAAA;AAAA;AAAA,UAGe,kBAAA,SAA2B,oBAAA;EAC1C,MAAA;EACA,QAAA;AAAA;AAAA,UAGe,kBAAA;EACf,SAAA;EACA,OAAA;EACA,OAAA;EACA,MAAA;EACA,IAAA;EACA,QAAA;EACA,MAAA;EACA,GAAA;AAAA;AAAA,UAGe,iBAAA;EACf,OAAA;EACA,QAAA;EACA,SAAA,EAAW,kBAAA;EAVX;EAYA,cAAA;EAVA;EAYA,mBAAA;EACA,SAAA;IAXG,0EAaD,UAAA;IACA,WAAA;EAAA;AAAA;AAAA,UAIa,yBAAA;EACf,QAAA;EACA,SAAA,EAAW,kBAAA;EACX,cAAA;EACA,mBAAA;EACA,UAAA;EACA,WAAA;EACA,UAAA;EACA,cAAA;EACA,eAAA;AAAA;;iBAIc,0BAAA,CAA2B,OAAA,EAAS,cAAA,GAAiB,oBAAA;;;;;;;;iBAuD/C,oBAAA,CAAqB,OAAA,EAAS,yBAAA,GAA4B,OAAA;EAC9E,MAAA;EACA,QAAA;AAAA;;iBA6BoB,kBAAA,CACpB,OAAA,EAAS,cAAA,EACT,IAAA,GAAM,oBAAA,GACL,OAAA,CAAQ,kBAAA"}
1
+ {"version":3,"file":"runtimeBundle.d.cts","names":[],"sources":["../../src/services/runtimeBundle.ts"],"mappings":";;;UAWiB,oBAAA;;EAEf,UAAA;EACA,eAAA;AAAA;AAAA,UAGe,kBAAA,SAA2B,oBAAA;EAC1C,MAAA;EACA,QAAA;AAAA;AAAA,UAGe,kBAAA;EACf,SAAA;EACA,OAAA;EACA,OAAA;EACA,MAAA;EACA,IAAA;EACA,QAAA;EACA,MAAA;EACA,GAAA;AAAA;AAAA,UAGe,iBAAA;EACf,OAAA;EACA,QAAA;EACA,SAAA,EAAW,kBAAA;EAVX;EAYA,cAAA;EAVA;EAYA,mBAAA;EACA,SAAA;IAXG,0EAaD,UAAA;IACA,WAAA;EAAA;AAAA;AAAA,UAIa,yBAAA;EACf,QAAA;EACA,SAAA,EAAW,kBAAA;EACX,cAAA;EACA,mBAAA;EACA,UAAA;EACA,WAAA;EACA,UAAA;EACA,cAAA;EACA,eAAA;AAAA;;iBAIc,0BAAA,CAA2B,OAAA,EAAS,cAAA,GAAiB,oBAAA;;;;;;;;iBAwD/C,oBAAA,CAAqB,OAAA,EAAS,yBAAA,GAA4B,OAAA;EAC9E,MAAA;EACA,QAAA;AAAA;;iBA6BoB,kBAAA,CACpB,OAAA,EAAS,cAAA,EACT,IAAA,GAAM,oBAAA,GACL,OAAA,CAAQ,kBAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"runtimeBundle.d.mts","names":[],"sources":["../../src/services/runtimeBundle.ts"],"mappings":";;;UAWiB,oBAAA;;EAEf,UAAA;EACA,eAAA;AAAA;AAAA,UAGe,kBAAA,SAA2B,oBAAA;EAC1C,MAAA;EACA,QAAA;AAAA;AAAA,UAGe,kBAAA;EACf,SAAA;EACA,OAAA;EACA,OAAA;EACA,MAAA;EACA,IAAA;EACA,QAAA;EACA,MAAA;EACA,GAAA;AAAA;AAAA,UAGe,iBAAA;EACf,OAAA;EACA,QAAA;EACA,SAAA,EAAW,kBAAA;EAVX;EAYA,cAAA;EAVA;EAYA,mBAAA;EACA,SAAA;IAXG,0EAaD,UAAA;IACA,WAAA;EAAA;AAAA;AAAA,UAIa,yBAAA;EACf,QAAA;EACA,SAAA,EAAW,kBAAA;EACX,cAAA;EACA,mBAAA;EACA,UAAA;EACA,WAAA;EACA,UAAA;EACA,cAAA;EACA,eAAA;AAAA;;iBAIc,0BAAA,CAA2B,OAAA,EAAS,cAAA,GAAiB,oBAAA;;;;;;;;iBAuD/C,oBAAA,CAAqB,OAAA,EAAS,yBAAA,GAA4B,OAAA;EAC9E,MAAA;EACA,QAAA;AAAA;;iBA6BoB,kBAAA,CACpB,OAAA,EAAS,cAAA,EACT,IAAA,GAAM,oBAAA,GACL,OAAA,CAAQ,kBAAA"}
1
+ {"version":3,"file":"runtimeBundle.d.mts","names":[],"sources":["../../src/services/runtimeBundle.ts"],"mappings":";;;UAWiB,oBAAA;;EAEf,UAAA;EACA,eAAA;AAAA;AAAA,UAGe,kBAAA,SAA2B,oBAAA;EAC1C,MAAA;EACA,QAAA;AAAA;AAAA,UAGe,kBAAA;EACf,SAAA;EACA,OAAA;EACA,OAAA;EACA,MAAA;EACA,IAAA;EACA,QAAA;EACA,MAAA;EACA,GAAA;AAAA;AAAA,UAGe,iBAAA;EACf,OAAA;EACA,QAAA;EACA,SAAA,EAAW,kBAAA;EAVX;EAYA,cAAA;EAVA;EAYA,mBAAA;EACA,SAAA;IAXG,0EAaD,UAAA;IACA,WAAA;EAAA;AAAA;AAAA,UAIa,yBAAA;EACf,QAAA;EACA,SAAA,EAAW,kBAAA;EACX,cAAA;EACA,mBAAA;EACA,UAAA;EACA,WAAA;EACA,UAAA;EACA,cAAA;EACA,eAAA;AAAA;;iBAIc,0BAAA,CAA2B,OAAA,EAAS,cAAA,GAAiB,oBAAA;;;;;;;;iBAwD/C,oBAAA,CAAqB,OAAA,EAAS,yBAAA,GAA4B,OAAA;EAC9E,MAAA;EACA,QAAA;AAAA;;iBA6BoB,kBAAA,CACpB,OAAA,EAAS,cAAA,EACT,IAAA,GAAM,oBAAA,GACL,OAAA,CAAQ,kBAAA"}
@@ -1,2 +1,2 @@
1
- import{writeFileAtomic as e}from"../utils/json.mjs";import"../utils/index.mjs";import{assembleChildExtensions as t,assembleExtensions as n}from"./extensionAssembler.mjs";import{compileExtensionSet as r}from"./extensionCompiler.mjs";import i from"node:path";const a=i.join(`.pi`,`doom`,`cache`),o=i.join(`.pi`,`doom`,`dist`);function s(e){let r={agents:e.options.agents,preset:e.options.preset,mcpEntry:e.mcpEntry,personaEntry:e.personaEntry,layers:e.selectedLayers,majorModesConfig:e.majorModesConfig};return{extensions:n({...r,autoStop:e.options.autoStop,mute:e.options.mute,workflowExtension:e.workflowExtension}),childExtensions:t({...r,agents:!1,autoStop:!1,mute:!0})}}function c(e){return{majorMode:e.majorMode,domains:[...e.domains],profile:e.profile,preset:e.preset,mute:e.mute,autoStop:e.autoStop,agents:e.agents,mcp:e.mcp}}function l(e){return[e.majorMode,e.mute?`mute`:void 0,e.autoStop?`auto-stop`:void 0].filter(e=>!!e).join(`.`)}function u(e){return e.replace(/\.mjs$/,`.manifest.json`)}async function d(t){let n=t.extensionPaths.map(e=>i.resolve(e)),s={...t.selection,domains:[...t.selection.domains]},c=await r(n,t.cacheDirectory??i.join(t.repoRoot,a),{outputDirectory:t.outputDirectory??i.join(t.repoRoot,o),outputName:t.outputName??l(s)}),d=u(c),f={version:1,artifact:c,selection:s,extensionPaths:n,childExtensionPaths:(t.childExtensionPaths??[]).map(e=>i.resolve(e)),resources:{skillPaths:(t.skillPaths??[]).map(e=>i.resolve(e)),pluginRoots:(t.pluginRoots??[]).map(e=>i.resolve(e))}};return e(d,`${JSON.stringify(f,null,2)}\n`),{bundle:c,manifest:d}}async function f(e,t=s(e)){let n=await d({repoRoot:e.options.repoRoot,selection:c(e.options),extensionPaths:[...t.extensions].reverse(),childExtensionPaths:t.childExtensions,skillPaths:e.resources.skillDirectories,pluginRoots:e.plugins.map(e=>e.directory)});return{...t,...n}}export{f as buildRuntimeBundle,d as compileModeExtension,s as createRuntimeExtensionPlan};
1
+ import{writeFileAtomic as e}from"../utils/json.mjs";import"../utils/index.mjs";import{assembleChildExtensions as t,assembleExtensions as n,createLayerResolvers as r}from"./extensionAssembler.mjs";import{compileExtensionSet as i}from"./extensionCompiler.mjs";import a from"node:path";const o=a.join(`.pi`,`doom`,`cache`),s=a.join(`.pi`,`doom`,`dist`);function c(e){let i={agents:e.options.agents,preset:e.options.preset,mcpEntry:e.mcpEntry,personaEntry:e.personaEntry,layers:e.selectedLayers,majorModesConfig:e.majorModesConfig,resolvers:r(e.options.repoRoot)};return{extensions:n({...i,autoStop:e.options.autoStop,mute:e.options.mute,workflowExtension:e.workflowExtension}),childExtensions:t({...i,agents:!1,autoStop:!1,mute:!0})}}function l(e){return{majorMode:e.majorMode,domains:[...e.domains],profile:e.profile,preset:e.preset,mute:e.mute,autoStop:e.autoStop,agents:e.agents,mcp:e.mcp}}function u(e){return[e.majorMode,e.mute?`mute`:void 0,e.autoStop?`auto-stop`:void 0].filter(e=>!!e).join(`.`)}function d(e){return e.replace(/\.mjs$/,`.manifest.json`)}async function f(t){let n=t.extensionPaths.map(e=>a.resolve(e)),r={...t.selection,domains:[...t.selection.domains]},c=await i(n,t.cacheDirectory??a.join(t.repoRoot,o),{outputDirectory:t.outputDirectory??a.join(t.repoRoot,s),outputName:t.outputName??u(r)}),l=d(c),f={version:1,artifact:c,selection:r,extensionPaths:n,childExtensionPaths:(t.childExtensionPaths??[]).map(e=>a.resolve(e)),resources:{skillPaths:(t.skillPaths??[]).map(e=>a.resolve(e)),pluginRoots:(t.pluginRoots??[]).map(e=>a.resolve(e))}};return e(l,`${JSON.stringify(f,null,2)}\n`),{bundle:c,manifest:l}}async function p(e,t=c(e)){let n=await f({repoRoot:e.options.repoRoot,selection:l(e.options),extensionPaths:[...t.extensions].reverse(),childExtensionPaths:t.childExtensions,skillPaths:e.resources.skillDirectories,pluginRoots:e.plugins.map(e=>e.directory)});return{...t,...n}}export{p as buildRuntimeBundle,f as compileModeExtension,c as createRuntimeExtensionPlan};
2
2
  //# sourceMappingURL=runtimeBundle.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtimeBundle.mjs","names":[],"sources":["../../src/services/runtimeBundle.ts"],"sourcesContent":["import path from 'node:path';\nimport type { HarnessOptions } from '../interfaces/index.ts';\nimport { writeFileAtomic } from '../utils/index.ts';\nimport type { HarnessContext } from './harnessContext.ts';\nimport { assembleChildExtensions, assembleExtensions } from './extensionAssembler.ts';\nimport { compileExtensionSet } from './extensionCompiler.ts';\n\nconst EXTENSION_CACHE_DIRECTORY = path.join('.pi', 'doom', 'cache');\nconst MODE_DIST_DIRECTORY = path.join('.pi', 'doom', 'dist');\nconst MODE_MANIFEST_VERSION = 1;\n\nexport interface RuntimeExtensionPlan {\n /** Pi CLI order. Pi evaluates explicit extension arguments in reverse. */\n extensions: string[];\n childExtensions: string[];\n}\n\nexport interface RuntimeBundleBuild extends RuntimeExtensionPlan {\n bundle: string;\n manifest: string;\n}\n\nexport interface ModeBuildSelection {\n majorMode: string;\n domains: string[];\n profile?: string;\n preset: string;\n mute: boolean;\n autoStop: boolean;\n agents: boolean;\n mcp: boolean;\n}\n\nexport interface ModeBuildManifest {\n version: number;\n artifact: string;\n selection: ModeBuildSelection;\n /** Original source entries in the order their factories activate. */\n extensionPaths: string[];\n /** Original source entries inherited by detached child agents. */\n childExtensionPaths: string[];\n resources: {\n /** Exact SKILL.md paths; never derived from the bundled artifact path. */\n skillPaths: string[];\n pluginRoots: string[];\n };\n}\n\nexport interface ModeExtensionBuildOptions {\n repoRoot: string;\n selection: ModeBuildSelection;\n extensionPaths: readonly string[];\n childExtensionPaths?: readonly string[];\n skillPaths?: readonly string[];\n pluginRoots?: readonly string[];\n outputName?: string;\n cacheDirectory?: string;\n outputDirectory?: string;\n}\n\n/** Resolves the parent and detached-child extension lists for one launch. */\nexport function createRuntimeExtensionPlan(context: HarnessContext): RuntimeExtensionPlan {\n const common = {\n agents: context.options.agents,\n preset: context.options.preset,\n mcpEntry: context.mcpEntry,\n personaEntry: context.personaEntry,\n layers: context.selectedLayers,\n majorModesConfig: context.majorModesConfig,\n };\n return {\n extensions: assembleExtensions({\n ...common,\n autoStop: context.options.autoStop,\n mute: context.options.mute,\n workflowExtension: context.workflowExtension,\n }),\n childExtensions: assembleChildExtensions({\n ...common,\n agents: false,\n autoStop: false,\n mute: true,\n }),\n };\n}\n\nfunction modeSelection(options: HarnessOptions): ModeBuildSelection {\n return {\n majorMode: options.majorMode,\n domains: [...options.domains],\n profile: options.profile,\n preset: options.preset,\n mute: options.mute,\n autoStop: options.autoStop,\n agents: options.agents,\n mcp: options.mcp,\n };\n}\n\nfunction modeOutputName(selection: ModeBuildSelection): string {\n return [selection.majorMode, selection.mute ? 'mute' : undefined, selection.autoStop ? 'auto-stop' : undefined]\n .filter((value): value is string => Boolean(value))\n .join('.');\n}\n\nfunction manifestPathFor(bundle: string): string {\n return bundle.replace(/\\.mjs$/, '.manifest.json');\n}\n\n/**\n * Emits one named mode extension and its original-path resource manifest.\n *\n * Extension code lives in dist while graph fingerprints remain in cache. Skill\n * loading never follows the bundle's import.meta.url: Pi receives the exact\n * original SKILL.md paths recorded here and in harness state.\n */\nexport async function compileModeExtension(options: ModeExtensionBuildOptions): Promise<{\n bundle: string;\n manifest: string;\n}> {\n const extensionPaths = options.extensionPaths.map((entry) => path.resolve(entry));\n const selection = { ...options.selection, domains: [...options.selection.domains] };\n const bundle = await compileExtensionSet(\n extensionPaths,\n options.cacheDirectory ?? path.join(options.repoRoot, EXTENSION_CACHE_DIRECTORY),\n {\n outputDirectory: options.outputDirectory ?? path.join(options.repoRoot, MODE_DIST_DIRECTORY),\n outputName: options.outputName ?? modeOutputName(selection),\n },\n );\n const manifest = manifestPathFor(bundle);\n const contents: ModeBuildManifest = {\n version: MODE_MANIFEST_VERSION,\n artifact: bundle,\n selection,\n extensionPaths,\n childExtensionPaths: (options.childExtensionPaths ?? []).map((entry) => path.resolve(entry)),\n resources: {\n skillPaths: (options.skillPaths ?? []).map((entry) => path.resolve(entry)),\n pluginRoots: (options.pluginRoots ?? []).map((entry) => path.resolve(entry)),\n },\n };\n writeFileAtomic(manifest, `${JSON.stringify(contents, null, 2)}\\n`);\n return { bundle, manifest };\n}\n\n/** Compiles the exact Pi load order used by the selected launch matrix. */\nexport async function buildRuntimeBundle(\n context: HarnessContext,\n plan: RuntimeExtensionPlan = createRuntimeExtensionPlan(context),\n): Promise<RuntimeBundleBuild> {\n const built = await compileModeExtension({\n repoRoot: context.options.repoRoot,\n selection: modeSelection(context.options),\n extensionPaths: [...plan.extensions].reverse(),\n childExtensionPaths: plan.childExtensions,\n skillPaths: context.resources.skillDirectories,\n pluginRoots: context.plugins.map((plugin) => plugin.directory),\n });\n return { ...plan, ...built };\n}\n"],"mappings":"iQAOA,MAAM,EAA4B,EAAK,KAAK,MAAO,OAAQ,QAAQ,CAC7D,EAAsB,EAAK,KAAK,MAAO,OAAQ,OAAO,CAqD5D,SAAgB,EAA2B,EAA+C,CACxF,IAAM,EAAS,CACb,OAAQ,EAAQ,QAAQ,OACxB,OAAQ,EAAQ,QAAQ,OACxB,SAAU,EAAQ,SAClB,aAAc,EAAQ,aACtB,OAAQ,EAAQ,eAChB,iBAAkB,EAAQ,iBAC3B,CACD,MAAO,CACL,WAAY,EAAmB,CAC7B,GAAG,EACH,SAAU,EAAQ,QAAQ,SAC1B,KAAM,EAAQ,QAAQ,KACtB,kBAAmB,EAAQ,kBAC5B,CAAC,CACF,gBAAiB,EAAwB,CACvC,GAAG,EACH,OAAQ,GACR,SAAU,GACV,KAAM,GACP,CAAC,CACH,CAGH,SAAS,EAAc,EAA6C,CAClE,MAAO,CACL,UAAW,EAAQ,UACnB,QAAS,CAAC,GAAG,EAAQ,QAAQ,CAC7B,QAAS,EAAQ,QACjB,OAAQ,EAAQ,OAChB,KAAM,EAAQ,KACd,SAAU,EAAQ,SAClB,OAAQ,EAAQ,OAChB,IAAK,EAAQ,IACd,CAGH,SAAS,EAAe,EAAuC,CAC7D,MAAO,CAAC,EAAU,UAAW,EAAU,KAAO,OAAS,IAAA,GAAW,EAAU,SAAW,YAAc,IAAA,GAAU,CAC5G,OAAQ,GAA2B,EAAQ,EAAO,CAClD,KAAK,IAAI,CAGd,SAAS,EAAgB,EAAwB,CAC/C,OAAO,EAAO,QAAQ,SAAU,iBAAiB,CAUnD,eAAsB,EAAqB,EAGxC,CACD,IAAM,EAAiB,EAAQ,eAAe,IAAK,GAAU,EAAK,QAAQ,EAAM,CAAC,CAC3E,EAAY,CAAE,GAAG,EAAQ,UAAW,QAAS,CAAC,GAAG,EAAQ,UAAU,QAAQ,CAAE,CAC7E,EAAS,MAAM,EACnB,EACA,EAAQ,gBAAkB,EAAK,KAAK,EAAQ,SAAU,EAA0B,CAChF,CACE,gBAAiB,EAAQ,iBAAmB,EAAK,KAAK,EAAQ,SAAU,EAAoB,CAC5F,WAAY,EAAQ,YAAc,EAAe,EAAU,CAC5D,CACF,CACK,EAAW,EAAgB,EAAO,CAClC,EAA8B,CAClC,QAAS,EACT,SAAU,EACV,YACA,iBACA,qBAAsB,EAAQ,qBAAuB,EAAE,EAAE,IAAK,GAAU,EAAK,QAAQ,EAAM,CAAC,CAC5F,UAAW,CACT,YAAa,EAAQ,YAAc,EAAE,EAAE,IAAK,GAAU,EAAK,QAAQ,EAAM,CAAC,CAC1E,aAAc,EAAQ,aAAe,EAAE,EAAE,IAAK,GAAU,EAAK,QAAQ,EAAM,CAAC,CAC7E,CACF,CAED,OADA,EAAgB,EAAU,GAAG,KAAK,UAAU,EAAU,KAAM,EAAE,CAAC,IAAI,CAC5D,CAAE,SAAQ,WAAU,CAI7B,eAAsB,EACpB,EACA,EAA6B,EAA2B,EAAQ,CACnC,CAC7B,IAAM,EAAQ,MAAM,EAAqB,CACvC,SAAU,EAAQ,QAAQ,SAC1B,UAAW,EAAc,EAAQ,QAAQ,CACzC,eAAgB,CAAC,GAAG,EAAK,WAAW,CAAC,SAAS,CAC9C,oBAAqB,EAAK,gBAC1B,WAAY,EAAQ,UAAU,iBAC9B,YAAa,EAAQ,QAAQ,IAAK,GAAW,EAAO,UAAU,CAC/D,CAAC,CACF,MAAO,CAAE,GAAG,EAAM,GAAG,EAAO"}
1
+ {"version":3,"file":"runtimeBundle.mjs","names":[],"sources":["../../src/services/runtimeBundle.ts"],"sourcesContent":["import path from 'node:path';\nimport type { HarnessOptions } from '../interfaces/index.ts';\nimport { writeFileAtomic } from '../utils/index.ts';\nimport { assembleChildExtensions, assembleExtensions, createLayerResolvers } from './extensionAssembler.ts';\nimport { compileExtensionSet } from './extensionCompiler.ts';\nimport type { HarnessContext } from './harnessContext.ts';\n\nconst EXTENSION_CACHE_DIRECTORY = path.join('.pi', 'doom', 'cache');\nconst MODE_DIST_DIRECTORY = path.join('.pi', 'doom', 'dist');\nconst MODE_MANIFEST_VERSION = 1;\n\nexport interface RuntimeExtensionPlan {\n /** Pi CLI order. Pi evaluates explicit extension arguments in reverse. */\n extensions: string[];\n childExtensions: string[];\n}\n\nexport interface RuntimeBundleBuild extends RuntimeExtensionPlan {\n bundle: string;\n manifest: string;\n}\n\nexport interface ModeBuildSelection {\n majorMode: string;\n domains: string[];\n profile?: string;\n preset: string;\n mute: boolean;\n autoStop: boolean;\n agents: boolean;\n mcp: boolean;\n}\n\nexport interface ModeBuildManifest {\n version: number;\n artifact: string;\n selection: ModeBuildSelection;\n /** Original source entries in the order their factories activate. */\n extensionPaths: string[];\n /** Original source entries inherited by detached child agents. */\n childExtensionPaths: string[];\n resources: {\n /** Exact SKILL.md paths; never derived from the bundled artifact path. */\n skillPaths: string[];\n pluginRoots: string[];\n };\n}\n\nexport interface ModeExtensionBuildOptions {\n repoRoot: string;\n selection: ModeBuildSelection;\n extensionPaths: readonly string[];\n childExtensionPaths?: readonly string[];\n skillPaths?: readonly string[];\n pluginRoots?: readonly string[];\n outputName?: string;\n cacheDirectory?: string;\n outputDirectory?: string;\n}\n\n/** Resolves the parent and detached-child extension lists for one launch. */\nexport function createRuntimeExtensionPlan(context: HarnessContext): RuntimeExtensionPlan {\n const common = {\n agents: context.options.agents,\n preset: context.options.preset,\n mcpEntry: context.mcpEntry,\n personaEntry: context.personaEntry,\n layers: context.selectedLayers,\n majorModesConfig: context.majorModesConfig,\n resolvers: createLayerResolvers(context.options.repoRoot),\n };\n return {\n extensions: assembleExtensions({\n ...common,\n autoStop: context.options.autoStop,\n mute: context.options.mute,\n workflowExtension: context.workflowExtension,\n }),\n childExtensions: assembleChildExtensions({\n ...common,\n agents: false,\n autoStop: false,\n mute: true,\n }),\n };\n}\n\nfunction modeSelection(options: HarnessOptions): ModeBuildSelection {\n return {\n majorMode: options.majorMode,\n domains: [...options.domains],\n profile: options.profile,\n preset: options.preset,\n mute: options.mute,\n autoStop: options.autoStop,\n agents: options.agents,\n mcp: options.mcp,\n };\n}\n\nfunction modeOutputName(selection: ModeBuildSelection): string {\n return [selection.majorMode, selection.mute ? 'mute' : undefined, selection.autoStop ? 'auto-stop' : undefined]\n .filter((value): value is string => Boolean(value))\n .join('.');\n}\n\nfunction manifestPathFor(bundle: string): string {\n return bundle.replace(/\\.mjs$/, '.manifest.json');\n}\n\n/**\n * Emits one named mode extension and its original-path resource manifest.\n *\n * Extension code lives in dist while graph fingerprints remain in cache. Skill\n * loading never follows the bundle's import.meta.url: Pi receives the exact\n * original SKILL.md paths recorded here and in harness state.\n */\nexport async function compileModeExtension(options: ModeExtensionBuildOptions): Promise<{\n bundle: string;\n manifest: string;\n}> {\n const extensionPaths = options.extensionPaths.map((entry) => path.resolve(entry));\n const selection = { ...options.selection, domains: [...options.selection.domains] };\n const bundle = await compileExtensionSet(\n extensionPaths,\n options.cacheDirectory ?? path.join(options.repoRoot, EXTENSION_CACHE_DIRECTORY),\n {\n outputDirectory: options.outputDirectory ?? path.join(options.repoRoot, MODE_DIST_DIRECTORY),\n outputName: options.outputName ?? modeOutputName(selection),\n },\n );\n const manifest = manifestPathFor(bundle);\n const contents: ModeBuildManifest = {\n version: MODE_MANIFEST_VERSION,\n artifact: bundle,\n selection,\n extensionPaths,\n childExtensionPaths: (options.childExtensionPaths ?? []).map((entry) => path.resolve(entry)),\n resources: {\n skillPaths: (options.skillPaths ?? []).map((entry) => path.resolve(entry)),\n pluginRoots: (options.pluginRoots ?? []).map((entry) => path.resolve(entry)),\n },\n };\n writeFileAtomic(manifest, `${JSON.stringify(contents, null, 2)}\\n`);\n return { bundle, manifest };\n}\n\n/** Compiles the exact Pi load order used by the selected launch matrix. */\nexport async function buildRuntimeBundle(\n context: HarnessContext,\n plan: RuntimeExtensionPlan = createRuntimeExtensionPlan(context),\n): Promise<RuntimeBundleBuild> {\n const built = await compileModeExtension({\n repoRoot: context.options.repoRoot,\n selection: modeSelection(context.options),\n extensionPaths: [...plan.extensions].reverse(),\n childExtensionPaths: plan.childExtensions,\n skillPaths: context.resources.skillDirectories,\n pluginRoots: context.plugins.map((plugin) => plugin.directory),\n });\n return { ...plan, ...built };\n}\n"],"mappings":"2RAOA,MAAM,EAA4B,EAAK,KAAK,MAAO,OAAQ,QAAQ,CAC7D,EAAsB,EAAK,KAAK,MAAO,OAAQ,OAAO,CAqD5D,SAAgB,EAA2B,EAA+C,CACxF,IAAM,EAAS,CACb,OAAQ,EAAQ,QAAQ,OACxB,OAAQ,EAAQ,QAAQ,OACxB,SAAU,EAAQ,SAClB,aAAc,EAAQ,aACtB,OAAQ,EAAQ,eAChB,iBAAkB,EAAQ,iBAC1B,UAAW,EAAqB,EAAQ,QAAQ,SAAS,CAC1D,CACD,MAAO,CACL,WAAY,EAAmB,CAC7B,GAAG,EACH,SAAU,EAAQ,QAAQ,SAC1B,KAAM,EAAQ,QAAQ,KACtB,kBAAmB,EAAQ,kBAC5B,CAAC,CACF,gBAAiB,EAAwB,CACvC,GAAG,EACH,OAAQ,GACR,SAAU,GACV,KAAM,GACP,CAAC,CACH,CAGH,SAAS,EAAc,EAA6C,CAClE,MAAO,CACL,UAAW,EAAQ,UACnB,QAAS,CAAC,GAAG,EAAQ,QAAQ,CAC7B,QAAS,EAAQ,QACjB,OAAQ,EAAQ,OAChB,KAAM,EAAQ,KACd,SAAU,EAAQ,SAClB,OAAQ,EAAQ,OAChB,IAAK,EAAQ,IACd,CAGH,SAAS,EAAe,EAAuC,CAC7D,MAAO,CAAC,EAAU,UAAW,EAAU,KAAO,OAAS,IAAA,GAAW,EAAU,SAAW,YAAc,IAAA,GAAU,CAC5G,OAAQ,GAA2B,EAAQ,EAAO,CAClD,KAAK,IAAI,CAGd,SAAS,EAAgB,EAAwB,CAC/C,OAAO,EAAO,QAAQ,SAAU,iBAAiB,CAUnD,eAAsB,EAAqB,EAGxC,CACD,IAAM,EAAiB,EAAQ,eAAe,IAAK,GAAU,EAAK,QAAQ,EAAM,CAAC,CAC3E,EAAY,CAAE,GAAG,EAAQ,UAAW,QAAS,CAAC,GAAG,EAAQ,UAAU,QAAQ,CAAE,CAC7E,EAAS,MAAM,EACnB,EACA,EAAQ,gBAAkB,EAAK,KAAK,EAAQ,SAAU,EAA0B,CAChF,CACE,gBAAiB,EAAQ,iBAAmB,EAAK,KAAK,EAAQ,SAAU,EAAoB,CAC5F,WAAY,EAAQ,YAAc,EAAe,EAAU,CAC5D,CACF,CACK,EAAW,EAAgB,EAAO,CAClC,EAA8B,CAClC,QAAS,EACT,SAAU,EACV,YACA,iBACA,qBAAsB,EAAQ,qBAAuB,EAAE,EAAE,IAAK,GAAU,EAAK,QAAQ,EAAM,CAAC,CAC5F,UAAW,CACT,YAAa,EAAQ,YAAc,EAAE,EAAE,IAAK,GAAU,EAAK,QAAQ,EAAM,CAAC,CAC1E,aAAc,EAAQ,aAAe,EAAE,EAAE,IAAK,GAAU,EAAK,QAAQ,EAAM,CAAC,CAC7E,CACF,CAED,OADA,EAAgB,EAAU,GAAG,KAAK,UAAU,EAAU,KAAM,EAAE,CAAC,IAAI,CAC5D,CAAE,SAAQ,WAAU,CAI7B,eAAsB,EACpB,EACA,EAA6B,EAA2B,EAAQ,CACnC,CAC7B,IAAM,EAAQ,MAAM,EAAqB,CACvC,SAAU,EAAQ,QAAQ,SAC1B,UAAW,EAAc,EAAQ,QAAQ,CACzC,eAAgB,CAAC,GAAG,EAAK,WAAW,CAAC,SAAS,CAC9C,oBAAqB,EAAK,gBAC1B,WAAY,EAAQ,UAAU,iBAC9B,YAAa,EAAQ,QAAQ,IAAK,GAAW,EAAO,UAAU,CAC/D,CAAC,CACF,MAAO,CAAE,GAAG,EAAM,GAAG,EAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi",
3
- "version": "0.0.1-alpha.2",
3
+ "version": "0.0.1-alpha.4",
4
4
  "description": "Pi-based agent harness for repository automations and developer workflows",
5
5
  "homepage": "https://agimon.ai",
6
6
  "license": "MIT",
@@ -327,21 +327,21 @@
327
327
  "protobufjs": "8.0.1",
328
328
  "rolldown": "1.0.0-rc.17",
329
329
  "yaml": "2.9.0",
330
- "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.2",
331
- "@agimon-ai/doompi-config": "0.0.1-alpha.2",
332
- "@agimon-ai/doompi-loop": "0.0.1-alpha.2",
333
- "@agimon-ai/doompi-autocompact": "0.0.1-alpha.2",
334
- "@agimon-ai/doompi-runner": "0.0.1-alpha.2",
335
- "@agimon-ai/doompi-mcp": "0.0.1-alpha.2",
336
- "@agimon-ai/doompi-plan": "0.0.1-alpha.2",
337
- "@agimon-ai/doompi-log": "0.0.1-alpha.2",
338
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.2",
339
- "@agimon-ai/doompi-team": "0.0.1-alpha.2",
340
- "@agimon-ai/doompi-task": "0.0.1-alpha.2",
341
- "@agimon-ai/doompi-file-edit": "0.0.1-alpha.2",
342
- "@agimon-ai/doompi-ui": "0.0.1-alpha.2",
343
- "@agimon-ai/doompi-workflow": "0.0.1-alpha.2",
344
- "@agimon-ai/doompi-voice": "0.0.1-alpha.2"
330
+ "@agimon-ai/doompi-autocompact": "0.0.1-alpha.4",
331
+ "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.4",
332
+ "@agimon-ai/doompi-log": "0.0.1-alpha.4",
333
+ "@agimon-ai/doompi-config": "0.0.1-alpha.4",
334
+ "@agimon-ai/doompi-file-edit": "0.0.1-alpha.4",
335
+ "@agimon-ai/doompi-loop": "0.0.1-alpha.4",
336
+ "@agimon-ai/doompi-mcp": "0.0.1-alpha.4",
337
+ "@agimon-ai/doompi-plan": "0.0.1-alpha.4",
338
+ "@agimon-ai/doompi-runner": "0.0.1-alpha.4",
339
+ "@agimon-ai/doompi-task": "0.0.1-alpha.4",
340
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.4",
341
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.4",
342
+ "@agimon-ai/doompi-workflow": "0.0.1-alpha.4",
343
+ "@agimon-ai/doompi-voice": "0.0.1-alpha.4",
344
+ "@agimon-ai/doompi-team": "0.0.1-alpha.4"
345
345
  },
346
346
  "devDependencies": {
347
347
  "@earendil-works/pi-ai": "0.84.1",